Skip to content

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:

bash
whisper interview_01.mp3 --model medium --output_format json --word_timestamps True
DateiInhaltBehalten?
.jsonSegmente mit Start- und Endzeit, optional Zeiten pro WortJa, diese hier
.srt / .vttSegmente mit Zeitangaben, keine MetadatenJa, funktioniert
.tsvStart und Ende in Millisekunden plus TextJa
.txtNur Text, keine ZeitangabenNein, 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:

bash
whisperx interview_01.mp3 --model medium --diarize --output_format json

Seine 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.

Drei Transkript-Turns mit schraffiertem grauem Hintergrund, jeweils mit der Beschriftung Unassigned und einem Auswahlpfeil.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:

bash
potato transcripts ./whisper_out --media-dir ./audio -o data/interviews.json

Transkripte 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:

bash
potato transcripts ./whisper_out --dry-run
text
Scanned 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:

json
{"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:

json
{
  "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:

yaml
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

Transkript-Turns als farbige Sprecherblasen mit Wiedergabeknopf und Zeitstempel je Turn und einer eingebetteten Labelfrage unter jedem Turn.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:

yaml
display_options:
  allow_speaker_assignment: auto
  speakers:
    - id: interviewer
      name: "Interviewer"
      color: "#7c3aed"
      side: left
    - id: participant
      name: "Participant"
      color: "#059669"
      side: right

auto 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:

bash
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