Skip to content

Whisper の文字起こしをアノテーションする方法

Whisper や WhisperX の出力をそのままアノテーションプロジェクトにする手順。どの出力ファイルを残すか、話者分離が必要になる場面、話者の割り当て方、時間情報を保ったままエクスポートする方法。

Whisper は時間付きのセグメントを返すので、アノテーションツールはそれをそのまま読むべきです。.txt ではなく .json 出力を残し、話者ラベルが必要なら WhisperX を実行して、あとは Potato をそのフォルダに向けるだけです。 変換スクリプトも整形処理も要りません。形式ごとの詳細は文字起こし形式を参照してください。

Whisper は OpenAI のオープンソース音声認識モデルで、大量の音声を扱う人の最初の一手として定着しました。出力されるのは時間付きセグメントの集合で、アノテーションプロジェクトが必要とするものの大部分がそこにあります。足りないのは「文字起こしはある」と「アノテーターがラベルを付けている」の間にあるすべてです。

Note: Potato は文字起こしをしません。音声認識は上流で実行され、Potato はその出力を取り込みます。このガイドは上流のどの選択が効いてくるかを扱いますが、文字起こし自体は Potato が何かを見る前に終わっています。

Whisper のどの出力ファイルを残すべきか

Whisper は複数のファイルを書き出し、その選択は見た目以上に重要です。

bash
whisper interview_01.mp3 --model medium --output_format json --word_timestamps True
ファイル中身使えるか
.json開始・終了時刻付きのセグメント、必要なら単語単位の時間もこれを使う
.srt / .vtt時間付きセグメント、メタデータなし使える
.tsvミリ秒単位の開始・終了とテキスト使える
.txtテキストのみ、時間情報なし使えない。音声と同期させる手がかりがない

.txt しか残っていなければ、時間の対応関係は失われており、モデルを再実行しない限り復元できません。文字起こしが Potato で一つの塊のテキストとして表示される原因は、ほとんどがこれです。

Whisper は話者ラベルを付けるか

付けません。ここで驚く人が非常に多いところです。Whisper は文字起こしをするだけで、誰が話しているかは教えてくれません。すべてのターンが未割り当てで届きます。内容のアノテーションには支障ありませんが、話者に関わる作業では厄介です。

話者分離は別の工程です。対処法は三つあります。

WhisperX を実行する。 Whisper を pyannote の話者分離で包んだものです。

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

出力にはセグメントごとの speaker フィールド(SPEAKER_00SPEAKER_01 など)が入り、Potato はそれを直接読みます。

話者分離を有効にしたクラウド API を使う。 diarize=true の Deepgram、speaker_labels の AssemblyAI、AWS Transcribe、Rev.ai はいずれも話者ラベルを出力し、Potato は四つとも直接読めます。

灰色のハッチング背景を持つ三つの文字起こしターン。それぞれにドロップダウン矢印付きで Unassigned と表示されている。話者分離されていないターンは、話者選択メニュー付きの Unassigned として表示される

アノテーターに任せる。 話者分離されていないターンは、各バブルが選択メニュー付きの Unassigned として表示されます。音質が悪い、発話が重なる、背景音が多いといった状況では、人が聴いて判断するほうが自動の話者分離より正確なことがよくあります。小規模なコーパスなら、これは妥協ではなく妥当な選択です。

データファイルはどう作るか

変換ツールを Whisper の出力フォルダに向けます。

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

文字起こしはベース名で音声と対応付けられるので、interview_01.jsoninterview_01.mp3 を見つけます。項目 ID はファイル名から取られ、Whisper の二重拡張子 interview_01.mp3.jsoninterview_01.mp3 のような ID にならないよう処理されます。

進める前に、何がどう読まれたか確認してください。

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

plain text やターン数ゼロと報告されるファイルが問題のファイルです。ほぼ確実に、.json 出力に紛れ込んだ .txt です。

変換の工程は省けるか

省けます。データファイルからディスク上の文字起こしを直接指すことができます。

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

Potato はそのファイルを読み、中身から形式を判定し、表示時に正規化します。文字起こしは差分が取れて再エクスポートもできるファイルのまま残り、データの塊に埋め込まれずに済みます。

設定はどうなるか

生成されるデータファイルはこの形です。

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."}
    ]
  }
}

対応する設定は potato transcripts --emit-config が出力してくれます。

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"
    target_field: conversation
    labels:
      - name: policy
      - name: personal

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

audio_dialogue 表示はターンを音声と同期した話者バブルとして描き、各ターンにはそのターンだけを再生するボタンが付きます。span_target: true にするとスパンがターンの境界をまたげるようになり、話者を割り当て直してもオフセットは安定したままです。

内容にラベルを付けるより文字起こし自体を点検してほしい場合は、speech_transcript がエラータグと修正欄付きのセグメントカードを提供します。発話の重なりや割り込みを扱うなら voice_interaction の二段タイムラインが使えます。どちらも同じファイルを読みます。

アノテーターはどう話者を割り当てるか

話者リストを定義すると、名前・色・左右の位置が安定します。

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 は、割り当てるべき未分離のターンがあるときに選択メニューを有効にします。元データに話者ラベルがある場合でも割り当て直しを許可したいときは true にします。空欄を埋めるのではなく、話者分離の誤りを直したい場合はこちらです。

割り当ては安定したターン ID に紐づいてアノテーションと一緒に保存されるので、再読み込みしても残ります。

アノテーションはどう取り出すか

JSON、JSONL、CSV へのエクスポートはこれまでどおりです。時間の対応関係を残したい場合は、階層アノテーションを ELAN EAF か 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/

どちらも往復できます。Potato は EAF と TextGrid を入力としても読むので、Potato でアノテーションし、ELANPraat で仕上げ、その結果をまた読み込めます。

関連文書