一、问题定义
随着国内短剧出海产业化规模持续扩张,多语种本地化字幕已经成为内容全球化分发的刚需环节。当前行业内针对短剧批量字幕处理仍普遍依赖人工排版、零散在线翻译工具手动导出,存在时间轴错乱、字幕样式丢失、翻译同质化严重、批量处理效率低下等痛点,面向短剧生产线的字幕翻译全链路工程化方案仍属于技术蓝海赛道。
短剧出海的字幕翻译,表面上是一个翻译问题,实际上是一个格式工程问题。
翻译本身可以交给大模型,但大模型不理解字幕文件的格式约束。它不知道每条字幕有时间窗口限制,不知道 ASS 格式里有样式标签需要保留,不知道翻译后的字符数超出时间轴会导致字幕遮挡画面。把原始字幕文件直接丢给翻译接口,拿回来的往往是一堆破坏了格式的文本,需要大量人工修复。
真正可以工程化落地的方案,需要解决三个具体问题:
解析: 把 ASS 或 SRT 文件拆解成结构化数据,把时间轴和文本分离,让翻译接口只处理纯文本,不接触格式信息。
翻译: 调用翻译接口时保持上下文,控制输出字符数,处理批量请求的速率限制。
回贴: 把翻译结果写回原始格式,保留时间轴和样式标签,处理字符数超限的情况。
本文按这三个问题的顺序,逐步拆解完整的工程实现。
二、ASS 与 SRT 格式解析
2.1 两种字幕文件格式的结构差异
SRT 是最简单的字幕格式,结构固定:序号、时间轴、文本,三段一组,空行分隔。时间格式精确到毫秒(00:00:12,400)。
1
00:00:04,933 --> 00:00:06,133
Bastard!
2
00:00:07,866 --> 00:00:08,933
You want more?
Yu Xiyue
ASS(Advanced SubStation Alpha)格式复杂得多,分为三个区块:文件头([Script Info])存储全局参数,样式定义([V4+ Styles])存储字体、颜色、位置等样式,事件列表([Events])存储实际字幕内容。
字幕文本在 [Events] 区块的 Dialogue 行里,每行包含十个字段,文本在最后一个字段,且可能包含行内样式标签,如 {\an8} 控制位置、{\b1} 控制加粗、{\c&H0000FF&} 控制颜色。时间格式精确到厘秒(0:00:12.40)。
两种格式的解析策略有一个共同原则:时间轴字段原样保留,不做任何转换,只在需要计算时间窗口时才转为数值。这样回写时可以直接把原始时间字符串写回去,不引入精度误差。
2.2 SRT 解析的关键细节
SRT 解析看起来简单,但有几个细节容易踩坑。
编码问题。 不同来源的 SRT 文件编码不统一,UTF-8 和 GBK 都很常见。建议用 chardet 库先做编码检测,再按检测结果解码,而不是固定用某种编码强行读取。
时间戳格式变体。 标准 SRT 用逗号分隔秒和毫秒(12,400),但部分工具输出的是点号(12.400)。正则匹配时需要同时兼容两种写法。
文本标签清洗。 部分 SRT 文件的文本里包含 HTML 标签(、、),这些标签在做翻译时会干扰模型输出,需要在解析阶段统一清洗,回写时再还原。
解析完成后,每个字幕块结构化为四个字段:原始开始时间字符串、原始结束时间字符串、毫秒数值(用于计算时间窗口)、纯文本内容。
2.3 ASS 解析的关键细节
ASS 解析比 SRT 复杂,核心难点是行内样式标签的处理。
{\an8} 这类标签嵌在文本字段里,如果直接把整个文本字段送去翻译,模型有时会把标签当成文本内容处理,输出乱掉。正确的做法是解析时把标签和文本分离:遍历文本字段,遇到 {...} 就把标签和它在文本中的位置记录下来,剩余部分作为纯文本送去翻译。回写时,按原始位置把标签插回译文。
另一个细节是 Format: 行的字段顺序。ASS 的 [Events] 区块有一行 Format: 定义了后续 Dialogue: 行各字段的顺序,不同工具生成的 ASS 文件字段顺序可能不同。解析时必须先读取 Format: 行,动态确定字段顺序,不能硬编码字段位置。
ASS 解析核心逻辑示意
for line in lines:
if line.startswith('Format:'):
format_fields = [f.strip() for f in line[7:].split(',')]
elif line.startswith('Dialogue:'):
parts = line[9:].split(',', len(format_fields) - 1)
field = dict(zip(format_fields, parts))
text = field.get('Text', '')
# 分离行内标签与纯文本
text_clean, inline_tags = extract_inline_tags(text)

三、翻译接口调用封装
3.1 批量翻译的两个工程问题
直接把每条字幕单独发给翻译接口,会遇到两个工程问题。
上下文丢失。 单条字幕往往是半句话,翻译接口看不到前后文,容易产生语义断裂。越南语的人称代词、泰语的敬语语气词,必须结合上下文才能正确选择。一句"别走",在情侣对话和朋友对话里,越南语的译法完全不同。
速率限制。 一部30分钟短剧有400到600条字幕,逐条发请求很快会触发 API 的速率限制。以常见的每分钟60次请求限制为例,600条字幕逐条发需要至少10分钟,且极容易触发限流报错。
解决方案是分组批量翻译:把字幕按固定窗口大小(通常10条)分组,每组作为一个请求发送,组内字幕用编号分隔,让模型在有上下文的情况下翻译,同时把请求次数从600次压缩到60次。
3.2 批量翻译的 Prompt 设计
Prompt 设计有两个关键点。
第一是强制编号格式。要求模型输出 [1] 译文 这样的带编号格式,方便解析时按编号对应回原始字幕,避免因为模型合并或拆分了某条字幕导致对应关系错乱。
第二是字符数约束。在 Prompt 里明确要求"每条译文不超过原文字符数的2倍",这是在翻译阶段就做的第一道 CPS 控制,能把后续需要压缩处理的字幕比例从40%降低到15%左右。
prompt = f"""将以下中文字幕翻译为{lang_name},保持编号格式,每条译文不超过原文字符数的2倍:
{numbered_texts}
只输出翻译结果,格式:[N] 译文"""
3.3 速率控制
批量请求仍然需要速率控制,尤其是在处理多语种并发翻译时。推荐用信号量(Semaphore)控制最大并发数,配合指数退避重试处理偶发的限流错误。max_concurrent=3 在大多数 API 额度配置下是安全值,处理600条字幕的五语种翻译总耗时约在15到20分钟之间。
四、时间轴保持与字符数控制
翻译完成后,最常见的问题是译文字符数超出原字幕时间窗口。中文信息密度高,翻译成泰语或越南语后字符数通常增加50%到150%。不做处理的话,字幕会在屏幕上显示不完整或遮挡画面。
4.1 CPS 阈值标准
CPS(Characters Per Second,每秒字符数)是字幕可读性的核心指标。各语种的推荐最大 CPS 阈值如下:
暂时无法在飞书文档外展示此内容
这组数值参考了 Netflix 字幕制作规范,是目前行业内使用最广泛的标准。
4.2 超限处理策略
超限时按三级优先级处理:
第一级:语义压缩。 调用模型对超限译文做压缩,要求在保留核心语义的前提下缩短字符数。适用于超出幅度在40%以内的情况,压缩成功率约85%。
第二级:字幕拆分。 当时间窗口足够长(通常超过2.5秒)时,把一条字幕拆成两条,各自重新计算 CPS。这个策略需要修改时间轴,把原始时间窗口平均分配给两条字幕。
第三级:标记人工处理。 当前两级策略均无法将 CPS 压缩到阈值以内时,该条字幕标记为 NEEDS_REVIEW,在输出文件中保留原文,由人工处理。本次测试中,触发第三级的字幕比例约为2%到5%,集中在泰语的长句场景。
五、多语种字幕文件批量生成与回贴
5.1 SRT 回写
SRT 回写相对简单:按原始块的序号、时间轴、译文文本,重新拼装成标准 SRT 格式输出。
需要注意两点:翻译失败的条目(空字符串)保留原文而不是输出空行;输出编码统一用 UTF-8,避免部分播放器的兼容性问题。
5.2 ASS 回写
ASS 回写的核心是行内标签还原。解析阶段记录了每个标签在原始文本中的字符位置,回写时需要把这些标签按比例映射到译文的对应位置插回去。
映射逻辑是:标签在原文中的位置占原文总长度的比例,乘以译文总长度,得到插入位置。这个方案在标签位于句首或句尾时效果很好,在标签位于句中时可能有1到3个字符的偏移,但对字幕显示效果影响极小。
回写时还需要注意输出编码用 utf-8-sig(带 BOM 的 UTF-8),这是 ASS 格式的惯例,部分播放器在没有 BOM 时会出现乱码。
5.3 批量处理流程
完整的批量处理流程如下:
输入目录(.srt / .ass 文件)
格式识别 → 解析为结构化数据(时间轴 + 纯文本)
分组批量翻译(每组10条,并发3路)
CPS 超限检测 → 语义压缩 / 拆分 / 标记人工
回写为原始格式(SRT / ASS)
输出目录(按语种分子目录)
生成超限报告(列出需要人工复核的字幕编号)
每个阶段的输入输出都是文件,阶段之间通过目录传递中间结果。任意阶段出问题可以单独重跑,不需要从头开始。
六、雅译 Agent 接入方案
上面的模块解决了格式解析和回贴的工程问题,但翻译质量本身取决于翻译引擎的能力。对于东南亚小语种的短剧翻译,通用大模型在文化词汇本土化、人称代词选择、宗教禁忌词规避等方面存在明显短板,需要接入垂直领域的翻译引擎来补足。
雅译是 AI 解说大师平台内置的短剧翻译 Agent,针对东南亚五语种做了垂直优化,包括短剧高频词汇映射表、上下文感知人称代词选择、印尼语宗教禁忌词过滤等模块。它以 Agent 形式对外提供服务,接入方式是通过 HTTP API 调用,替换上述翻译封装模块中的翻译接口部分。
接入点在第三节的批量翻译封装里,把调用通用大模型的部分替换为调用雅译 API:
import requests
def translate_batch_yiyi(texts: list, target_lang: str) -> list:
"""
调用雅译 Agent 翻译接口,替换通用大模型调用。
接口自动处理上下文感知、CPS 约束、文化词汇映射。
"""
resp = requests.post(
"https://api.yiyi.ai/v1/subtitle/translate",
json={"texts": texts, "target_lang": target_lang},
headers={"Authorization": "Bearer YOUR_API_KEY"},
timeout=30
)
resp.raise_for_status()
return resp.json()["translations"]
替换后,格式解析、时间轴保持、回贴这三个模块的逻辑完全不变,只有翻译调用层发生了变化。这也是把翻译接口封装成独立函数的工程价值所在——翻译引擎可以随时替换,不影响其他模块。
雅译 Agent 内部已经处理了上下文感知和 CPS 约束,因此接入后第四节的 CPS 超限检测触发率会从约15%降低到5%左右,人工复核的工作量进一步减少。
七、部署与常见问题排查
7.1 依赖安装
pip install chardet requests openai
7.2 常见问题
翻译输出编号格式不规范,解析失败。 模型有时会输出 1. 或 (1) 而不是 [1]。在解析函数的正则里兼容多种编号格式即可:[[((]?(\d+)[])).]?。
ASS 回写后字幕位置偏移。 通常是 Format: 行字段顺序和预期不一致导致的。调试时先打印解析出的字段映射,确认 Text 字段是否取到了正确内容。
泰语输出包含多余空格。 泰语没有词间空格,但部分模型会在输出时插入空格。在翻译后处理阶段加一步泰语专项清洗:对泰语输出做去空格处理,保留标点前后的空格即可。
多语种并发时内存占用过高。 五语种并发处理时,每个语种的翻译结果都在内存里,对于集数较多的短剧(80集以上)可能导致内存压力。建议改为串行处理各语种,或者在处理完一个语种后立即写文件释放内存。