Skip to content

文字起こし形式

Potato が読める文字起こし・字幕形式の一覧。Whisper、WhisperX、Deepgram、AssemblyAI、AWS Transcribe、SRT、WebVTT、TTML、YouTube 字幕、CTM、Praat TextGrid、ELAN EAF に加え、サイドカーファイルと正規化されたターンモデルについて。

Potato は 21 種類の文字起こし・字幕入力を読み、表示する前にすべてを同じターンモデルへ変換します。音声認識パイプラインや字幕ツールが何を出力していても、変換スクリプトを書かずにそのまま Potato を向けられます。 このページは、何が使えるか、各形式がどう判定されるか、それぞれが何をもたらすかの参照資料です。手順を追った説明はWhisper の文字起こしをアノテーションする方法YouTube の字幕をアノテーションする方法にあります。

色分けされた話者バブルとして表示された文字起こし。各バブルに再生ボタン、時刻、ターン単位のラベル質問が付いている。サイドカーの SRT ファイルが話者バブルとして表示され、各ターンに質問が付いている

Potato は文字起こしをしない

音声認識話者分離は、Potato が何かを見る前に行われます。まず Whisper、WhisperX、pyannote、あるいはクラウド API を実行してください。Potato はその結果を取り込みます。このページに書かれていることでモデルが動くことはありません。

Note: Think-Aloud モードはたしかにローカルの Whisper モデルを動かしますが、目的が違います。作業中に声に出して考えるアノテーターを録音する機能です。これは収録の機能であって、文字起こしの取り込みではありません。

判定は拡張子ではなく中身で行う

Potato はファイル名を信用せず、中身を調べます。captions.txt という名前の WebVTT ファイルも WebVTT として解析されます。実務上の意味は、同じ文字起こしがデータファイルに埋め込まれていても、ディスク上のサイドカーファイルから読まれても、設定を変えずに機能するということです。

対応形式

音声認識の出力

形式判定材料話者単語単位の時間
Whisper JSONsegments 配列なしあり(--word_timestamps 使用時)
WhisperX / 話者分離済み JSONspeaker を含む segmentsありあり
whisper.cpp JSONtranscription 配列なしなし
Whisper TSVstart/end/text ヘッダーなしなし
AWS Transcriberesults.items または results.audio_segmentsありあり
Deepgramresults.channels または results.utterancesあり(diarize=true 時)あり
AssemblyAItextwords/utterancesあり(speaker_labels 時)あり
Rev.aimonologues 配列ありあり
SPoRCturn_text/turnText の行あり(推定)なし

字幕・キャプション

形式判定材料話者
SubRip(.srtキューの矢印、,mmm 区切りName: 接頭辞から
WebVTT(.vttWEBVTT ヘッダー<v Name> タグまたは Name: 接頭辞から
SubStation Alpha(.ass.ssa[Script Info] / Dialogue:Name フィールドから
TTML / DFXP<tt> ルート要素speaker/agent 属性から
YouTube srv1/srv2/srv3<transcript><text> XMLName: 接頭辞から
YouTube json3events 配列なし。自動生成字幕には話者情報がない

SubRipWebVTT で、実際に出会うものの大半はカバーできます。TTML は放送アーカイブや、業務用字幕ツールからの書き出しで見かけます。

アライメントと言語学的アノテーション

形式判定材料話者
NIST CTM空白区切りの列、数値の開始時刻と長さチャンネルフィールドから
Praat TextGridFile type = "ooTextFile"話者ごとに 1 ティア
ELAN EAFANNOTATION_DOCUMENT ルートPARTICIPANT から、なければティア ID

TextGrid は長形式・短形式のどちらも解析されます。ELAN ファイルでは、Potato が ALIGNABLE_ANNOTATIONREF_ANNOTATIONTIME_ORDER テーブルに対して解決し、ヘッダーからメディア参照を読み取ります。

Praat TextGrid が fieldworker と speaker という 2 人の話者として表示され、ティアごとに 1 人ずつ、無音区間は省かれている。TextGrid の各ティアが話者になり、その間の空区間は飛ばされる

その他

三つの汎用的な形が 21 の残りを埋めます。

  • {speaker, start, end, text} 辞書の単純なリスト。
  • {"audio": ..., "turns": [...]} オブジェクト。
  • 時間情報のまったくない平文の段落。一つのバブルとして表示されます。

非対応

これらのパーサーはありません。先に変換してください。

  • SAMI(.smi)、MicroDVD(.sub)、SubViewer(.sbv
  • Transcriber(.trs)、EXMARaLDA、CHAT/CHILDES(.cha
  • Montreal Forced Aligner と Gentle のネイティブ出力。これらからは CTM か TextGrid で書き出してください。
  • 変わったラッパーに包まれた Azure Speech、Google Speech-to-Text、Whisper API の verbose_json
  • 単体の RTTM 話者分離ファイル

単語単位の信頼度は、元データにあれば解析されデータモデルに保持されますが、現時点でそれを表示する画面はありません。

正規化されたターンモデル

上記のすべての形式は、これになります。

json
{
  "audio": "media/interview_01.mp3",
  "turns": [
    {
      "turn_id": "t0",
      "speaker": "host",
      "start": 0.0,
      "end": 6.5,
      "text": "Welcome back.",
      "words": [{"word": "Welcome", "start": 0.0, "end": 0.4, "confidence": 0.99}],
      "confidence": 0.97
    }
  ]
}

wordsconfidence は元データに含まれていた場合にだけ現れます。話者分離されていないターンでは speakernull になり、アノテーターが埋められるよう選択メニュー付きの Unassigned として表示されます。

灰色のハッチング背景を持つ三つの文字起こしターン。元の形式に話者ラベルがなかったため、ドロップダウン矢印付きで Unassigned と表示されている。YouTube の自動生成字幕は話者情報なしで届くので、アノテーターが選ぶまで各ターンは Unassigned のまま

turn_id はターン単位のアノテーションと話者割り当ての保存キーです。元データに明示的な turn_id または step_id 文字列があればそれを使い、なければ t{index} になります。同じファイルからは常に同じ ID が生成されるので、再読み込みしてもアノテーションは残ります。

Warning: 時間の単位はツールによって異なります。Whisper と Deepgram は浮動小数点の秒を使い、AssemblyAI、whisper.cpp のオフセット、Whisper の TSV 出力は整数のミリ秒を使います。Potato は境界で変換するので、それより後はすべて秒になります。自前の前処理で両者を混同すると、時間が 1000 倍ずれます。

サイドカーファイル

既知の文字起こし拡張子で終わる短い 1 行のパスである値は、テキストとしてではなくディスクから読み込まれます。つまり、音声ファイルの隣に .srt.json が置かれた、音声認識ツールがすでに作った配置がそのまま前処理なしで使えます。

json
{
  "id": "int_001",
  "conversation": {
    "audio": "media/int_001.mp3",
    "transcript": "media/int_001.srt"
  }
}

パスは task_dir からの相対で解決され、他の設定パスと同じパスセキュリティ検証を通ります。したがってデータファイルがプロジェクト外を読むことはできません。

認識される拡張子: .srt .vtt .webvtt .json .json3 .srv1 .srv2 .srv3 .ttml .dfxp .xml .ass .ssa .tsv .ctm .TextGrid .eaf .txt

データにファイル名のように見える 1 行の文字起こしが本当に入っている場合は、この推定を切ってください。

yaml
display_options:
  transcript_is_path: auto   # auto (default) | true | false

設定

表示: audio_dialogue

音声対話表示はターンを音声と同期した話者バブルとして描き、各ターンに再生ボタンを付けます。

yaml
instance_display:
  fields:
    - key: conversation
      type: audio_dialogue
      label: "Transcript"
      span_target: true
      display_options:
        audio_key: audio
        turns_key: turns
        speaker_key: speaker
        text_key: text
        transcript_is_path: auto
        show_timestamps: true
        allow_speaker_assignment: auto
        scroll_height: 460px
オプション既定値動作
audio_keyaudio音声の URL またはパスを持つフィールド値のサブキー。
turns_keyturnsターン一覧を持つサブキー。segments も受け付けます。
speaker_key / text_keyspeaker / textターン単位のキー。
speakers[]{id, name, color, side} のリスト。一覧にない話者には決定的な色と交互の配置が割り当てられます。
allow_speaker_assignmentauto未分離のターンや修正すべきリストがあるとき、auto はクリックでの割り当てを有効にします。true は常時有効、false は無効。
transcript_is_pathautoフィールド値をサイドカーのパスとして読むかどうか。
show_timestampstrue各ターンに mm:ss–mm:ss を表示します。
scroll_height480pxスクロールする文字起こしペインの高さ。
playback_rates[1, 1.25, 1.5, 2]速度セレクタの選択肢。

スキーマ

2.7.1 以降、speech_transcriptvoice_interactiontiered_annotation はいずれもインスタンスのレコードから直接文字起こしを読み、このページのすべての形式を受け付けます。それ以前は audio_dialogue 表示だけがそうしていました。

yaml
annotation_schemes:
  - annotation_type: speech_transcript
    name: transcript_review
    description: "Mark transcription errors"
    segments_key: segments       # record field holding the transcript
 
  - annotation_type: voice_interaction
    name: barge_in
    description: "Mark overlaps and barge-in"
    turns_key: turns

階層アノテーションでは、文字起こしからティアをあらかじめ埋めておけます。アノテーターは音声を一から切り直すのではなく、既存のアライメントを修正することになります。

yaml
  - annotation_type: tiered_annotation
    name: tiers
    source_field: audio_url
    media_type: audio
    transcript_field: asr_output   # opt-in; omit to start from a blank timeline
    transcript_tier: utterance     # defaults to the first tier
    tiers:
      - name: utterance
        labels:
          - name: speech
            color: "#7c3aed"

あらかじめ埋められたアノテーションは、アノテーターが実際に編集するまで書き込まれません。インスタンスを開いただけの人に作業が帰属することはありません。

potato transcripts でデータファイルを作る

変換ツールを音声認識出力のフォルダに向けると、アノテーション可能なデータファイルを書き出します。

bash
# Pair transcripts to media by basename
potato transcripts ./whisper_out --media-dir ./audio -o data/interviews.json
 
# Media served from elsewhere
potato transcripts './captions/*.vtt' \
  --media-url-prefix https://cdn.example.org/audio -o data/talks.json
 
# What did it detect? Writes nothing.
potato transcripts ./whisper_out --dry-run

--dry-run は何も書かずに見つけたものを報告します。Whisper の間違った出力ファイルが入ったフォルダを見つけるいちばん速い方法です。

text
Scanned 3 file(s):
  talk_01.srt          SRT                  2 turns      5.0s  2 speaker(s): Alice, Bob
  talk_02.vtt          WebVTT               1 turns      3.0s  undiarized
  talk_03.json         whisper.cpp JSON     1 turns      2.4s  undiarized

3 item(s), 4 turn(s).
1 item(s) have no media. Pass --media-dir or --media-url-prefix to enable playback.
オプション用途
-o, --output書き出し先。--dry-run 以外では必須。
--formatjson(既定)または jsonl
--media-dirベース名で対応付ける素材のフォルダ。
--media-url-prefixローカルファイルの代わりに使う素材のベース URL。
--field文字起こしを入れる項目のフィールド。既定は conversation
--id-prefix生成される各 ID の前に付ける文字列。
--speaker-key話者ラベルを持つ元データのキー。
-r, --recursiveサブフォルダも辿ります。
--dry-runファイルごとに判定された形式とターン数を報告します。
--emit-config対応する config.yaml の断片も出力します。
-q, --quietファイルごとの報告を抑制します。

項目 ID はファイル名から末尾の素材拡張子を除いたものになるので、Whisper の interview_01.mp3.jsoninterview_01.mp3 ではなく interview_01 になります。

書き出して戻す

階層アノテーションは ELAN EAF と Praat TextGrid にエクスポートできるので、文字起こしは往復できます。取り込み、Potato でアノテーションし、書き出し、ELAN や Praat で仕上げて、その結果をまた読み込めます。

bash
python -m potato.export --config config.yaml --format eaf --output ./out/
python -m potato.export --config config.yaml --format textgrid --output ./out/

どちらのエクスポーターも tiered_annotation の出力を対象とします。他のスキーマは標準の形式で書き出されます。

トラブルシューティング

症状何が起きているか
すべてが一つの大きなバブルになる形式が判定できず、平文段落のフォールバックに落ちています。potato transcripts <file> --dry-run を実行し、plain text と出るなら時間情報は一度も読まれていません。たいてい .json.srt ではなく、時間情報を持たない Whisper の .txt です。
話者がなく、すべて Unassigned元データに話者ラベルがありません。Whisper 単体は話者分離せず、YouTube の自動生成字幕も同じです。上流で話者分離を実行するか、アノテーターに画面上で割り当ててもらってください。
時間が 1000 倍ずれている上流で秒とミリ秒が混ざりました。whisper.cpp の offsets、AssemblyAI、Whisper の TSV はいずれもミリ秒です。
文字起こしのパスがそのまま本文として出るサイドカーファイルが読めず、パスが内容として表示されています。task_dir の下で解決されるか確認してください。サーバーログに具体的な理由が記録されます。
1 行の文字起こしがファイル名として読まれる表示フィールドに transcript_is_path: false を設定してください。

サンプルプロジェクト

Potato リポジトリの examples/audio/transcript-formats/ は、六つの形式を並べて表示します。SubRip、WebVTT、Whisper JSON、YouTube json3、Praat TextGrid、Deepgram のそれぞれがサイドカーファイルから読み込まれ、六つとも同じ話者バブルになります。

bash
python potato/flask_server.py start examples/audio/transcript-formats/config.yaml -p 8000

同じタスクはダウンロードできるショーケースとしても用意されています: Transcript Format Ingestion

実装の詳細は元のドキュメントを参照してください。

関連文書