Skip to content

YouTube の字幕をアノテーションする方法

yt-dlp で字幕を取得して Potato でアノテーションする手順。自動生成字幕にできること・できないこと、動画の扱い方、他人の作った素材を再配布する前に確認すべきこと。

yt-dlp で字幕を取得し、Potato をそのファイルに向けてください。まず知っておくべきこと。自動生成字幕には句読点も話者ラベルもなく、改行位置にも意味がないため、何を無理なくアノテーションできるかが制約されます。 Potato は yt-dlp が出力するすべての字幕形式を読めます。一覧は文字起こし形式にあります。

動画プラットフォームは、自然な話し言葉の大きくて扱いやすい供給源です。字幕はその話し言葉をテキストにし、テキストは軽く、持ち運びやすく、アノテーションもしやすい。問題は、すべての字幕が同じ種類のものではないことで、同じものとして扱うところで字幕ベースのプロジェクトはたいてい破綻します。

字幕はどう取得するか

yt-dlp は素材の有無にかかわらず字幕を取得できます。

bash
# Human-written subtitles, if the uploader provided any
yt-dlp --write-subs --sub-langs en --skip-download <URL>
 
# Auto-generated captions
yt-dlp --write-auto-subs --sub-langs en --sub-format vtt --skip-download <URL>

Potato は vttsrtjson3ttml、および srv1srv2srv3 の各種を読めるので、どの --sub-format を選んでも解析できます。何を残したいかで選んでください。json3 は単語単位の時間を保持し、WebVTTSubRip は人が読んで手で直すのが楽です。

人手字幕と自動生成字幕はどう違うか

テキストエディタで見ると似ていますが、アノテーション素材としてはまったく違う振る舞いをします。

人手字幕自動生成字幕
句読点ありなし、または信頼できない
文の切れ目意味がある恣意的
話者ときどき、<v Name> タグでまったくない
単語単位の時間なしあり(json3
逐語性読みやすさのために圧縮されがち実際の発話に近い

どちらが優れているという話ではありません。人手字幕は読みやすい代わりに読みやすさのために編集されており、言いよどみが削られ、繰り返しが圧縮され、ときには文ごと書き直されています。自動生成字幕は逐語に近く、人が実際にどう話すかを扱うならこちらが欲しいものですが、句読点のない流れとして届きます。

自動生成字幕には何をアノテーションできるか

自動生成字幕の上に文単位のアノテーション設計を載せてはいけません。 キューの境界は文が終わったところではなく、字幕ウィンドウが埋まったところに落ちます。自動生成字幕におけるキューは表示上の産物であって、言語単位ではありません。アノテーターに「文ごとに」評価させると、各自が黙って別々の文境界を選び、一致度の数字は測りたかったものではなくその差を測ることになります。

現実的な選択肢は二つです。

  • キューをそのままアノテーションする。 各キューに当てはまるラベルを付け、単位が恣意的でも一貫してはいることを受け入れます。話題、ある現象の有無、断片で判断できるものなら問題ありません。
  • 上流で切り直す。 句読点復元モデルか、句読点を打つ音声認識を通してから時間を振り直します。手間は増えますが、本物の単位が得られます。

人手字幕であれば、この問題はほぼ消えます。境界は読みながら人が置いたものなので、たいてい節の切れ目を尊重しています。

動画はどうするか

字幕は小さく、再配布も容易です。動画はたいていそうではありません。選択肢は三つあります。

bash
# Media hosted somewhere your annotators can reach
potato transcripts ./captions --media-url-prefix https://cdn.example.org/video -o data/talks.json
 
# Media downloaded locally as audio only
yt-dlp -f 'ba' -x --audio-format mp3 -o './audio/%(id)s.%(ext)s' <URL>
potato transcripts ./captions --media-dir ./audio -o data/talks.json

三つ目は素材をまったく使わないことです。素材のない文字起こしでも問題なくアノテーションできます。アノテーターは聴く代わりに読みます。それが方針なら指示書にそう書いてください。書かないと、プレーヤーがないのは不具合だと思われて報告されます。

Warning: 自分が作ったものでない素材を再配布する前に、権利を確認してください。自分の研究のために字幕や素材をダウンロードすることと、その素材をアノテーターの集団に配信することは別の問題であり、プラットフォームの利用規約、著作権、所属機関の規則がいずれも関わります。パイプラインを組んだ後ではなく、組む前に片づけてください。

タスクはどう組むか

変換ツールは、正規化済みの文字起こしを含むデータファイルを書き出します。

bash
potato transcripts ./captions --media-dir ./audio -o data/talks.json
json
{
  "id": "talk_01",
  "conversation": {
    "audio": "audio/talk_01.mp3",
    "turns": [
      {"turn_id": "t0", "speaker": null, "start": 0.0, "end": 4.2,
       "text": "so the thing about caption windows is"}
    ]
  }
}

自動生成字幕から得られるのは speaker: null です。こうしたターンは選択メニュー付きの Unassigned として表示されるので、アノテーターは聴きながら話者を割り当てられます。

灰色のハッチング背景を持つ三つの自動生成字幕のターン。ドロップダウン矢印付きで Unassigned と表示され、各ターンに質問が付いている。自動生成字幕には話者情報がないため、アノテーターが選ぶまで各キューは Unassigned のまま

yaml
annotation_task_name: "Talk Annotation"
task_dir: .
data_files:
  - data/talks.json
 
item_properties:
  id_key: id
  text_key: conversation
 
instance_display:
  fields:
    - key: conversation
      type: audio_dialogue
      label: "Captions"
      span_target: true
      display_options:
        show_timestamps: true
        allow_speaker_assignment: auto
 
annotation_schemes:
  - annotation_type: radio
    name: cue_topic
    description: "What is this caption window about?"
    labels: [setup, argument, example, aside]
    turn_level: true
    turn_binding:
      field: conversation

turn_level: true は質問を動画全体ではなく各キューに紐づけます。キューしか手元にないときは、これが正しい単位です。実行するには次のようにします。

bash
python potato/flask_server.py start config.yaml -p 8000

変換ツールをまったく使わず、データファイルからディスク上の字幕ファイルを直接指すこともできます。残りの流れは同じなので、Whisper の文字起こしをアノテーションする方法を参照してください。

関連文書