本文目录 10 节
影视级双语字幕:中上英下与 libass 逐镜头烧录
入档:2026-09-29 来源:77《右下角的池塘》成片字幕重做,
{aigc-creative-archive}/77_工位池塘_微电影宣传片/06_剪辑/字幕.py验证状态:✅首验(作者用这一版投稿 B站);只在一支 1:48 的 16:9 片上做过
一句话
作者看了第一版说明字:「不要用remotion的字幕,很廉价,采用影视级双语字幕标准」。
“廉价”来自三样东西:半透明圆角底框、淡入淡出、对白不配字幕。影视字幕的做法正好相反:
- 没有框:白字、细描边、柔阴影。
- 硬切入出:字幕直接出现、直接消失。
- 时间跟语音走:入出点按语音定,不跨剪切点。
- 有数值规范:行长、读速、最短时长都有硬指标。
浅色画面上白字立不住,也不加框,而是在字幕区垫一道全宽的柔和渐变压暗,只在实测够亮的镜头上垫。
规范(数值参照 Netflix 简体中文与英文 Timed Text Style Guide)
| 项 | 规则 |
|---|---|
| 版式 | 中文在上、英文在下,每种语言一行,底部居中;画面底部有关键内容时整条上移到顶部 |
| 字体 | 思源黑体(Noto Sans SC):中文 Medium 500、英文 Regular 400;1080p 下中文 58 号、英文 36 号;英文用浅灰白,和中文拉开层级 |
| 描边 | 白字 + 2px 黑描边(约 70% 不透明,模糊 1.2)+ 1.2px 柔阴影;不加底框 |
| 行长 | 中文 ≤ 16 字宽(半角字符算半个),英文 ≤ 42 字符 |
| 读速 | 中文 ≤ 9 字/秒,英文 ≤ 20 字符/秒 |
| 时长 | 每条至少 20 帧(24fps 下 5/6 秒) |
| 入点 | 起音那一帧 |
| 出点 | 话音结束后停 0.5 秒;和下一条间隔不到 12 帧就接上(留 2 帧);离剪切点不到 12 帧就贴到剪切点前 2 帧;不跨剪切点 |
| 标点 | 中文句中的逗号、句号换成空格(两个半角),句末不留;问号、叹号、省略号、引号、书名号照留。英文按正常英文标点 |
| 入出 | 硬切,不做淡入淡出 |
| 说明字 | 非对白的说明文字按同一版式;时间从镜头首帧到剪切点前 2 帧 |
如何使用
1. 对白时间:whisper 定句,能量包络校起止
- faster-whisper 开逐字时间戳,按句切分。
initial_prompt里放剧本台词,能提高专名和断句的准确度。 - **whisper 的词起点会被拉长。**例如“你摸鱼它就空军”,它标 20.84 秒起,实际起音在 21.56 秒;“这个”被标成 4.70–6.28。
- 用能量包络校准:音频滤到 120–4000Hz,算 10ms 帧的 RMS。以底噪 10% 分位 +14dB 为阈值,间隔小于 120ms 的合并,短于 60ms 的丢掉。得到每段人声的真实起止,再和 whisper 的句子对齐。
- 句尾幻听:它在最后一句后面多听出一个“好。”,那里没有任何人声能量,不上字幕。
2. 渲染:逐镜头 ASS + libass
- 每个镜头写一份自己的 ASS,时间相对镜头首帧。在出这个镜头的 ffmpeg 滤镜链末尾挂
ass=subs/<镜头>.ass:fontsdir=fonts。- 字幕只活在本镜头里,天然不跨剪切点。
- 和画面同一遍编码,不会为了烧字幕再压一遍。
- 中英文各一个事件,用
\an2\pos(x,y)钉死位置。不钉的话,libass 的防重叠会把行挤走。 - 全片另存一份双语 SRT 和 ASS 做存档。字幕已经烧进画面,B站就不要再上传 CC 字幕,否则画面上会叠两层。
3. 字体:可变字体先实例化成静态字重
- 坑:Windows 自带的
NotoSansSC-VF.ttf是可变字体,字重轴默认值是 100(Thin)。libass 直接用它,会一律渲染成最细的 Thin,\b500不起作用。第一次试渲染,字就发虚。 - 解法:用 fontTools 实例化出 400、500、700 三个静态字重,并改成独立的字族名(不然会和系统的可变字体重名,libass 选不准)。
from fontTools.ttLib import TTFont
from fontTools.varLib import instancer
f = instancer.instantiateVariableFont(TTFont('C:/Windows/Fonts/NotoSansSC-VF.ttf'), {'wght': 500})
# 删掉 nameID 1/2/3/4/6/16/17/21/22,再写入新字族名,例如 "DeskPond Sub 500"
f.save('NotoSansSC-500.ttf')
- 片中标题、片尾卡用 PIL 排版的,也换成同一套静态字重,全片字体统一。
4. 浅色画面:按实测亮度垫柔和压暗
- 白字压在米色弹窗、浅天空上,加粗描边、柔光晕都试过,只会让字发灰发脏。白底上的白字,描边救不回来。
- 做法:
- 出每个镜头前,用同一条滤镜链量字幕区横带的逐帧平均亮度(
crop+signalstats的 YAVG)。 - 只统计这条字幕在屏的那几帧,平均值超过 110(0–255)才垫。
- 垫的是一个全宽矩形加大模糊(
\p1绘图 +\blur90,60% 不透明黑,从 y=860 画到画面外),上缘是柔和的渐变,看不出是一个框。
- 出每个镜头前,用同一条滤镜链量字幕区横带的逐帧平均亮度(
- 按整段平均会误判:G08 前 2.45 秒顶部是米色结算弹窗,字幕出现时已经是深色水面。按整段平均算是 141(误判为要垫),按字幕窗口算是 103(不垫)。
- 本片结果:G03、G04、G05、G10 垫了(亮度 133–194);暗调 AI 镜头(约 53)、水面(约 87–103)都不垫。
5. 小坑
- 全角空格当断句,间隔太宽:一个全角空格加上两侧字形的留白,看起来像 1.5 个字,读着像断句。换成两个半角空格,约半个字宽。
- 英文省略号连成四个点:“1 a.m.…” 会显示成 “a.m…”。改写句子,别让缩写的句点紧挨省略号。
- 字幕窗口短时先砍字:G08 的说明字只能在弹窗收起后出现,窗口只有 1.47 秒。中文“会有访客自己游进来”改成“访客会自己游进来”,英文压到 29 个字符,读速才合规。改之前要能说出依据:哪条读速超了。
- 片中的设计标题不算字幕:像素天空上的墨字标题保留原来的标点和字体设计,只在下面加同色的英文小字,并确认英文不压画面元素(G02 整体上移了 27px)。
边界
- 只做过一支片。英文由助手翻译,作者没有逐句审(“空军”译成 “catches nothing”,六角恐龙译成 axolotl)。
- 读速上限按成人节目取。面向 B站中文观众,英文是次要层,个别镜头按中文优先取舍。
- 压暗阈值 110 是本片的经验值,别的画面风格要重新看。
- 竖屏(9:16)的字号和位置没有试过。
关联文档
- 来源复盘:2026-09-29_右下角的池塘_游戏宣传微电影_实录混剪到双语字幕发布_复盘_v1
- 被取代的做法:2026-06-09_Remotion全片音频字幕BGM复盘_v1(Remotion 功能讲解片的字幕,放在微电影里显得廉价)
- ASR 定时的上游:TTS旁白也能蒙眼质检_ASR拼音级评分_v1
- “白底上的白”同族:白底上的白事件等于没发生_v1
- 画面里的字幕样式范式(对照):伪央视字幕条视觉范式_v1
- 机器验参数、人眼验内容:自动化产出双重验收_机器验参数人眼验内容_v1