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:
+23
-5
@@ -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)})
|
||||
Reference in New Issue
Block a user