搜索文章

输入关键词开始搜索

Whisper提取文本和切分音频

AI#音视频#English

最近在整理托福真题音频时,遇到一个很现实的问题。

很多口语音频不是一个文件对应一个题目,而是一个文件里面同时包含多个环节。比如先是 Listen and Repeat,后面又接着 Take an interview。如果只是把整段音频丢给 Whisper 转成文本,当然能看懂内容,但这还不够。

真正麻烦的是:我后面还要把其中某一段音频切出来,单独做训练材料。

这就需要知道每一段在原始音频里的起止时间。否则只能靠手动拖进度条听,太慢,而且很容易切不准。

Whisper 音频转写与切分校验流程

先说结论

Whisper 是可以拿到时间戳的。

它至少可以给两层时间信息:

  1. segment 级别的起止时间,也就是一小段文本对应音频里的哪一段。
  2. word 级别的起止时间,也就是每个单词大概从第几秒开始、第几秒结束。

对我这个场景来说,真正有用的是第二种,也就是 word_timestamps

因为 segment 是模型自动切出来的,它不一定刚好按题型边界切。比如 Take an interview 可能会被合并到上一段 Listen and Repeat 的最后一句里面。如果只看 segment,就会切过头。

这次实验的音频

我随便找了一个真题口语音频:

真题/1月/1.21-2/新托福2026真题02SpeakingModule1.mp3

这个音频总长大约 159.61s。它的结构比较典型:

Speaking section
Listen and repeat
...
Take an interview
...

这正好适合用来验证:Whisper 能不能帮我找到 Listen and Repeat 的起点,以及 Take an interview 的起点。

Whisper 命令

我本机上 whisper 在 Anaconda 里,ffmpeg 在 Homebrew 里,所以命令里加了一下 PATH

PATH="/opt/homebrew/bin:$PATH" /opt/homebrew/anaconda3/bin/whisper \
  "真题/1月/1.21-2/新托福2026真题02SpeakingModule1.mp3" \
  --model base.en \
  --language English \
  --output_format json \
  --word_timestamps True \
  --output_dir /tmp/codex-whisper-speaking02

这里关键是两个参数:

--output_format json
--word_timestamps True

json 里会保留结构化结果,word_timestamps 会把每个词的时间也带出来。

识别出来的关键时间

Whisper 的 segment 结果大概是这样:

[13.10-18.92] Listen and repeat. You will listen as someone speaks to you...
[77.08-85.04] informed about upcoming sales. Take an interview. An interviewer will ask you questions...

这里就能看出一个问题:Take an interview 被放进了上一段 segment 里。也就是说,segment 对阅读转写文本够用,但对精准切音频还不够。

再看 word 级别时间戳,就清楚多了:

13.10  13.42  Listen
13.42  13.82  and
13.82  14.06  repeat.

79.88  80.56  Take
80.56  80.74  an
80.74  81.08  interview.

所以这次可以得到几个比较实用的边界:

内容时间
Listen and repeat 标题13.10s - 14.06s
Take an interview 标题79.88s - 81.08s
Listen and Repeat 整个环节13.10s - 79.75s
题目材料,含场景描述和跟读句子28.54s - 79.50s
只保留跟读句子,不含场景描述42.86s - 79.50s

这里我留了一点点 buffer。比如 Take an interview79.88s 开始,那前一段可以切到 79.75s 左右,避免把下一个标题带进去。

这里还有一个容易漏掉的点:42.86s 是第一句跟读内容的开始,但它前面还有一小段场景描述。比如这次是:

You are working a part-time job at a grocery store near campus.
You are being trained to assist customers.
Listen to your trainer and repeat what he says.
Repeat only once.

如果是做真正的题目训练,这段场景描述应该保留。否则虽然跟读句子还在,但题目背景没了,材料是不完整的。

用 FFmpeg 切出来

如果我要保留整个 Listen and Repeat 环节,包括说明、场景和所有跟读句子,可以这样切:

/opt/homebrew/bin/ffmpeg -y \
  -i "真题/1月/1.21-2/新托福2026真题02SpeakingModule1.mp3" \
  -ss 13.10 -to 79.75 \
  -c:a libmp3lame -q:a 2 \
  /tmp/codex-audio-cuts/speaking02-listen-repeat-section.mp3

如果我只想保留题目材料,不要前面的考试说明,但要保留场景描述,应该从 28.54s 开始切:

/opt/homebrew/bin/ffmpeg -y \
  -i "真题/1月/1.21-2/新托福2026真题02SpeakingModule1.mp3" \
  -ss 28.54 -to 79.50 \
  -c:a libmp3lame -q:a 2 \
  /tmp/codex-audio-cuts/speaking02-listen-repeat-question-with-context.mp3

如果只想做非常纯粹的 shadowing,完全不要场景描述,也可以从第一句跟读内容开始切。但这个更像是二次加工材料,不适合作为默认切法:

/opt/homebrew/bin/ffmpeg -y \
  -i "真题/1月/1.21-2/新托福2026真题02SpeakingModule1.mp3" \
  -ss 42.86 -to 79.50 \
  -c:a libmp3lame -q:a 2 \
  /tmp/codex-audio-cuts/speaking02-listen-repeat-prompts-only.mp3

这里我没有用 -c copy,而是重新编码了一下。原因很简单:我更在意切点准确。-c copy 更快,但有时会受音频帧或视频关键帧影响,切出来的点不一定那么干净。

这个方法的原理

不用把它想复杂。

Whisper 在识别音频时,本来就不是只输出一整段文字。它内部会把音频切成若干片段,每个片段都有对应的时间范围。开启 word_timestamps 之后,它会进一步估计每个单词在音频时间轴上的位置。

所以我们可以反过来利用这个信息:

  1. 先把整段音频转成带时间戳的 JSON。
  2. 在 JSON 里找题型标题,比如 Listen and repeatTake an interview
  3. 用下一个题型标题的开始时间,作为上一个题型的结束时间。
  4. 前后加一点点 buffer。
  5. 用 FFmpeg 按这个时间范围切音频。

这里最重要的不是 Whisper 把每个词都识别对。比如这次它把一些商品词识别得并不准,但这不影响切分,因为我要找的是题型标题和结构边界。

真正需要注意的是:不要只依赖普通文本,也不要只依赖 segment。普通文本没有时间,segment 的边界又不一定等于题型边界。比较稳的做法是用 word 级别的时间戳找锚点。

后面可以自动化

如果后面要批量处理真题音频,我会按这个流程做:

  1. 遍历所有口语音频。
  2. 用 Whisper 生成 JSON。
  3. 把所有 word 拉平成一个列表。
  4. 忽略大小写和标点,匹配 listen and repeattake an interview 这类锚点。
  5. 生成一个切分清单,比如 audio_path / section / start / end
  6. 再批量调用 FFmpeg 切出目标音频。

这个流程比较适合托福这种固定结构的音频。因为题型标题通常比较稳定,不需要真的理解整段内容,只要能找到结构锚点就行。

这次的经验

这个方法解决的不是“转写文本”的问题,而是“文本和音频时间轴对齐”的问题。

以前我容易把 Whisper 理解成一个转文字工具,但这次发现它更有价值的地方是:它可以帮我把一段长音频变成可检索、可切分的结构化材料。

这对后面的听力和口语训练很有用。真题音频只要先过一遍 Whisper,就可以自动拆成更小的训练单元,不需要每次都手动听、手动切。

下次再处理这类音频,我会直接先生成带 word timestamp 的 JSON,再决定怎么切。不要一开始就手动拖进度条,那个效率太低了。