译幕了然:视频 → 中文 ASS 字幕
完整的流水线:拿到原文 → 审查幻觉 → 逐句翻译 → 排版编译 → 校验交付。
核心难点不在翻译,在三个容易翻车的地方:
- ASR 会在没有人的地方硬造台词 —— 掏耳声被"听"成「晚安」,不剔除就穿帮
- 字幕时长与换行 —— 话没说完就消失、字冲出画面,用户必然打回
- 错字与碎句 —— 按字面硬译就是错的,必须先读懂再落笔
决策:先判断输入类型
用户给了字幕文件(.vtt / .srt / 带时间轴的 txt)?
├─ 是 → 跳过第 1 步,直接 parse_vtt.py
└─ 否,只有视频/音频 → transcribe.py 自己听译
两种情况都在第 2 步汇合。
输出路径永远是:与视频同目录、同文件名,只把扩展名换成 .ass。
这样 PotPlayer / VLC / mpv 打开视频会自动挂载,不用手动选字幕。
第 0 步:环境自检(每个会话第一次跑听译时)
python scripts/env_setup.py
四项都要 ✓。缺什么看 references/environment.md,里面有 pip 装不动时用 curl 抓 wheel 的绕法、
CUDA DLL 怎么补、模型怎么并行下载。
只用现成字幕做翻译的话,这一步可以跳过(那条链路只依赖标准库)。
第 1 步:听译(只有视频时)
# 先试听 60 秒,确认语种和音质,别一上来就跑全片
python scripts/transcribe.py "视频.mp4" --limit 60
# 确认没问题再跑全片
python scripts/transcribe.py "视频.mp4" --lang ja
语种怎么给
| 情况 | 做法 |
|---|---|
| 已知语种 | --lang ja / en / zh / ko …(优先这样) |
| 完全不知道 | 默认 auto,但必须看打印出来的 p 值 |
p < 0.6 |
片头多半是音乐/音效导致误判 → 显式指定后重跑 |
| 日式口音的英语 | 指定 --lang en,口音本身不影响识别 |
实测:某片前 30 秒是纯音乐,auto 判成 en 但 p=0.28;强制 --lang en 后
文本逐字相同、p=1.00。auto 不可信的时候要果断覆盖它。
三个默认值的由来
vad_filter=False:silero VAD 在不少环境里是坏的,会把轻声 ASMR 整段滤光
(实测 20 分钟人声只剩 1 句幻觉)。要开用--vad。--drop-nsp 0.95:先粗暴剔掉最高置信度的幻觉,更细的判断交给第 2 步。- 不给
initial_prompt:会把提示词原样吐回,还诱发循环幻觉。详见references/asr-pitfalls.md。
产出在 --out 目录(默认 <媒体目录>/_sub/):
| 文件 | 用途 |
|---|---|
segs.json |
下游吃这个:[{"i":0,"start":1.0,"end":4.5,"src":"原文"}] |
raw.srt |
人眼复核、或交给别的工具 |
raw_segments.json |
带 nsp / lp / 词级时间,第 2 步审查用 |
第 1b 步:解析现成字幕(有字幕文件时)
python scripts/parse_vtt.py "字幕.vtt" -o segs.json
已自动处理 YouTube 自动字幕的三个坑:<c> 卡拉 OK 标签、10ms 过渡行、滚动重复行。
SRT 的序号行也会剔除。
第 2 步:审查幻觉(别跳过)
python scripts/review.py <听译输出目录>
输出四块,按这个顺序用:
| 块 | 内容 | 怎么用 |
|---|---|---|
[A] 建议跳段 |
nsp≥0.9、或单字跨 30 秒、或撞固定句式 | 直接跳,跳了不丢内容 |
[B] 待判断 |
有可疑信号但可能真是轻声 | 回听,或 crosscheck.py 跑第二遍 |
[C] 空白区间 |
连续 ≥20 秒无分段 | 交付时用来证明"这不是漏译" |
[D] 全景 |
逐段列出(加 --full) |
通读时对照 |
判断依据(脚本已实现,但要知道原理):
nsp ≥ 0.95—— 模型自己都不信这儿有语音- 词级时间被拉长 —— 一个字"说"了 30 秒,物理上不可能。这条最可靠
- 撞固定句式 ——
おやすみなさい/Thank you./谢谢观看。
但要结合上下文:如果它周围全是人声,那可能是真的 - 簇推断 —— 幻觉成簇出现:邻段撞固定句式、且自己也"不算干净"(nsp≥0.25 或 lp≤-0.7)时一并标记。
nsp 0.02 的真句子不会被株连
拿不准就去核对
# 只看第二遍
python scripts/crosscheck.py "视频.mp4" --clips 36-70,180-235 --lang ja
# 顺带和第一遍自动比对(推荐)
python scripts/crosscheck.py "视频.mp4" --clips 36-70,180-235 --lang ja --compare _sub
参数刻意与第一遍不同(beam 10 vs 5)。给了 --compare 会按时间重叠找对应段、
用文本相似度标 ✓一致 / ~部分 / ✗不同。标 ✗ 的地方尤其要看——
实测那种地方常常是第一遍在音乐段上造了假句子("请点赞订阅"),第二遍才是真的。
⚠️ 日语别加 --prompt。实测会把提示词原样吐回,还会连着吐 12 遍同一句。
第 3 步:通读 + 翻译
先读完整个 segs.json 再动笔,确认:谁在说话、这期主题、哪些段是噪声。
不通读就逐句译,会出现"上一句在回答下一句的问题"。
写 lines.py:
LINES = [
(0, 0, "こんばんは。", "晚上好。"), # 一对一
(5, 7, "え、とあるお2人とコラボ…", "嗯,想和两位一起拍支合作视频。"), # 多段合并
(9, 9, [("前半はおりちゃんです。", "前半段是おりちゃん,来吧~"), # 一句内换人
("こんにちは、おはりです。", "你好,我是おはり。")]),
]
段号(元组第 1、2 项)是 segs.json 里的 i,不是时间。
跳过的段就是不出字幕,跳段时前后两条的段号不能交叉。
规则、各语种注意点、ASR 错字还原表 → references/translate-guide.md(翻译前必读)。
三条最容易忘的:
- 人名/频道名保留原文(
はるつき、JGA),不要音译 - 一句一人,两个人的话用列表拆成两条
- 单条不超过 36 汉字,长句交给编译器均分
第 4 步:编译
python scripts/build_ass.py segs.json lines.py "<与视频同名>.ass" \
--title "标题(简体中文)" --max-dur 12
自动完成:标点优先断行、超长条均分、时长跟到语音真正结束、条目之间不重叠。
--max-dur 按素材调(这是实战总结,不是可选):
| 素材 | 建议 |
|---|---|
| 话密的闲聊 | 20(默认) |
| 一般 ASMR | 12 |
| 「囁き少なめ」/大段纯音效 | 10 ~ 12 |
为什么:听译出的 end 等于下一段的 start,中间隔 40 秒纯音效时会被拉得极长,
不设上限就会出现"一条字幕停 40 秒不动"。
描述条自动铺满(1.2.0 起)
整条被全角括号包住的译文((轻声哼起摇篮曲)、(音乐轻轻响着,她在耳边低声念着什么))
会被当成舞台/气氛描述,按它覆盖的段区间铺满,不受 --max-dur 限制;
一条里放多个描述(list 形式)则均分成几段铺满。
这就替代了以前"编译完再手写 patch 脚本重设起止"的做法——音乐盖住耳语、纯环境音的长段落,
写一条描述就能盖住整段,不会出现"闪一下、后面几十秒全空"。
只有整条都被括号包住才算描述条。(搓出泡沫)像这样,轻轻涂上去。 这种带台词的仍按正常估时。
编译完看两行输出:
desc-filled: 4 biggest blank: 0.0s @0:03:24.98
desc-filled 是铺满的描述条数,biggest blank 是剩余最大空白——交付时这条数字直接当
"这段确实没人说话"的证据用。数字大得离谱说明 segs 有问题,而不是该去编台词。
第 5 步:校验
python scripts/verify_ass.py "<输出.ass>"
要看到 ✓ 无问题。有问题的处理见 references/timing-layout.md 的故障对照表。
第 6 步:交付
用 present_files 把 .ass 给用户(可附 segs.json 或 raw.srt 供核对),然后说明:
- 文件在哪、与视频同名所以会自动加载
- 条目数、平均停留时长
- 跳过了哪些段、为什么 —— 用
review.py的空白区间作证据
("1:13–7:44 确实没人说话,不是漏译") - 哪几句是推的 —— 主动交代存疑处,别等用户发现
参数总表
| 参数 | 默认 | 何时改 |
|---|---|---|
--width |
18 | 用户说"字出画面/左右被切" → 16 |
--max-lines |
2 | 一般不动,改这个不如调 width |
--font-size |
54 | 用户嫌字大/小 |
--max-dur |
20 | 见上表,按素材类型调 |
--tail |
0.45 | 结尾收得太急 → 0.6 |
--bilingual |
关 | 用户要双语(中文下加小字原文) |
--fill-desc |
开 | 括号描述条铺满整段;字面翻译的稿子怕拉太长 → --no-fill-desc |
--desc-max-dur |
120 | 单条描述最长铺多少秒,0 = 不限 |
默认偏向"停留偏长":用户对"字幕消失太快"的容忍度远低于"停留略久"。
不要做的事
- ❌ 不要依赖播放器自动折行 ——
WrapStyle: 2下不折行,一条长句直接冲出画面 - ❌ 不要把两个人的对话塞进同一条 —— 观众分不清谁说的
- ❌ 不要音译人名/团体名 ——
はるつき就写はるつき - ❌ 不要编内容 —— ASR 糊到无法还原的段落直接跳过,宁可留白
- ❌ 不要给日语加
initial_prompt做核对 —— 会吐回提示词、会循环 - ❌ 不要并行跑多支视频 —— 显存只够放一个模型,会 OOM
- ❌ 不要输出
.srt顶替.ass—— 样式和定位只有 ASS 能给 - ❌ 不要把"跳过幻觉段"说成"没识别出来" —— 要说清是素材里真没人说话
目录
SKILL.md 本文件:工作流与铁律
scripts/env_setup.py 环境自检(依赖/CUDA/模型)
scripts/transcribe.py 视频/音频 → segs.json + raw.srt(多语种听译)
scripts/parse_vtt.py 现成字幕 → segs.json
scripts/review.py 幻觉审查:跳段建议 / 待判断 / 空白区间
scripts/crosscheck.py 二遍交叉核对(指定片段或整片)
scripts/build_ass.py segs + lines → .ass(断行/时长/双语)
scripts/verify_ass.py 校验:重叠 / 过短 / 溢出 / 行数
references/translate-guide.md 翻译规则、多语种注意点、错字还原表
references/asr-pitfalls.md 幻觉三信号、prompt 反噬、VAD 失效、常见错法
references/timing-layout.md 时长公式、断行参数、故障对照
references/environment.md 安装、CUDA DLL、模型下载
一句话版
> 只有视频就 transcribe.py,有字幕就 parse_vtt.py,
> 然后 review.py 挑掉幻觉、写 lines.py 翻译、build_ass.py 编译、verify_ass.py 校验、present_files 交付。