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
    turn_binding:
      field: conversation

turn_level: true 把问题挂在每个字幕块上,而不是整段视频上。当你手里只有字幕块时,这才是正确的单位。运行方式:

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

你也可以完全不用转换工具,让数据文件直接指向磁盘上的字幕文件。后面的流程完全相同,见如何标注 Whisper 转写文本

延伸阅读