2.5 KiB
2.5 KiB
Empty Segment Gap(空白段漏转写)
症状
全长转写结果中,某一段(几秒到几十秒)输出为空文本:
{"start": 1376.42, "end": 1406.42, "text": ""}
该段音频实际包含清晰的对话,单独提取转写完全正常。
根因
与 token repetition loop 同一类问题——Whisper autoregressive decoder 的边界故障,但表现不同:
- 循环幻觉:低置信度边界 → 猜错第一个 token → 自激放大 → 重复输出
- 空段:低置信度边界 → decoder 全程置信度不足 → 输出空字符串
两种故障都发生在长音频中后段(20+ 分钟),此时 KV cache 积累了大量上下文,在模糊发音、犹豫、或者声学域偏移(如不同录音源的拼接点)处模型失去对齐。
修复方案
与 token repetition loop 相同——检测 → 提取 → 冷启动重转写:
检测
def find_empty_gaps(segments: list[dict], min_duration: float = 1.0) -> list[int]:
"""返回所有空文本且时长超过 min_duration 的 segment 索引"""
return [
i for i, s in enumerate(segments)
if not s.get("text", "").strip() and s["end"] - s["start"] > min_duration
]
合并连续空段
连续的空段合并为一个时间范围,避免对同一片音频发起多次重转写。
冷启动重写
ffmpeg 切出该段 → mlx_whisper.transcribe() 不带前序上下文 → 如果重转写得到有效文本,替换回原结果。
如果重转写也无输出(说明该段确实是静音/纯噪),则直接删除该段,不保留原始空字幕。这与旧版"保留 originals"的做法不同——避免静音段冷启动逼出幻听。
为什么 VAD 过滤没抓到
VAD(静音过滤)检查的是 segments 是否落在 ffmpeg silencedetect 标记的静音区。空段大多不是静音(有音乐、背景音甚至人声),只是 Whisper 的 decoder 没有输出文字——所以不会被 VAD 丢弃。
在脚本中的实现
空段检测已合并进 fix_repetition_loops() 函数(Mode B),与循环检测共享重转写逻辑骨架。位于 whisper_transcribe.py 的 Post-processing 阶段,在 --no-clean 未设置时运行。
核心行为:
- 空段 ≥2s → 标记为异常,冷启动重试
- 重试成功 → 用新 segments 替换
- 重试仍为空 →
del删除该段(不入库) - 使用
reversed(ranges)逆序遍历,防止多区间替换时的索引漂移