对话视频字幕 是做什么的?
概述
字幕是一个独立图层:它承载每个口语词,绝不依赖于画面中已经绘制的内容。画面中事物的标签(部件名称、利弊、箭头)是不同的图层,使用另一种视觉语言(对象旁边的标注);永远不要因为“画面已经显示了”就从字幕中删掉一个词。
流程:words.json + forbid.json(字幕不得覆盖的框) + config.json + 配置文件
→ plan → manifest.json(规范数据) → burn(烘焙预览 = 视觉真实) / export srt|ass|vtt → validate。
格式角色(不要混淆):
| 格式 | 承载内容 | 不承载内容 |
|---|---|---|
manifest.json | 词语、时间、布局、placement、接受的覆盖、带原因的抬升、渲染检查:数据的真实来源 | 像素 |
.srt | 文本 + 时间 | 粗细、颜色、轮廓、当前词、位置。永远不要在SRT中承诺样式 |
.ass | 字体系列 + 粗体/\b700、转换为libass度量的大小、轮廓、背景框、每个时间片的活动词、位置 | 淡入淡出;需要字体:subtitles=x.ass:fontsdir=fonts |
.vtt | ::cue CSS(字体系列、粗细、通过text-shadow的轮廓、背景)+ 每个时间片的类 .active_<spk> / .em_<spk> / .dim / .u | 精确位置;页面必须使用@font-face加载字体 |
烘焙 preview.mp4 | 一切:唯一的视觉真实来源 | — |
何时使用
- 对话短剧、访谈、有旁白的讲解:竖屏或方屏,最多两行。
- 你有词时间码(或音频:通过环境密钥可选云端STT),并且知道面部/手部/关键道具的位置(框;对于3D场景,渲染纯色遮罩并取框)。
- 不适用于歌词视频(故意不支持弹跳、缩放文本)。
算法
分段(配置文件中的chunk):在停顿 > pause_s(0.35秒)、句子结束、说话人变化时强制断开;在逗号处软断开,前提是当前块已有2+个词;受max_words、像素宽度(安全区的max_width_frac)和max_dur_s(约1.2秒的口语阅读效果较好)限制。块不会以功能词(не、в、а、and、the……)结尾:它们会移到下一个块。单个词如果空间允许,则加入同一句子的相邻块。词持续时间限制为中位数的2.5倍,以免词悬在停顿中。时间:一个字幕单元在其第一个词开始前≤50毫秒出现,绝不切断前一个词,在其最后一个词结束后停留hold_s,至少min_show_s。
放置——placement: {mode: fixed_bottom_center, collision_lift: false}(对话默认)。
- 整个视频使用一条基线:
baseline = safe.bottom - baseline_margin_px(或显式设置)。每个单元在该基线上底部对齐(y + h = baseline)并在画面上居中(x + w/2 = W/2)。无任何移动:无抬升、无横向偏移、无按说话人的x偏移。行宽受限,确保居中行保持在安全区内。 - 在基线上穿过禁止框的行保持原样:该单元获得
overlap_accepted: [classes],QA表会画一个黄色框“重叠已接受(固定基线)”。此为信息,非失败。穿过平台UI仍然是错误(缩小字体或拆分词组)。 - 可选
collision_lift: true(模式baseline_lift):x位于说话人下方(align: speaker)或居中,左右回退;如果每个x都碰撞,则按整个lift_step_px步长抬升直到空闲。该单元获得lift = {px, reason: [基线处命中的类], t0, t1},清单中列出所有抬升在collision_lifts。无原因则无抬升。仅在需求明确要求不惜一切代价保持面部清晰时使用。 - 绝不删除词。
外观。 轮廓(outline_px)+ 柔和阴影 + 浅色背景,其透明度仅根据需要为每个单元提高,以保持最暗的亮色与背后第90百分位像素的对比度≥4.5:1(WCAG)。说话人通过活动词颜色区分,在混合/卡片模式下还有一条4像素的说话人色条;无气泡对话,无厚重名牌。平稳运动:仅透明度淡入淡出,无缩放弹出,无垂直滑动。
质量保证(scripts/validate.py,也可通过demo运行):
- schema:清单对照
templates/manifest.schema.json; - 行数 ≤ 2;
- 放置:在
fixed_bottom_center模式下,所有单元的y基线分布和x中心分布为0(容忍0.5像素),且无抬升;在baseline_lift模式下,每个抬升都在网格上,有原因,且该单元确实在基线处碰撞; - 每一帧与平台UI的覆盖为零;禁止框:
baseline_lift模式下为0,fixed_bottom_center模式下报告为overlap_accepted(信息); - 每个词精确出现一次,按顺序,在说话期间可见;
- 对比度 ≥ 4.5且无弹跳,两者均由
burn逐帧测量; - 字体可解析:文件存在,Pillow系列和样式,OS/2粗细,
fc-scan,且libass的fontselect选中声明的文件和粗细; - 像素:在烘焙帧上以及ASS的libass渲染中,活动颜色存在,字形底部在布局3像素内,主干宽度在声明粗细参考栅格的1像素内,轮廓在
outline_px的1像素内。
配置文件(profiles/*.json)
| 配置文件 | 特点 | 最适合 |
|---|---|---|
karaoke | 每次显示2–4个词,当前词为说话人颜色,其余变暗 | 最稳定;固定轨道,讲解 |
reveal | 词随说话淡入;布局预先固定;≤ 2行 | 平缓旁白 |
cards | 每个卡片一个想法(≤ 8词,2行),密集面板,关键词着色 | 慢速、引文;比说话提前阅读 |
kinetic | 一次一个标记,更大字号 | 仅限点睛句;上下文少 |
hybrid(默认) | ≤ 3词,词淡入,当前词下划线,强调颜色,说话人色条 | 对话 |
研发系统中的选项作为参数:节奏 = chunk.max_dur_s: 1.2 + timing.min_show_s: 0.83(混合模式下开启);强调 = emphasis: "energy"(每个说话人从--audio得到的词响度z分数),或带有keywords列表的"keyword"。
命令
python -m subkit demo examples/fixture # 5种配置 + hybrid_lift -> 预览、srt/ass/vtt、validation_report.{md,json}
python -m subkit plan --words W --forbid F --config C [--profile hybrid] [--audio A] -o manifest.json
python -m subkit burn manifest.json --forbid F --video in.mp4 [--audio A] -o preview.mp4 --qa qa.jpg
python -m subkit export manifest.json --format ass -o captions.ass # srt | ass | vtt
python scripts/check_clean.py # 提交前:无媒体/密钥/绝对路径
ELEVENLABS_API_KEY=... python -m subkit stt audio.wav -o words.json # 可选;或 --provider groq 配合 GROQ_API_KEY
陷阱
- libass ≠ Pillow字体大小。 libass按OS/2 winAscent+winDescent大小调整,并将基线置于行顶下方winAscent处;导出时进行转换,否则ASS文本会小约35%、更细且更低。
- ASS中的可变字体会回退到默认实例(通常是Regular)。请提供你使用的粗细的静态实例(此处为
Onest-Bold.ttf,700)并传递fontsdir。 ffmpeg -ss在-i之前会重置时间戳:字幕滤镜随后渲染t=0。烧录ASS时应在-i之后seek。\k/\kf卡拉OK单位是厘秒,且会永久着色每个口语词;对于“仅当前词”,应发射时间片(如导出所做)。- SRT无样式:如果交付物需要粗细或高亮,请提供ASS/VTT或烘焙视频。
- 浮动行(按说话人的x、滞后、“看起来更美”的抬升)在对话中会被视为抖动。保持默认固定模式;在
baseline_lift模式下,验证器会重新检查每个抬升是否确实碰撞。 - 对比度以亮色与背后最亮10%像素(而非平均值)判断:火焰、雪地和高亮区域会使平均值失效。
- 不要删除已在画面上绘制的词。将绘制的标签移到注释图层。
templates/safe_zones.json中的安全区是近似值;平台UI会变化,发布前请重新检查。
清单
- words.json覆盖整个语音(云端STT或强制对齐器);说话人已标记
- forbid.json:每个镜头的面部、手部、火焰/道具(+
align: speaker用的speakers框) - config:尺寸、fps、
safe_zone、说话人颜色(不同色调,两者在背景上均须≥4.5:1) -
plan→ manifest;阅读overlap_accepted(固定模式)或collision_lifts(抬升模式,每个有真实原因) -
burn→ 查看QA表(青色 = 基线,黄色 = 重叠已接受,橙色 = 有原因的抬升)以及真实帧 -
validate全部通过;交付烘焙视频(+ 如果播放器能渲染则加ASS/VTT;仅纯文本则用SRT)