测试了所有这些解决方案,但没有一个对我有用,我找到了一个对我有用且速度相对较快的解决方案。
先决条件:
- 它适用于
ffmpeg
- 它基于本文 (https://stackoverflow.com/a/37573133/2747626) 中 Vincent Berthiaume 的代码
- 它需要
numpy(尽管它不需要太多来自 numpy 的解决方案,没有numpy 的解决方案可能相对容易编写并进一步提高速度)
操作模式,原理:
- 这里提供的解决方案是基于 AI 的,或者非常慢,或者将整个音频加载到内存中,这对我的目的来说是不可行的(我想将巴赫的所有勃兰登堡协奏曲的录音分割成特定的歌曲, 2 LP 时长 2 小时,@ 44 kHz 16 位立体声,内存为 1.4 GB,速度非常慢)。从一开始,当我偶然发现这篇文章时,我就告诉自己必须有一个简单的方法,因为这只是一个阈值过滤操作,不需要太多开销,一次可以在一小块音频上完成。几个月后,我偶然发现了https://stackoverflow.com/a/37573133/2747626,这让我产生了相对高效地完成音频分割的想法。
- 命令行参数提供源 mp3(或任何 ffmpeg 可以读取的内容)、静音持续时间和噪声阈值。对于我的巴赫 LP 录音,1 秒 0.01 的全振幅就可以了。
- 它让
ffmpeg 将输入转换为无损的 16 位 22kHz PCM 并通过subprocess.Popen 将其传回,其优势在于ffmpeg 的速度非常快,而且在不占用太多内存的小块中。
- 回到python,最后一个和最后一个缓冲区的2个临时
numpy数组被连接起来,并检查它们是否超过给定的阈值。如果他们不这样做,则意味着有一段沉默,并且(我天真地承认)只需计算“沉默”的时间。如果时间至少与给定的分钟一样长。沉默持续时间,(再次天真地)将当前间隔的中间作为分裂时刻。
- 该程序实际上并没有对源文件做任何事情,而是创建一个可以运行的批处理文件,告诉
ffmpeg 获取由这些“静音”限定的段并将它们保存到单独的文件中。
- 然后,用户可以运行输出批处理文件,可能会过滤一些重复的带有微小静音块的微小间隔,以防歌曲之间出现长时间的停顿。
- 此解决方案既有效又快速(此线程中的其他解决方案均不适合我)。
小代码:
import subprocess as sp
import sys
import numpy
FFMPEG_BIN = "ffmpeg.exe"
print 'ASplit.py <src.mp3> <silence duration in seconds> <threshold amplitude 0.0 .. 1.0>'
src = sys.argv[1]
dur = float(sys.argv[2])
thr = int(float(sys.argv[3]) * 65535)
f = open('%s-out.bat' % src, 'wb')
tmprate = 22050
len2 = dur * tmprate
buflen = int(len2 * 2)
# t * rate * 16 bits
oarr = numpy.arange(1, dtype='int16')
# just a dummy array for the first chunk
command = [ FFMPEG_BIN,
'-i', src,
'-f', 's16le',
'-acodec', 'pcm_s16le',
'-ar', str(tmprate), # ouput sampling rate
'-ac', '1', # '1' for mono
'-'] # - output to stdout
pipe = sp.Popen(command, stdout=sp.PIPE, bufsize=10**8)
tf = True
pos = 0
opos = 0
part = 0
while tf :
raw = pipe.stdout.read(buflen)
if raw == '' :
tf = False
break
arr = numpy.fromstring(raw, dtype = "int16")
rng = numpy.concatenate([oarr, arr])
mx = numpy.amax(rng)
if mx <= thr :
# the peak in this range is less than the threshold value
trng = (rng <= thr) * 1
# effectively a pass filter with all samples <= thr set to 0 and > thr set to 1
sm = numpy.sum(trng)
# i.e. simply (naively) check how many 1's there were
if sm >= len2 :
part += 1
apos = pos + dur * 0.5
print mx, sm, len2, apos
f.write('ffmpeg -i "%s" -ss %f -to %f -c copy -y "%s-p%04d.mp3"\r\n' % (src, opos, apos, src, part))
opos = apos
pos += dur
oarr = arr
part += 1
f.write('ffmpeg -i "%s" -ss %f -to %f -c copy -y "%s-p%04d.mp3"\r\n' % (src, opos, pos, src, part))
f.close()