python 使用静音检测拆分音频文件

r3i60tvu  于 2023-06-04  发布在  Python
关注(0)|答案(5)|浏览(593)

我有200多个MP3文件,我需要使用静音检测来分割每个文件。我尝试了Audacity和WavePad,但它们没有批处理过程,而且一个一个地创建它们非常慢。
场景如下:

  • 分割音轨,同时静音2秒或更长时间
  • 然后在这些曲目的开始和结束处添加0.5秒,并将其保存为.mp3
  • 比特率192立体声
  • 规格化音量以确保所有文件的音量和质量相同

我尝试了FFmpeg,但没有成功。

snz8szmq

snz8szmq1#

我发现pydub是以简单的方式和紧凑的代码完成这种音频操作的最简单的工具。
您可以使用以下命令安装pydub

pip install pydub

如果需要,您可能需要安装ffmpeg/avlib。请参阅this link了解更多详细信息。
下面是一个代码片段,它完成了您所要求的操作。某些参数(如silence_thresholdtarget_dBFS)可能需要进行一些调优以满足您的要求。总的来说,我能够拆分mp3文件,尽管我不得不为silence_threshold尝试不同的值。

片段

# Import the AudioSegment class for processing audio and the 
# split_on_silence function for separating out silent chunks.
from pydub import AudioSegment
from pydub.silence import split_on_silence

# Define a function to normalize a chunk to a target amplitude.
def match_target_amplitude(aChunk, target_dBFS):
    ''' Normalize given audio chunk '''
    change_in_dBFS = target_dBFS - aChunk.dBFS
    return aChunk.apply_gain(change_in_dBFS)

# Load your audio.
song = AudioSegment.from_mp3("your_audio.mp3")

# Split track where the silence is 2 seconds or more and get chunks using 
# the imported function.
chunks = split_on_silence (
    # Use the loaded audio.
    song, 
    # Specify that a silent chunk must be at least 2 seconds or 2000 ms long.
    min_silence_len = 2000,
    # Consider a chunk silent if it's quieter than -16 dBFS.
    # (You may want to adjust this parameter.)
    silence_thresh = -16
)

# Process each chunk with your parameters
for i, chunk in enumerate(chunks):
    # Create a silence chunk that's 0.5 seconds (or 500 ms) long for padding.
    silence_chunk = AudioSegment.silent(duration=500)

    # Add the padding chunk to beginning and end of the entire chunk.
    audio_chunk = silence_chunk + chunk + silence_chunk

    # Normalize the entire chunk.
    normalized_chunk = match_target_amplitude(audio_chunk, -20.0)

    # Export the audio chunk with new bitrate.
    print("Exporting chunk{0}.mp3.".format(i))
    normalized_chunk.export(
        ".//chunk{0}.mp3".format(i),
        bitrate = "192k",
        format = "mp3"
    )

如果您的原始音频是立体声(2声道),则您的块也将是立体声。你可以像这样检查原始音频:

>>> song.channels
2
hxzsmxv2

hxzsmxv22#

您可以尝试使用此方法在静音时分割音频,而无需探索静音阈值的可能性

def split(filepath):
    sound = AudioSegment.from_file(filepath)
    chunks = split_on_silence(
        sound,
        min_silence_len = 500,
        silence_thresh = sound.dBFS - 16,
        keep_silence = 250, # optional
    )

注意,在使用该值之后,不需要调整silence_thresh值。
此外,如果你想通过设置音频块的最小长度来分割音频,你可以在上面提到的代码之后添加它。

# minimum chunk length
target_length = 25 * 1000 # 25 seconds

output_chunks = [chunks[0]]
for chunk in chunks[1:]:
    if len(output_chunks[-1]) < target_length:
        output_chunks[-1] += chunk
    else:
        # if the last output chunk
        # is longer than the target length,
        # we can start a new one
        output_chunks.append(chunk)

现在我们使用output_chunks进行进一步的处理

mwkjh3gx

mwkjh3gx3#

测试了所有这些解决方案,没有一个对我有效,我找到了一个对我有效而且相对快速的解决方案。
先决条件:
1.使用ffmpeg
1.它基于Vincent Berthiaume在本文中的代码(https://stackoverflow.com/a/37573133/2747626
1.它需要numpy(尽管它不需要numpy太多,没有numpy的解决方案可能相对容易编写并进一步提高速度)
操作模式、原理:
1.这里提供的解决方案是基于人工智能的,或者非常慢,或者将整个音频加载到内存中,这对我的目的来说是不可行的(我想将巴赫的所有勃兰登堡协奏曲的录音分成特定的歌曲,2个LP长达2小时,@44 kHz 16 bit立体声,内存为1.4 GB,非常慢)。从一开始,当我偶然发现这篇文章时,我就告诉自己,必须有一个简单的方法,因为这只是一个阈值过滤操作,不需要太多的开销,并且可以一次在很小的音频块上完成。几个月后,我偶然发现了https://stackoverflow.com/a/37573133/2747626,这给了我一个相对有效地完成音频分割的想法。
1.命令行参数给予源mp3(或ffmpeg可以读取的任何内容)、静音持续时间和噪声阈值。对于我的巴赫LP录音,1秒的垃圾0. 01的全振幅做的伎俩。
1.它允许ffmpeg将输入转换为无损的16位22 kHz PCM,并通过subprocess.Popen将其传递回来,其优点是ffmpeg的速度非常快,并且在小块中不会占用太多内存。
1.回到python中,最后一个和最后一个缓冲区之前的两个临时numpy数组被连接起来,并检查它们是否超过给定的阈值。如果他们没有,这意味着有一个沉默的块,(天真地我承认)简单地计算时间有“沉默”。如果时间至少与给定的min.静默持续时间,(再次天真地)将该当前间隔的中间作为分裂时刻。
1.该程序实际上不对源文件做任何事情,而是创建一个可以运行的批处理文件,告诉ffmpeg获取由这些“沉默”限制的片段,并将它们保存到单独的文件中。
1.然后,用户可以运行输出的批处理文件,可能会过滤一些重复的微间隔,其中包含微小的静音块,以防歌曲之间有长时间的停顿。
1.这个解决方案既有效又快速(这个线程中的其他解决方案都不适合我)。
小代码:

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()
dfuffjeb

dfuffjeb4#

除了上面的长答案。我最终在紧要关头做了下面的事情,然后你像split.py {input.wav或mp3} 1.3那样运行它,其中最后两个分别是沉默的最小长度和阈值。
仅在Windows上进行测试。由于原件显示为ffmpeg.exe。YMMV
如果阈值太高,它往往只会创建最小沉默长度的块?或者低?所以你必须玩它,并观察结果。 bat 长度的线索。通常越短越好。使用更现代的库可能会有更好的解决方案。我已经想到一个了,但现在没有时间。这只是对另一个问题的修正,在现代Python中,但是我将把前面的答案留给老Python用户

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 = 16000
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

try:
    while tf:

        raw = pipe.stdout.read(buflen)
        if raw == '':
            tf = False
            break

        arr = numpy.frombuffer(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
            # print(f"sm {sm} len2 {len2}")
            if sm >= len2:
                part += 1
                apos = pos + dur * 0.5
                #print( mx, sm, len2, apos)
                f.write(f'ffmpeg -i "{src}" -ss {opos} -to {apos} -c copy -y "{src}-p{part}.wav"\r\n'.encode() )
                opos = apos

        pos += dur

        oarr = arr

except OSError as err:
    print("OS error: {0}".format(err))
except ValueError:
    print("Could not convert data to an integer.")
except BaseException as err:
    print(f"Unexpected {err}=, {type(err)}=")

part += 1    
f.write(f'ffmpeg -i "{src}" -ss {opos} -to {pos} -c copy -y "{src}-p{part}.wav"\r\n'.encode())
f.close()
kknvjkwl

kknvjkwl5#

下面的代码对我来说效果最好:

from pydub import AudioSegment
from pydub.silence import split_on_silence

def split_audio_by_silence(input_file, silence_threshold=-50, min_silence_duration=500):
    audio = AudioSegment.from_file(input_file)

    # Split the audio based on silence
    segments = split_on_silence(
        audio,
        min_silence_len=min_silence_duration,
        silence_thresh=silence_threshold
    )

    # Export each segment as a separate file
    for i, segment in enumerate(segments, start=1):
        output_file = f"chunk_{i}.mp3"
        segment.export(output_file, format="mp3")

        # Print the start and end time of each chunk
        chunk_start_time = (segment[0].frame_count() / segment.frame_rate) * 1000
        chunk_end_time = (segment[-1].frame_count() / segment.frame_rate) * 1000
        print(f"Chunk {i}: {chunk_start_time}ms to {chunk_end_time}ms")

# Example usage
input_file = "input.mp3"

split_audio_by_silence(input_file)

记住在运行代码之前使用pip install pydub安装pydub库。

相关问题