Zum Hauptinhalt springen

Spracherkennung mit Cohere Transcribe

cohere-transcribe-03-2026 erreichst du über dieselbe Route /v1/audio/transcriptions wie die OpenAI-Transkriptions-API. Code, der für OpenAI geschrieben wurde, funktioniert, wenn du base_url änderst. Über die reine Transkription hinaus trennt das Modell Sprecher, liefert Wort-Zeitstempel und schreibt Untertitel. Sprachen, Eingabeformate, Limits und die vollständigen Antworten stehen auf der Modellseite zu Cohere Transcribe.

Für die reine Transkription mit whisper-large-v3-turbo siehe Spracherkennung mit Whisper.

Vorbereitung​

user@local $ pip install openai
user@local $ export OPENAI_API_KEY="sk-…"

Einfache Transkription​

import os
from openai import OpenAI

client = OpenAI(
base_url="https://llm.aihosting.mittwald.de/v1",
api_key=os.environ["OPENAI_API_KEY"],
)

with open("meeting.mp3", "rb") as f:
result = client.audio.transcriptions.create(
model="cohere-transcribe-03-2026",
file=f,
language="de",
)

print(result.text)

Ohne language erkennt das Modell die Sprache selbst. Setz den Parameter, wenn du die Sprache kennst: Bei sehr kurzen oder ungewöhnlichen Aufnahmen kann die Erkennung auf Deutsch zurückfallen. Eine Aufnahme muss bei einer Sprache bleiben, siehe Eine Sprache pro Anfrage.

Die folgenden Beispiele verwenden client aus diesem Block weiter.

Wer hat was gesagt: Sprechertrennung​

response_format="diarized_json" teilt das Transkript in Segmente nach Sprecher:

with open("interview.mp3", "rb") as f:
result = client.audio.transcriptions.create(
model="cohere-transcribe-03-2026",
file=f,
language="de",
response_format="diarized_json",
)

for seg in result.segments:
print(f"[{seg.start:7.2f}s] {seg.speaker}: {seg.text}")
[ 0.38s] SPEAKER_00: Am Ende wird es wieder in eine Fressorgie ausarten.
[ 4.78s] SPEAKER_01: Ja, aber bei mir ist das auch etwas ganz anderes.

Wie viele Personen sprechen, schätzt das Modell selbst. Kennst du die Zahl, gib sie als num_speakers mit. Das OpenAI-SDK hat dafür kein eigenes Argument, der Wert gehört deshalb in extra_body:

with open("interview.mp3", "rb") as f:
result = client.audio.transcriptions.create(
model="cohere-transcribe-03-2026",
file=f,
language="de",
response_format="diarized_json",
extra_body={"num_speakers": 2},
)

Kennst du nur einen Bereich, nimm auf dieselbe Weise min_speakers und max_speakers. Alle drei nehmen Ganzzahlen von 1 bis 20.

Sprechzeit pro Sprecher​

diarized_json enthält außerdem speaker_analytics. Das SDK kennt dieses Feld nicht, lies es deshalb aus model_dump():

data = result.model_dump()
analytics = data["speaker_analytics"]

for s in analytics["speakers"]:
print(f"{s['speaker']}: {s['speaking_time']:.0f}s, {s['speaking_share']:.0%}, {s['turns']} turns")

print(f"Speech {analytics['total_speech_time']:.0f}s, silence {analytics['silence_time']:.0f}s")
SPEAKER_00: 41s, 52%, 7 turns
…
Speech 79s, silence 13s

Telefonaufnahmen mit einem Sprecher pro Kanal​

Viele Telefonanlagen zeichnen jede Seite eines Gesprächs auf einem eigenen Kanal auf. Schick eine solche Zweikanaldatei wie oben mit response_format="diarized_json". Das Modell erkennt, dass jeder Kanal einen Sprecher enthält, und transkribiert die Kanäle getrennt, statt die Stimmen per Schätzung zu trennen. Die Kennungen nennen dann den Kanal, CHANNEL_00 und CHANNEL_01, sodass du weißt, welche Seite des Gesprächs was gesagt hat.

wav-Dateien mit 8 kHz und µ-law, wie viele Telefonanlagen sie schreiben, funktionieren ohne Umwandlung.

Wort-Zeitstempel​

Fordere timestamp_granularities=["word"] zusammen mit response_format="verbose_json" an:

with open("ansage.wav", "rb") as f:
result = client.audio.transcriptions.create(
model="cohere-transcribe-03-2026",
file=f,
language="de",
response_format="verbose_json",
timestamp_granularities=["word"],
)

for w in result.words:
print(f"{w.start:6.2f}–{w.end:6.2f} {w.word} ({w.confidence:.2f})")
0.04– 0.26 Heute (0.97)
0.36– 0.72 regnet (0.95)

confidence gibt an, wie gut sich das Wort dem Audio zuordnen ließ, nicht wie sicher sich das Modell ist, das Wort richtig gehört zu haben. Wort-Zeitstempel gibt es für de, en, es, fr, it, nl, pl und pt. verbose_json enthält außerdem immer die Segmente, result.segments ist also ebenfalls gefüllt.

Dasselbe Argument funktioniert mit response_format="diarized_json", wenn du Sprecher und Wort-Zeitstempel in einer Anfrage brauchst.

Untertitel​

response_format="srt" oder "vtt" liefert eine fertige Untertiteldatei. Das SDK gibt sie als String zurück:

with open("video.mp4", "rb") as f:
subtitles = client.audio.transcriptions.create(
model="cohere-transcribe-03-2026",
file=f,
language="de",
response_format="srt",
)

with open("video.srt", "w", encoding="utf-8") as out:
out.write(subtitles)

Die Untertitel sind an Wortgrenzen geschnitten und kurz genug, um sie am Bildschirm zu lesen. Aus einer mp4-Datei transkribiert das Modell die Tonspur, du musst sie vorher nicht herauslösen.

Für Untertitel mit Sprecherkennung ergänze diarize:

with open("video.mp4", "rb") as f:
subtitles = client.audio.transcriptions.create(
model="cohere-transcribe-03-2026",
file=f,
language="de",
response_format="vtt",
extra_body={"diarize": "true"},
)

In vtt wird die Kennung zum Voice-Tag:

00:00:00.380 --> 00:00:04.220
<v SPEAKER_00>Am Ende wird es wieder in eine Fressorgie ausarten.

In srt steht die Kennung als SPEAKER_00: … vor dem Text.

Namen und Fachbegriffe​

Gib Namen, Produktnamen oder Fachbegriffe, die in der Aufnahme vorkommen, als prompt mit. Das Modell nutzt sie als Hinweis für die Schreibweise:

with open("standup.mp3", "rb") as f:
result = client.audio.transcriptions.create(
model="cohere-transcribe-03-2026",
file=f,
language="de",
prompt="mStudio, Kubernetes, Container, Deployment",
)

Push-to-Talk und Diktat​

Für Push-to-Talk brauchst du kein Streaming: Nimm auf, solange die Taste gedrückt ist, und lade den Clip hoch, wenn sie losgelassen wird. Die Transkription kommt so schnell zurück, dass der Text direkt nach dem Loslassen erscheint. In unserem Test mit dem Beispiel unten stand der Text 0,2 bis 0,4 Sekunden nach dem Loslassen da. Dazu kommt der Weg durch dein Netz.

Ruf die API nicht direkt aus dem Browser auf, sonst steht dein API-Key im Quelltext der Seite. Lass den Browser die Aufnahme an dein eigenes Backend schicken und reich sie von dort weiter. Ein minimales Backend mit FastAPI:

user@local $ pip install openai fastapi uvicorn python-multipart
from fastapi import FastAPI, UploadFile
from fastapi.responses import FileResponse

app = FastAPI()


@app.get("/")
def seite():
return FileResponse("index.html")


@app.post("/api/transcribe")
def transkribieren(file: UploadFile):
result = client.audio.transcriptions.create(
model="cohere-transcribe-03-2026",
file=(file.filename, file.file.read(), file.content_type),
language="de",
)
return {"text": result.text}

Starte es mit uvicorn app:app. Die index.html daneben nimmt mit dem MediaRecorder des Browsers auf, solange die Taste gedrückt ist:

<button id="talk">Gedrückt halten und sprechen</button>
<p id="text"></p>

<script type="module">
const button = document.getElementById("talk");
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
let recorder;

button.addEventListener("pointerdown", () => {
const chunks = [];
recorder = new MediaRecorder(stream);
recorder.ondataavailable = (event) => chunks.push(event.data);
recorder.onstop = async () => {
// Chrome und Firefox nehmen webm auf, Safari mp4
const name = recorder.mimeType.includes("mp4")
? "speech.m4a"
: "speech.webm";
const form = new FormData();
form.append("file", new Blob(chunks, { type: recorder.mimeType }), name);
const response = await fetch("/api/transcribe", {
method: "POST",
body: form,
});
document.getElementById("text").textContent = (
await response.json()
).text;
};
recorder.start();
});

button.addEventListener("pointerup", () => recorder?.stop());
</script>

Setz bei Push-to-Talk language. Ein Clip mit wenigen Wörtern ist zu kurz für eine verlässliche Spracherkennung.

Fast live transkribieren​

cohere-transcribe-03-2026 transkribiert kein Audio, das noch aufgenommen wird, und für das Modell gibt es keinen Endpunkt /v1/realtime. Für Live-Untertitel oder fortlaufendes Diktat schneidest du das Mikrofonsignal bei dir an Sprechpausen und schickst jeden Satz als eigene Anfrage. Ein Satz braucht nur einen Bruchteil einer Sekunde zum Transkribieren, der Text erscheint also kurz nach jeder Pause.

Das folgende Beispiel erkennt Pausen an der Lautstärke, ohne zusätzliches Modell. Wer flüssig spricht, macht selten lange Pausen. Deshalb beendet schon eine Pause von 300 ms einen Satz, sobald er mindestens 3 Sekunden lang ist, und nach 15 Sekunden schneidet das Beispiel in jedem Fall. In unseren Tests mit einer Interviewaufnahme stand ein Wort im Mittel rund 5 Sekunden nach dem Sprechen da, höchstens nach rund 15 Sekunden. Für Diktat und Notizen reicht das, für Live-Untertitel oder Sprachassistenten, die innerhalb einer Sekunde reagieren müssen, ist es zu langsam. Es braucht numpy und sounddevice:

user@local $ pip install numpy sounddevice
import io
import queue
import wave

import numpy as np
import sounddevice as sd

RATE = 16000
FRAME = 480 # 30 ms pro Frame
THRESHOLD = 0.02 # Lautstärke, die als Sprache zählt, an dein Mikrofon anpassen
PAUSE_FRAMES = 10 # 300 ms unter dem Schwellwert beenden einen Satz ...
MIN_FRAMES = 100 # ... sobald er mindestens 3 s lang ist
MAX_FRAMES = 500 # Nach 15 s auch ohne Pause schneiden


def mikrofon():
"""Liefert das Mikrofonsignal in Frames zu 30 ms."""
frames = queue.Queue()
with sd.InputStream(samplerate=RATE, channels=1, blocksize=FRAME, dtype="float32",
callback=lambda data, *_: frames.put(data[:, 0].copy())):
while True:
yield frames.get()


def saetze(frames):
"""Fasst Frames zu Sätzen von 3 bis 15 Sekunden zusammen, die an einer Pause enden."""
aktuell, leise = [], 0
for frame in frames:
laut = np.sqrt(np.mean(frame ** 2)) > THRESHOLD
if laut or aktuell:
aktuell.append(frame)
leise = 0 if laut else leise + 1
if (leise >= PAUSE_FRAMES and len(aktuell) >= MIN_FRAMES) or len(aktuell) >= MAX_FRAMES:
yield np.concatenate(aktuell)
aktuell, leise = [], 0


def als_wav(samples):
buf = io.BytesIO()
with wave.open(buf, "wb") as w:
w.setnchannels(1)
w.setsampwidth(2)
w.setframerate(RATE)
w.writeframes((np.clip(samples, -1, 1) * 32767).astype(np.int16).tobytes())
return buf.getvalue()


for satz in saetze(mikrofon()):
if len(satz) < RATE // 2: # Klicks und Husten unter 0,5 s überspringen
continue
result = client.audio.transcriptions.create(
model="cohere-transcribe-03-2026",
file=("satz.wav", als_wav(satz)),
language="de",
)
print(result.text, flush=True)

Während eine Anfrage läuft, nimmt das Mikrofon weiter in die Queue auf, es geht also nichts verloren. Jede Anfrage wird nach ihrer Audiodauer abgerechnet, die Pausen zwischen den Sätzen werden gar nicht erst geschickt. Sprecherkennungen und Wort-Zeitstempel gelten auch hier pro Anfrage, sie fangen also mit jedem Satz von vorn an.

Lange Aufnahmen​

Ein Upload darf bis zu 25 MB groß sein, das Audio bis zu 95 Minuten lang. Bei langen Aufnahmen stößt du zuerst an die Dateigröße, komprimier sie deshalb vor dem Upload zu mp3 oder opus in mono. Bei 64 kbit/s passen in 25 MB rund 52 Minuten:

user@local $ ffmpeg -i recording.wav -ac 1 -c:a libopus -b:a 64k recording.ogg

Noch längere Aufnahmen brauchen eine niedrigere Bitrate oder müssen aufgeteilt werden. Dafür kannst du das pydub-Beispiel aus dem Whisper-Guide mit längeren Abschnitten verwenden. Die Sprecherkennungen werden pro Anfrage vergeben, SPEAKER_00 im einen Teil ist also nicht unbedingt dieselbe Person wie SPEAKER_00 im nächsten.

Fehler behandeln​

Ungültige Anfragen antworten mit HTTP 400 und einer Meldung, die sagt, was nicht stimmt, zum Beispiel bei einer nicht unterstützten Sprache, einer Datei, die kein Audio ist, oder einem Video ohne Tonspur. Dateien über 25 MB und Audio über 95 Minuten antworten mit HTTP 413. Das OpenAI-SDK löst dafür Exceptions aus:

import openai

try:
with open("meeting.mp3", "rb") as f:
result = client.audio.transcriptions.create(
model="cohere-transcribe-03-2026",
file=f,
language="de",
)
except openai.BadRequestError as e:
print(f"Request rejected: {e.message}")
except openai.APIStatusError as e:
if e.status_code == 413:
print("File too large or recording too long, compress or split it")
else:
raise

Streaming wird nicht unterstützt: stream=True antwortet mit HTTP 400. Schick die Anfrage ohne stream, um das vollständige Transkript zu bekommen.

Umstieg von Whisper​

Code, der für whisper-large-v3-turbo geschrieben wurde, funktioniert weiter, wenn du den Modellnamen änderst. Das sind die Unterschiede:

whisper-large-v3-turbocohere-transcribe-03-2026
Sprachenüber 9914
Ohne languagenimmt Deutsch anerkennt die Sprache
Audio pro Anfrage30 Minuten95 Minuten
response_formatjson, verbose_jsonjson, text, srt, vtt, verbose_json, diarized_json
Wort-Zeitstempelneinin 8 Sprachen
Sprechertrennungneinja
Lebenszyklus-StatusLTSTestphase