Splitting a 2-hour radio show into per-song files with silence detection

这篇内容暂时只有 English 版本,以下是原文。

A friend hands me a 2-hour community radio show: one continuous WAV, 48 kHz, 24-bit, recorded off the desk feed. She wants it as individual tracks for the archive, with the spoken intros attached to the song that follows. There are 31 songs and no cue sheet. The recording has a low noise floor and a compressor that breathes a little between tracks, which matters later.

My first pass was pure amplitude thresholding — ffmpeg -af silencedetect=noise=-38dB:d=1.2 — and it found 44 boundaries. Nine of them were false: quiet piano endings, a fade-out, two intros where she pauses to breathe. The opposite failure happened too: a segue between two tracks that cross-fade for 4 seconds produced no boundary at all.

So I stopped treating silence as the signal and started using spectral change. I decode to mono 16 kHz and compute an RMS envelope in 50 ms hops, then take the absolute difference between consecutive frames. Track changes show up as a sustained jump over 300–600 ms, while speech pauses show a single spike and immediate return. A short numpy pass over the decoded samples was enough:

import soundfile as sf, numpy as np
x, sr = sf.read('show.wav', dtype='float32', always_2d=True)
mono = x.mean(axis=1)
hop = int(0.05 * sr)
frames = mono[:len(mono)//hop*hop].reshape(-1, hop)
rms = np.sqrt((frames**2).mean(axis=1))
d = np.abs(np.diff(rms))
# candidates: sustained rise, not a one-frame blip
win = 8  # 400 ms
score = np.convolve(d, np.ones(win)/win, mode='same')
cand = np.where(score > 0.02)[0]

That got me to 30 boundaries, one missing. The missing one was the cross-fade: both tracks were loud, so RMS barely moved. I caught it by ear and cut on the midpoint of the overlap. Cost: about 40 minutes total, most of it listening to candidate boundaries rather than writing code. The threshold 0.02 is not portable — it depends on how compressed the desk feed is. On a different show I had to drop it to 0.008.

Where this breaks: acoustic sets with sustained drones, applause that swells and decays, and any show where the host talks over the intro. In those cases the spectral-difference method will put the cut inside the song, and you will only find out when you listen. I also would not trust it on a lossy source — MP3 pre-echo smears the envelope and the scores flatten out. If the archive needs certainty, the only honest answer is to listen to every boundary, which is why I now ask for a running order at the start and use the detector only to place cuts within ±2 seconds.

One question for anyone who has done this at scale: do you keep the spoken intro with the preceding track or the following one, and how do you name it so the archive stays searchable?

0
赞同度 100% · 0 条评论 · 4 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。