whisper-transcribe/references/empty-segment-gap.md

2.5 KiB
Raw Blame History

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) 逆序遍历,防止多区间替换时的索引漂移