书译
书译

如何翻译 ASS/SSA 字幕而不破坏样式、位置或卡拉 OK 效果

只翻译 .ass 文件中可见的对话,然后检查时间轴、标签、卡拉 OK 和字体。附示例文件、免费检查工具和 9 次本地 LLM 测试。

BookTranslator

BookTranslator Team

19 min read

ASS 或 SSA 字幕文件就像一个小程序:包含文件头、样式表,以及一系列带时间轴的事件,每个事件的最后一个字段混合了对话和格式代码。翻译时,只需修改最后一个字段中可读的文字,其余部分保持逐字节不变,然后检查三件翻译工具无法替你处理的事情:字体、间距代码和卡拉 OK 行。

我们用本地模型将一份包含 14 个事件的样本翻译成简体中文九次,使用了三种不同的提示词,并用自己编写的脚本检查了每个结果。时间轴、位置、颜色、注释和绘图在九次测试中全部保留。出问题的是文件中那些默认使用英文的部分:九次测试中,样式字体都没有中文字符,因此文件将依赖播放器替换字体;英文行中的硬空格在中文里变成了可见的空隙。卡拉 OK 行在每次运行中都出现了时间轴指向错误单词的问题,其中四次运行中某些音节完全没有高亮。

快速回答:翻译什么,保留什么

文件部分样本中的示例是否翻译?
[Script Info] 文件头PlayResX: 1920, WrapStyle: 0否。位置是根据这些值计算的。
[V4+ Styles]Style: Default,Trebuchet MS,64,...否,但字体名称可能需要有意更改
事件字段 1–90,0:00:01.00,0:00:03.20,Default,Jun,0,0,0,否。这些是图层、时间轴、样式、说话人、边距和效果。
覆盖块{\i1}, {\an8\pos(960,120)}, {\k25}否
绘图{\p1}m 0 0 l 200 0 200 60 0 60{\p0}否。这是矢量图形,不是文本。
Comment: 事件Comment: ...,Earlier draft: Try the stacks.可选。注释永远不会显示在屏幕上。
花括号注释{TL note: "stacks" means the closed shelves}可选。播放器会忽略它们。
可见对话Did you find the second volume?是
行代码\N, \n, \h保留 \N。对于 \n 和 \h,请根据具体情况分别处理。

本指南的剩余部分将解释每一行为什么重要,以及如何检查结果。

ASS 行的构成方式

[Events] 部分中的每一行都遵循其上方声明的格式:

Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:03.40,0:00:06.10,Default,Mara,0,0,0,,{\i1}I thought it was{\i0} sold out.

只有第十个字段 Text 包含观众能读到的内容。libass 项目的 ASS 文件格式指南 指出,这最后一个字段“允许包含逗号”,这就是为什么粗心地在每个逗号处分割会破坏像 Well, if it's there, then, we go now. 这样的行。安全的工具只在前九个逗号处分割。

在 Text 内部,花括号中的任何内容都是覆盖块。Aegisub 的 覆盖标签参考 定义了您最常遇到的代码:

  • \N 在所有换行模式下强制换行。\n 仅在换行模式 2 下换行,否则为空格。\h 是不间断空格。
  • \an8 使用数字键盘布局设置对齐方式,因此 8 表示顶部居中。\pos(960,120) 将行放置在脚本坐标处。
  • \k25 是卡拉 OK 音节;持续时间以厘秒为单位,因此 25 表示四分之一秒。
  • 在 \p1 之后,文本“将被解释为绘图指令”,因此翻译者若“翻译” m 0 0 l 200 0 会破坏形状。
  • 花括号内无法识别的文本“会被静默忽略”,这就是为什么粉丝字幕文件经常在花括号中携带翻译注释。

以 Comment: 开头的行使用相同的字段,但根据 libass 指南,“屏幕上不会显示任何内容”。

我们测试了什么

我们编写了 sample-source.ass,这是一个以书店为背景的 14 事件场景,每个上述功能都有一个示例:斜体、硬换行、对话中的逗号、花括号注释、注释行、定位标志、矢量框、淡入淡出、硬空格、使用 \r 的颜色变化、软换行、滚动 Banner 效果以及六音节卡拉 OK 行。

然后我们让一个本地模型以三种方式将其翻译成简体中文:

  1. 整个文件。 粘贴整个 .ass 文件并要求返回翻译后的文件。
  2. 仅文本。 仅将每个对话行的 Text 字段作为 JSON 发送,标签内联,并指示保留每个 {...} 块以及每个 \N、\n 和 \h。
  3. 受保护。 相同,但首先将每个花括号块和行代码替换为编号占位符,然后再将原始内容放回。

在第二和第三种条件下,脚本从不将矢量绘图或注释行发送给模型,因此这些行只可能在整文件条件下被损坏。

模型是 Qwen 3.6 27B (Q4_K_M),运行在 LM Studio 0.4.8 中,硬件为 Apple M4 Max,64 GB 内存,关闭思考功能,温度设为 0.3。我们对每种条件运行了三次,总共九次运行。第三次运行的第一次尝试因本地服务器断开连接而结束,因此我们重新启动并再次运行了第三轮。脚本是 run_llm_ass_test.py,所有九次输出均未经编辑地随附发布;README 列出了每个文件。如果您从未设置过本地模型,我们的 本地 LLM 翻译指南 涵盖了相关设置。

这是一个针对一个模型、一个语言对和一个样本的小型测试。它展示了哪些问题会发生以及如何捕捉它们。它不对工具或提示进行排名。

结果:机制幸存;英语假设未能幸存

我们使用 check_ass_translation.py 检查了每个输出。完整输出在 check-results.txt 中,results.csv 每次运行一行,包括脚本无法看到的问题。

检查整个文件仅文本受保护
时间轴、样式和字段 1–9 保持不变3/33/33/3
覆盖标签保持不变3/32/33/3
绘图和注释行未改动3/33/33/3
样式字体能够绘制译文0/30/30/3
\h 和 \n 在中文文本中保留为间隙3/33/33/3
所有卡拉OK标签均保留,每个都带有文本3 次中 3 次3 次中 0 次3 次中 1 次

有两个结果让我们感到意外。粘贴整个文件在三次运行中都保持了结构完整,但其中两次也翻译了头部的 Title 行。而听起来更安全的纯文本提示,却产生了唯一一次标签损坏。在一次运行中,它针对一行包含六个卡拉 OK 标签的文本返回了 {\k25}翻{\k30}页,{\k45}开{\k60}灯。最后两个标签和 1.20 秒的高亮时间消失了,因此高亮会在该行结束前很久就结束。同一次运行还将 Room\h12 is upstairs. 变成了 12\h12号房间在楼上。,房间号出现了两次。

所以有用的教训不是“使用提示 X”。而是你所担心的结构性损坏很容易检测,而全部九次运行中实际出现的损坏,集中在 ASS 翻译工具页面没有讨论的三个地方。

1. 字体无法绘制新语言

示例的样式使用 Trebuchet MS 作为对话字体,Georgia 作为标牌字体,这对英文发行版来说很正常。这两种字体都不包含中文字符。当我们将检查器的字形测试指向这两个字体文件时,每次运行中所有 12 个翻译文本事件都失败了:

ERROR   event 1: font 'Trebuchet MS' has no glyph for 9 characters, e.g. 了二你到卷吗找第?
ERROR   event 7: font 'Georgia' has no glyph for 10 characters, e.g. 书人仅作员工库架闭限

播放器可能会替换为其他已安装的字体,因此字幕可能仍会显示,但发行版不再控制其外观,而系统缺少合适字体的观众可能会看到空框。修复方法是刻意编辑每个样式的 Fontname 字段。当我们将一个输出中的所有三个样式更改为 Hiragino Sans GB(macOS 自带的中文字体),并对该字体运行字形测试时,字形错误降为零。

选择观众实际拥有的字体,或将其与视频一起打包。libass 指南指出,发行版应附带其使用的所有字体,最好附加到 MKV 容器中。由于目标语言字体是新的,现在附加它是翻译工作的一部分。

2. 间距代码带有英语习惯

Room\h12 将“Room”和“12”粘在一起,使它们永远不会跨行拆分。在中文中,词语之间没有空格分隔,但每次运行都在某处保留了 \h:五次运行将其放在 12号房间 和 在楼上 之间,三次放在数字之前,一次放在数字的两个副本之间。无论哪种方式,观众都会看到一个多余的空格。Wait\nhere. 也有同样的问题。在 WrapStyle: 0 下,\n 渲染为空格,因此 在\n这里等 在一个三词句子中间显示了一个间隙。

这里的结构没问题;语言不对。对于词语之间没有空格的目标语言,请从翻译文本中删除 \h 和 \n。仅在需要强制换行的地方保留 \N,并将其放在目标句子中的自然位置。检查器将更改的 \h 和 \n 计数报告为警告而非错误,原因就在于此,并且当其中一个在中文、日文或韩文文本中残留时,它会发出警告。

3. 卡拉 OK 时间属于原始词语

卡拉 OK 行 {\k25}Turn {\k30}the {\k45}page, {\k60}turn {\k40}the {\k80}light 将每个英语音节与歌曲同步。翻译无法有意义地保持这种时间,因为中文词语不会落在相同的节拍上。我们的运行显示了它出错的不同方式:

  • 在四次运行中,所有六个标签都被保留,每个标签都放在与之前不同的词上。其中一个以 {\k40}打{\k80}开(“open”)结尾,并完全从行中删除了“light”。
  • 在四次运行中,一个音节没有高亮任何内容,例如 打开{\k40}{\k80}灯(空音节上有 0.40 秒的高亮)。在其中两次中,一个 0.45 秒的音节也只高亮了一个逗号。
  • 在一次运行中,两个标签消失了,如上所述。

检查器会标记就地翻译的卡拉 OK 行以及任何没有文本的音节。更好的修复方法是编辑性的:不要就地翻译卡拉 OK。保留原始定时行以供跟唱,并将翻译添加为单独的事件:复制卡拉 OK 事件,为其指定自己的样式(例如,更小并放置在底部),删除 \k 标签,然后翻译文本。仅当你准备自己根据音频计时时,才逐音节重新计时。

经得起考验的工作流程

  1. 首先让源文件可读。 如果文件显示乱码字符,请先修复编码。我们的编码指南 使用 SRT 示例,但相同的字节级步骤适用于 .ass 文件,并保持原始字节安全。检查器仅读取 UTF-8 和 UTF-16。

  2. 确定范围。 列出样式并决定哪些是对话,哪些是屏幕标牌,哪些是卡拉 OK。标牌通常需要翻译;卡拉 OK 通常需要单独一行;注释和花括号注释由你决定。

  3. 仅翻译 Text 字段。 使用解析 ASS 的工具,而不是将其视为纯文本,或者像我们的第三个条件那样自己保护标签。将源文件保留在输出旁边。

  4. 运行结构检查。

    python3 check_ass_translation.py source.ass translated.ass
    

    它会比较事件计数、事件类型、字段 1–9、样式值、覆盖块、绘图、注释行、\N、卡拉 OK 音节和持续时间,以及返回为空或未更改的行。它不评判翻译质量。

  5. 有意修正字体。 在每个现在承载目标语言文本的样式中更改 Fontname,然后针对你将发布的字体文件运行字形测试:

    pip install fonttools
    python3 check_ass_translation.py source.ass translated.ass --font "Noto Sans SC=/path/to/NotoSansSC-Regular.otf"
    

    更改的字体名称会显示为警告,因此你可以确认每处更改都是有意为之。

  6. 清理间距代码和卡拉OK,如上所述。

  7. 结合视频观看。 在 Aegisub 或你的播放器中打开文件。检查标志是否仍适合其背后绘制的形状,因为 \p 框保持其英文尺寸,而翻译后的标志可能长得多或短得多。检查行是否因屏幕时间过长。这两个问题在文件本身中都不可见。

BookTranslator 的适用之处

BookTranslator 的字幕翻译器 接受 SRT、VTT、ASS 和 SSA 文件。根据产品页面,它会保留时间戳和字幕顺序,并将支持的 ASS 和 SSA 样式附加到每个字幕条目上,因此你无需自行提取和重新插入文本。它会结合上下文翻译对话,这有助于处理整集节目中的名称和反复出现的笑话。

我们没有为本文通过 BookTranslator 运行此示例。无论你使用什么工具,都要对结果运行步骤 4 到 7。保留英文字体的样式仍然是无法绘制中文的样式,而就地翻译的卡拉OK仍然需要编辑决策。有关字幕工作流程的其余部分,请参阅我们的 AI 字幕翻译指南、我们的 字幕翻译软件比较,以及,如果你的翻译要上传到 YouTube,请参阅 YouTube 字幕工作流程。

何时改为转换为 SRT

在翻译前将 ASS 转换为 SRT 会移除样式、位置、绘图和卡拉OK,这也消除了上述大部分问题。如果你的目标平台只显示纯字幕,或者样式对发布从来都不重要,那么这是一个合理的取舍。对于标志和卡拉OK是作品一部分的动画和音乐发布来说,这是错误的取舍。根据文件将在何处展示来决定,而不是根据哪种格式更容易翻译。

获取测试文件

本次测试中的所有内容均可免费重复使用:

相关文章