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

第三種是完全不提供素材。沒有素材的轉寫照樣能標註,標註員是讀而不是聽。如果這就是你的方案,請在說明裡寫清楚,否則標註員會以為沒有播放器是個 bug 並當作 bug 報上來。

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 把問題掛在每個字幕塊上,而不是整段影片上。當你手裡只有字幕塊時,這才是正確的單位。執行方式:

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

你也可以完全不用轉換工具,讓資料檔案直接指向磁碟上的字幕檔案。後面的流程完全相同,見如何標註 Whisper 轉寫文本

延伸閱讀