发布于 约 5 分钟读完 · 1,746 字

本文目录 10 节
  1. 一句话
  2. 规范(数值参照 Netflix 简体中文与英文 Timed Text Style Guide)
  3. 如何使用
  4. 1. 对白时间:whisper 定句,能量包络校起止
  5. 2. 渲染:逐镜头 ASS + libass
  6. 3. 字体:可变字体先实例化成静态字重
  7. 4. 浅色画面:按实测亮度垫柔和压暗
  8. 5. 小坑
  9. 边界
  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)的字号和位置没有试过。

关联文档

类型/IP视觉主题/字幕