web-dev-qa-db-ja.com

無音検出を使用してオーディオファイルを分割する

私は200を超えるMP3ファイルを持っているので、無音検出を使用してそれぞれを分割する必要があります。 AudacityとWavePadを試しましたが、バッチプロセスがなく、1つずつ作成するのが非常に遅いです。

シナリオは次のとおりです。

  • トラックを分割するが、2秒以上無音にする
  • 次に、これらのトラックの最初と最後に0.5秒を追加し、.mp3として保存します
  • BitRate 192ステレオ
  • すべてのファイルが同じボリュームと品質であることを確認するためにボリュームを正規化する

私はFFmpegを試しましたが、成功しませんでした。

9
beero

私は 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
15
Anil_M

これらのソリューションのすべてをテストし、どれも私のために機能しなかったので、私のために機能し、比較的高速なソリューションを見つけました。

前提条件:

  1. ffmpegで動作します
  2. これは、この投稿のVincent Berthiaumeによるコードに基づいています( https://stackoverflow.com/a/37573133/2747626
  3. numpyが必要です(numpyからの多くは必要ありませんが、numpyなしのソリューションはおそらく比較的簡単に記述でき、さらに速度が向上します)

動作モード、根拠:

  1. ここで提供されるソリューションはAIに基づいているか、非常に遅いか、オーディオ全体をメモリにロードしましたが、これは私の目的には適していませんでした(バッハのブランデンブルク協奏曲のすべての録音を特定の曲に分割したかったので、2つのLPは2時間の長さ、@ 44 kHz 16ビットステレオ。メモリは1.4 GBで非常に低速です)。私がこの投稿を偶然見つけたときから、これはオーバーヘッドをあまり必要とせず、一度に小さなオーディオのチャンクで実行できる単なるしきい値フィルター操作であるため、簡単な方法があるはずだと自分に言い聞かせていました。数か月後、私は偶然偶然に遭遇しました https://stackoverflow.com/a/37573133/2747626 オーディオ分割を比較的効率的に達成するためのアイデアを私に与えました。
  2. コマンドライン引数は、ソースmp3(またはffmpegが読み取ることができるもの)、無音時間、およびノイズしきい値を指定します。私のバッハLPレコーディングの場合、0.01秒のフル振幅の1秒のジャンクでうまくいきました。
  3. ffmpegにより、入力をロスレス16ビット22kHz PCMに変換し、subprocess.Popenffmpegは非常に高速であり、メモリをあまり占有しない小さなチャンクで実行できるという利点があります。
  4. Pythonに戻ると、最後と最後のバッファの2つの一時的なnumpy配列が連結され、指定されたしきい値を超えるかどうかがチェックされます。そうでない場合、それは沈黙のブロックがあることを意味し、(単純に私は認めます)単純に「沈黙」がある時間を数えます。時間が少なくとも指定された分と同じである場合。沈黙の持続時間、この場合も単純に、この現在の間隔の中央が分割の瞬間と見なされます。
  5. プログラムは実際にはソースファイルに対して何も実行せず、代わりに実行可能なバッチファイルを作成します。このバッチファイルはffmpegに、これらの「沈黙」によって区切られたセグメントを取得し、それらを個別のファイルに保存するように指示します。
  6. 次に、ユーザーは出力バッチファイルを実行できます。曲の間に長い休止がある場合は、小さな無音のチャンクで繰り返しマイクロインターバルをフィルタリングできます。
  7. このソリューションは機能していて高速です(このスレッドの他のソリューションはどれも私にとってうまくいきませんでした)。

小さなコード:

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()
1
mxl

これを使用して、無音しきい値の可能性を探る手間をかけずに、無音でオーディオを分割することができます

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

これを使用した後は、silence_thresh値を調整する必要がないことに注意してください。

さらに、オーディオチャンクの最小長を設定してオーディオを分割する場合は、上記のコードの後に​​これを追加できます。

target_length = 25 * 1000 //setting minimum length of each chunk to 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を使用してさらに処理します

0
droidmainiac