Files
vrsub/tests/test_hallucination_mask.py
T
cat-shark a032855210 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 行翻译 + 清洗 + 专名注入)通过
2026-09-05 22:32:40 +08:00

87 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""长时寒暄幻觉词清洗测试(先红后绿)。
背景(实测 run 20260905115050):修复时间对齐后,字幕仍残留四类问题,
其中"寒暄/收尾幻觉词"最具确定性、可规则化:
- 产物里 '晚安 / 感谢观看 / 感谢收看 / 感谢您的观看' 等固定套话出现 36 次;
- 其中 **33 条展示时长 = 30s(整块占满)**,明显是 ASR/LLM 对无内容段
的音量幻觉占位,与视频内容毫无关系;
- 仅 2 条时长 ~2s(如 720.00-722.00 '晚安')可能是剧情里真的说了"晚安",
属于真实内容,不应误删。
方案(用户确认):日文转译完成后,对**展示时长过长**(≥阈值)且文本匹配
寒暄词表的条目,把文本替换为 '-' 占位,由后续处理(SRT/过滤流程)移除。
这样既清掉幻觉占位,又用"时长阈值"保住可能为真实对话的短时寒暄词。
阈值从本次真实运行实测数据判定:30s 幻觉占位 vs 2s 真实词,分界明显,
本测试选 threshold=15s>15s 才视为幻觉;≤15s 保留)。
"""
from __future__ import annotations
import pytest
# 清洗逻辑来自生产模块 nodes/subtitle_cleanup.py(纯函数),测试只 import。
from nodes.subtitle_cleanup import (
DEFAULT_THRESHOLD_SECONDS,
HALLUCINATION_TOKENS,
mask_hallucination_text,
)
def _mk(start: float, end: float, text: str) -> dict:
"""构造一个字幕条目(测试辅助)。"""
return {"start": start, "end": end, "text": text}
def test_long_hallucination_masked() -> None:
"""30s 的'晚安/感谢观看'(幻觉占位)必须被替换为 '-'。"""
entries = [
_mk(60.0, 90.0, "晚安"),
_mk(90.0, 120.0, "感谢您的观看"),
_mk(1260.0, 1289.98, "感谢您的观看"),
]
out = mask_hallucination_text(entries)
assert all(e["text"] == "-" for e in out)
def test_short_hallucination_preserved() -> None:
"""2s 的'晚安'(可能为剧情真实对话)必须保留,不误删。"""
entries = [
_mk(720.0, 722.0, "晚安"),
_mk(238.0, 240.0, "非常感谢您的观看。"),
]
out = mask_hallucination_text(entries)
assert out[0]["text"] == "晚安"
assert out[1]["text"] == "非常感谢您的观看。"
def test_non_hallucination_always_preserved() -> None:
"""普通内容(即使很长)绝不能被当成寒暄幻觉处理。"""
entries = [
_mk(0.0, 30.0, "今天我将为您提供精神调适服务"),
_mk(10.0, 40.0, "请尽量放松,无论多少次都能感到舒适愉悦"),
]
out = mask_hallucination_text(entries)
assert out[0]["text"] == "今天我将为您提供精神调适服务"
assert out[1]["text"] == "请尽量放松,无论多少次都能感到舒适愉悦"
def test_threshold_boundary() -> None:
"""阈值边界:刚好 ≥ 阈值才清洗;< 阈值保留。"""
entries = [
_mk(0.0, 15.0, "晚安"), # 恰好 15s → 清洗(≥ threshold
_mk(0.0, 14.99, "晚安"), # 14.99s → 保留
]
out = mask_hallucination_text(entries, threshold_seconds=15.0)
assert out[0]["text"] == "-"
assert out[1]["text"] == "晚安"
@pytest.mark.integration
def test_mask_does_not_mutate_input() -> None:
"""清洗不得修改原始条目对象(纯函数约束)。"""
entries = [_mk(60.0, 90.0, "晚安")]
original_text = entries[0]["text"]
mask_hallucination_text(entries)
assert entries[0]["text"] == original_text