Whisper-Transkripte annotieren
Wie aus Whisper- oder WhisperX-Ausgaben ein laufendes Annotationsprojekt wird: welche Ausgabedatei du behältst, wann du Diarisierung brauchst, wie Sprecher zugewiesen werden und wie du mit erhaltenen Zeitangaben exportierst.
Whisper liefert dir zeitcodierte Segmente, und ein Annotationswerkzeug sollte sie so lesen, wie sie sind. Behalte die .json-Ausgabe statt der .txt, nimm WhisperX, wenn du Sprecherlabels brauchst, und richte Potato dann auf den Ordner. Kein Konvertierungsskript, kein Umformatieren. Die Details zu den einzelnen Formaten stehen unter Transkriptformate.
Whisper ist das quelloffene Modell von OpenAI zur Spracherkennung und inzwischen der übliche erste Schritt, wenn ein Stapel Audio abzuarbeiten ist. Heraus kommen zeitcodierte Segmente, also fast alles, was ein Annotationsprojekt braucht. Die Lücke ist alles zwischen «ich habe Transkripte» und «die Annotierenden labeln sie».
Hinweis: Potato transkribiert nicht. Die ASR läuft vorgelagert, Potato liest deren Ausgabe ein. Dieser Leitfaden behandelt, welche vorgelagerten Entscheidungen zählen, aber die Transkription selbst passiert, bevor Potato irgendetwas sieht.
Welche Whisper-Ausgabedatei soll ich behalten?
Whisper schreibt mehrere Dateien, und die Wahl wiegt schwerer, als sie aussieht:
whisper interview_01.mp3 --model medium --output_format json --word_timestamps True| Datei | Inhalt | Behalten? |
|---|---|---|
.json | Segmente mit Start- und Endzeit, optional Zeiten pro Wort | Ja, diese hier |
.srt / .vtt | Segmente mit Zeitangaben, keine Metadaten | Ja, funktioniert |
.tsv | Start und Ende in Millisekunden plus Text | Ja |
.txt | Nur Text, keine Zeitangaben | Nein, nichts zum Synchronisieren |
Wenn du nur die .txt behalten hast, ist die Ausrichtung weg und ohne erneuten Modelllauf nicht wiederherstellbar. Das ist der häufigste Grund, warum ein Transkript in Potato als ein einziger ungegliederter Textblock ankommt.
Labelt Whisper Sprecher?
Nein, und das überrascht immer wieder. Whisper transkribiert, aber sagt dir nicht, wer spricht. Jeder Turn kommt ohne Zuordnung an, was für inhaltliche Annotation in Ordnung ist und für alles Sprecherbezogene mühsam.
Die Sprecherdiarisierung ist ein eigener Schritt. Drei Wege, damit umzugehen:
WhisperX einsetzen, das Whisper um die Diarisierung von pyannote ergänzt:
whisperx interview_01.mp3 --model medium --diarize --output_format jsonSeine Ausgabe trägt pro Segment ein speaker-Feld (SPEAKER_00, SPEAKER_01 und so weiter), und Potato liest es direkt.
Eine Cloud-API mit aktivierter Diarisierung nutzen. Deepgram mit diarize=true, AssemblyAI mit speaker_labels, AWS Transcribe und Rev.ai liefern alle Sprecherlabels, und Potato liest alle vier nativ.
Nicht diarisierte Turns erscheinen als Unassigned mit einer Sprecherauswahl
Die Annotierenden machen es. Kommen die Turns ohne Diarisierung an, erscheint jede Blase als Unassigned mit einer Auswahl. Bei schlechtem Audio, überlappender Sprache oder Hintergrundgeräuschen im Raum ist ein zuhörender Mensch oft genauer als die automatische Diarisierung. Für ein kleines Korpus ist das ein vernünftiger Tausch und keine Notlösung.
Wie erzeuge ich die Datendatei?
Richte den Konverter auf deinen Whisper-Ausgabeordner:
potato transcripts ./whisper_out --media-dir ./audio -o data/interviews.jsonTranskripte werden über den Dateinamen ohne Endung mit den Medien gepaart, interview_01.json findet also interview_01.mp3. Die Element-IDs stammen aus dem Dateinamen, und Whispers doppelte Endung interview_01.mp3.json wird abgefangen, ohne eine ID interview_01.mp3 zu erzeugen.
Sieh nach, was verstanden wurde, bevor du dich darauf festlegst:
potato transcripts ./whisper_out --dry-runScanned 3 file(s):
interview_01.json Whisper JSON 42 turns 891.4s undiarized
interview_02.json WhisperX JSON 51 turns 1120.8s 2 speaker(s): SPEAKER_00, SPEAKER_01
interview_03.json Whisper JSON 38 turns 754.2s undiarized
3 item(s), 131 turn(s).
Eine Datei, die plain text oder null Turns meldet, ist dein Problemfall. Fast immer ist es eine .txt, die sich unter die .json-Ausgabe gemischt hat.
Kann ich den Konvertierungsschritt überspringen?
Ja. Eine Datendatei kann direkt auf die Transkriptdateien auf der Platte zeigen:
{"id": "int_001", "conversation": {"audio": "media/int_001.mp3",
"transcript": "media/int_001.srt"}}Potato liest die Datei, erkennt das Format am Inhalt und normalisiert es beim Rendern der Instanz. Deine Transkripte bleiben Dateien, die du diffen und neu exportieren kannst, statt in einem Datenblob einzubacken.
Wie sieht die Konfiguration aus?
Die erzeugte Datendatei hat diese Form:
{
"id": "interview_01",
"conversation": {
"audio": "audio/interview_01.mp3",
"turns": [
{"turn_id": "t0", "speaker": "SPEAKER_00", "start": 0.0, "end": 6.5,
"text": "Welcome back."}
]
}
}Und die passende Konfiguration, die dir potato transcripts --emit-config ausgibt:
annotation_task_name: "Interview Annotation"
task_dir: .
data_files:
- data/interviews.json
item_properties:
id_key: id
text_key: conversation
instance_display:
fields:
- key: conversation
type: audio_dialogue
label: "Transcript"
span_target: true
display_options:
show_timestamps: true
allow_speaker_assignment: auto
annotation_schemes:
- annotation_type: span
name: topics
description: "Highlight topic mentions"
labels:
- name: policy
- name: personal
Die audio_dialogue-Darstellung, mit einer Frage an jedem einzelnen Turn
Die audio_dialogue-Darstellung zeigt Turns als Sprecherblasen synchron zum Audio, mit einem Wiedergabeknopf pro Turn, der nur diesen Turn abspielt. span_target: true erlaubt Spans über Turngrenzen hinweg, und die Offsets bleiben stabil, wenn ein Sprecher neu zugewiesen wird.
Sollen die Annotierenden lieber die Transkription selbst prüfen als ihren Inhalt labeln, liefert speech_transcript Segmentkarten mit Fehler-Tags und einem Korrekturfeld. Für Arbeit an Überlappungen und Unterbrechungen liefert voice_interaction eine Zeitleiste mit zwei Spuren. Beide lesen dieselben Dateien.
Wie weisen Annotierende Sprecher zu?
Definiere eine Sprecherliste, damit Sprecher stabile Namen, Farben und Seiten bekommen:
display_options:
allow_speaker_assignment: auto
speakers:
- id: interviewer
name: "Interviewer"
color: "#7c3aed"
side: left
- id: participant
name: "Participant"
color: "#059669"
side: rightauto schaltet die Auswahl ein, sobald es nicht diarisierte Turns zu labeln gibt. Auf true gesetzt erlaubt es die Neuzuweisung auch dann, wenn die Quelle Sprecher gelabelt hat, was du willst, wenn du Diarisierungsfehler korrigierst statt Lücken zu füllen.
Zuweisungen werden mit den Annotationen gespeichert, verankert an einer stabilen Turn-ID, und überstehen so ein Neuladen.
Wie bekomme ich die Annotationen wieder heraus?
Export nach JSON, JSONL und CSV funktioniert wie gewohnt. Soll die Zeitausrichtung erhalten bleiben, exportiere die Annotationsebenen nach ELAN EAF oder Praat TextGrid:
python -m potato.export --config config.yaml --format eaf --output ./out/
python -m potato.export --config config.yaml --format textgrid --output ./out/Beides funktioniert in beide Richtungen. Potato liest EAF und TextGrid auch als Eingabe, du kannst also in Potato annotieren, in ELAN oder Praat nacharbeiten und das Ergebnis wieder einlesen.
Weiterführende Lektüre
- Transkriptformate, alle unterstützten Formate und wie sie erkannt werden
- YouTube-Untertitel annotieren, dieselbe Arbeitsweise ausgehend von Untertiteldateien
- Audio-Annotation, für Arbeit an der Wellenform ohne Transkript als Ausgangspunkt
- Transcript Format Ingestion, ein lauffähiges Showcase-Design mit sechs Formaten nebeneinander
- Speaker Diarization, ein lauffähiges Showcase-Design
- Inter-Annotator-Übereinstimmung, zur Messung der Übereinstimmung bei Labels auf Turn-Ebene