feat: whisper 新增 decode_full 无VAD整段解码参数并改为整条删除式幻觉清洗
解决转写漏句(有人说话但没识别出来)问题:silero VAD 对呻吟/轻语/BGM 混叠声学切段能力天然不足,把真话当非语音剔除(实测 savr-1054 全片仅 召回 115 条)。新增 decode_full 参数(默认 false 保持 VAD 现状): - decode_full=true 时强制无 VAD 整段解码 + 跳过自动 VAD 分析,救回被 剔除的弱语音(savr-1054 全片 115 条 → 340 条) - 副作用是长时寒暄套话幻觉(おやすみなさい/ご視聴ありがとうございま した 等),whisper 转录后连带时间戳整条删除(clean_japanese_ha lllucinations),不留下 '-' 占位污染下游(占位会渲染进 ASS 成减号) - llm-translate 翻译后同样整条删除中文长时寒暄幻觉(clean_srt_text) - 短时(≤15s)相同词可能是剧情真实道晚安,保留(15s 阈值实测校准) - subtitle_cleanup 由 '-' 占位式改为整条删除式 + 剩余重编号,新增 JAPANESE_HALLUCINATION_TOKENS 词表 新增工作流 learn-translate(学习资料转译+翻译字幕)示范 decode_full 用法,并确立参数标注约定:params._note_<参数名> 存放设定理由与正反例、 _node_help 放节点参数手册(_ 前缀说明键,节点执行时忽略,零运行影响)。 调研记录见 docs/调研-whisper漏句与decode_full验证.md(A/B 实验、结论 修正与 5 个待决问题)。
This commit is contained in:
@@ -1426,3 +1426,76 @@ def test_vlm_truncate_at_stop() -> None:
|
||||
assert _truncate_at_stop("还有没有什么困扰 或者奇怪的地方吗") == (
|
||||
"还有没有什么困扰 或者奇怪的地方吗"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# decode_full:无 VAD 整段解码 + 日语幻觉清洗(TDD)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_whisper_decode_full_disables_vad(tmp_path, monkeypatch) -> None:
|
||||
"""decode_full=true 时 transcribe 收到 vad_filter=False 且不触发自动 VAD。"""
|
||||
captured = {}
|
||||
|
||||
class FullModel:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def transcribe(self, path, **kwargs):
|
||||
captured["vad_filter"] = kwargs.get("vad_filter")
|
||||
captured["vad_parameters"] = kwargs.get("vad_parameters")
|
||||
return ([FakeSegment(0, 1, "ok")], None)
|
||||
|
||||
monkeypatch.setitem(
|
||||
sys.modules, "faster_whisper", types.SimpleNamespace(WhisperModel=lambda *a, **k: FullModel())
|
||||
)
|
||||
_make_wav(tmp_path / "audio.wav", 5)
|
||||
# 默认 vad_filter=true(保持现状),显式 decode_full=true 强制无 VAD 整段解码。
|
||||
resp = whisper_invoke(
|
||||
_whisper_request(tmp_path, params={"language": "ja", "decode_full": True})
|
||||
)
|
||||
assert resp.status == "completed"
|
||||
assert captured["vad_filter"] is False
|
||||
assert captured["vad_parameters"] is None
|
||||
|
||||
|
||||
def test_whisper_decode_full_cleanup_jp_hallucination(tmp_path, monkeypatch) -> None:
|
||||
"""decode_full=true 时产物 SRT 中日文长时寒暄幻觉被**整条删除**(连带时间戳)。"""
|
||||
class HallucModel:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def transcribe(self, path, **kwargs):
|
||||
# 模拟无 VAD 解码:正常句 + 一条 30s '晚安'幻觉占位 + 一条 2s 真实晚安。
|
||||
return (
|
||||
[
|
||||
FakeSegment(0, 2, "気持ちいい"),
|
||||
FakeSegment(10, 40, "おやすみなさい"), # 30s 幻觉
|
||||
FakeSegment(45, 47, "おやすみなさい"), # 2s 真实
|
||||
],
|
||||
None,
|
||||
)
|
||||
|
||||
monkeypatch.setitem(
|
||||
sys.modules, "faster_whisper", types.SimpleNamespace(WhisperModel=lambda *a, **k: HallucModel())
|
||||
)
|
||||
_make_wav(tmp_path / "audio.wav", 5)
|
||||
resp = whisper_invoke(
|
||||
_whisper_request(tmp_path, params={"language": "ja", "decode_full": True})
|
||||
)
|
||||
assert resp.status == "completed"
|
||||
content = Path(resp.outputs["srt_uri"]).read_text(encoding="utf-8")
|
||||
# 30s 幻觉整条删除(时间轴 10-40s 不出现);2s 真实晚安与正常句保留。
|
||||
assert "気持ちいい" in content
|
||||
assert content.count("おやすみなさい") == 1
|
||||
assert "00:00:10,000 --> 00:00:40,000" not in content
|
||||
# 不残留 '-' 占位(时间轴里的 '-' 是 SRT 合法分隔符,只检查文本行)。
|
||||
text_lines = [
|
||||
l for l in content.splitlines()
|
||||
if l.strip() and not l.strip().isdigit() and "-->" not in l
|
||||
]
|
||||
assert all(t != "-" for t in text_lines)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user