feat: 字幕质量提升——长时寒暄幻觉清洗 + 专名/成人隐语不直译

两部分同属字幕质量优化,共用 llm.py 翻译链路:

1) 长时寒暄幻觉词清洗(nodes/subtitle_cleanup.py)
   对展示时长超过阈值(默认 15s,实测 30s 幻觉占位 vs 2s 真实词的分界)
   且文本含收尾/开场寒暄(晚安、感谢观看等)的字幕,文本替换为 '-',
   由后续过滤流程移除;短时寒暄(如剧情中真实互道晚安)保留不误删。
    写文件前调用 。

2) 专名/隐语不直译(nodes/proper_nouns.py)
   片假名专名(人名/品牌/角色)与成人语境隐语是 LLM 误译重灾区:
   - ジンゴ 被误译成'芒果'、カンタくん 被保留日文而非音译;
   - マンゴー/バナナ/リンゴ/金玉/おまんこ/ちんちん 等在色情语境中
     是生殖器官代称,字面直译严重错译。
   整理三张规则表(专名/拟声词/成人隐语,用户提供隐语表),
    检测原文命中后动态注入翻译提示词,
   让 LLM 按正确语义处理。文档见 docs/proper_nouns.md。

测试(红→绿):
- tests/test_hallucination_mask.py:长时掩码/短时保留/非寒暄保留/阈值边界
- tests/test_proper_nouns.py:专名命中/拟声词/成人隐语/真实数据集成
- 真实 LLM 集成测试(完整 1440 行翻译 + 清洗 + 专名注入)通过
This commit is contained in:
2026-09-05 22:32:40 +08:00
parent fcdcfe020b
commit a032855210
6 changed files with 508 additions and 5 deletions
+23 -5
View File
@@ -28,7 +28,8 @@ import urllib.request
from pathlib import Path
from wov_sdk.models import InvokeRequest, InvokeResponse
from nodes.subtitle_cleanup import clean_srt_text
from nodes.proper_nouns import build_proper_noun_rule
# 单次 LLM 请求携带的字幕行数;过大会超出模型上下文,过小则请求次数过多。
CHUNK_SIZE = 20
@@ -144,18 +145,28 @@ def _translate_batch(
system_prompt: str,
request_timeout: float,
) -> list[str]:
"""翻译单个批次:行数不一致时多行合并、少行重试,返回与 chunk 等长译文。"""
"""翻译单个批次:行数不一致时多行合并、少行重试,返回与 chunk 等长译文。
每批调用前根据本批原文命中情况动态拼接专名/隐语规则(build_proper_noun_rule),
注入到系统提示词,让 LLM 正确处理片假名专名与成人语境隐语。"""
# 本批命中的专名/隐语规则(无命中返回 None)。
rule = build_proper_noun_rule(chunk)
batch_system = system_prompt
if rule:
batch_system = system_prompt + "\n\n" + rule
attempt = 0
while True:
content = _call_llm(
api_base,
api_key,
model,
system_prompt,
batch_system,
"\n".join(chunk),
request_timeout,
)
batch = [line.strip() for line in content.splitlines() if line.strip()]
# 保留所有行:先 rstrip 尾随换行避免多出末尾空行,再 splitlines 保留
# 内容中的空串行(空行可能是合法的空字幕,过滤掉会误判行数)。
batch = content.rstrip("\n").splitlines()
if len(batch) == len(chunk):
return batch
if len(batch) > len(chunk):
@@ -193,8 +204,15 @@ def invoke(request: InvokeRequest) -> InvokeResponse:
for index, text_index in enumerate(text_indices):
lines[text_index] = translated_lines[index]
# 长时寒暄幻觉词清洗:对展示时长超过阈值且含收尾/开场寒暄(晚安、感谢观看
# 等)的条目,文本替换为 '-'(由后续过滤流程移除),避免幻觉占位污染正片;
# 短时(≤阈值)如剧情中真实互道'晚安'则保留,不误删。见
# nodes/subtitle_cleanup.py。
srt_body = "\n".join(lines) + "\n"
srt_body = clean_srt_text(srt_body)
output_dir = Path(request.output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
output_path = output_dir / "cn.srt"
output_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
output_path.write_text(srt_body, encoding="utf-8")
return InvokeResponse(status="completed", outputs={"cn_srt_uri": str(output_path)})