web-dev-qa-db-fra.com

Fractionner des fichiers audio à l'aide de la détection du silence

J'ai plus de 200 fichiers MP3 et je dois diviser chacun d'eux en utilisant la détection de silence. J'ai essayé Audacity et WavePad mais ils n'ont pas de processus batch et c'est très lent de les faire un par un.

Le scénario est le suivant:

  • diviser la piste alors que le silence est de 2 secondes ou plus
  • puis ajoutez 0,5 s au début et à la fin de ces pistes et enregistrez-les au format .mp3
  • BitRate 192 stéréo
  • normaliser le volume pour être sûr que tous les fichiers ont le même volume et la même qualité

J'ai essayé FFmpeg mais sans succès.

9
beero

J'ai trouvé pydub pour être l'outil le plus simple pour faire ce genre de manipulation audio de manière simple et avec du code compact.

Vous pouvez installer pydub avec

pip install pydub

Vous devrez peut-être installer ffmpeg/avlib si nécessaire. Voir ce lien pour plus de détails.

Voici un extrait qui fait ce que vous avez demandé. Certains des paramètres tels que silence_threshold et target_dBFS peut nécessiter quelques ajustements pour répondre à vos besoins. Dans l'ensemble, j'ai pu diviser mp3 fichiers, même si j'ai dû essayer différentes valeurs pour silence_threshold.

Extrait

# 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"
    )

Si votre audio d'origine est stéréo (2 canaux), vos morceaux seront également stéréo. Vous pouvez vérifier l'audio d'origine comme ceci:

>>> song.channels
2
15
Anil_M

Après avoir testé toutes ces solutions et aucune d'entre elles n'ayant fonctionné pour moi, j'ai trouvé une solution qui a fonctionné pour moi et qui est relativement rapide.

Conditions préalables:

  1. Cela fonctionne avec ffmpeg
  2. Il est basé sur le code de Vincent Berthiaume de ce post ( https://stackoverflow.com/a/37573133/2747626 )
  3. Il nécessite numpy (bien qu'il n'ait pas besoin de beaucoup de numpy et une solution sans numpy serait probablement relativement facile à écrire et augmenterait encore la vitesse)

Mode de fonctionnement, justification:

  1. Les solutions fournies ici étaient basées sur l'IA, ou étaient extrêmement lentes, ou chargeaient l'intégralité de l'audio en mémoire, ce qui n'était pas faisable pour mes besoins (je voulais diviser l'enregistrement de tous les concertos brandebourgeois de Bach en chansons particulières, les 2 LPs sont 2 heures de long, @ 44 kHz 16 bits stéréo qui est de 1,4 Go en mémoire et très lent). Dès le début, lorsque je suis tombé sur ce post, je me disais qu'il devait y avoir un moyen simple car il s'agit d'une simple opération de filtrage de seuil qui ne nécessite pas beaucoup de surcharge et pourrait être accomplie sur de minuscules morceaux audio à la fois. Quelques mois plus tard, je suis tombé sur https://stackoverflow.com/a/37573133/2747626 ce qui m'a donné l'idée de réaliser un fractionnement audio de manière relativement efficace.
  2. Les arguments de la ligne de commande donnent le mp3 source (ou tout ce que ffmpeg peut lire), la durée du silence et la valeur du seuil de bruit. Pour mon enregistrement Bach LP, 1 seconde jonque de 0,01 de pleine amplitude a fait l'affaire.
  3. Il permet à ffmpeg de convertir l'entrée en un PCM 16 bits 22 kHz sans perte et de la renvoyer via subprocess.Popen, avec l'avantage que ffmpeg le fait très rapidement et en petits morceaux qui n'occupent pas beaucoup de mémoire.
  4. De retour en python, 2 tableaux numpy temporaires du dernier et avant-dernier tampon sont concaténés et vérifient s'ils dépassent le seuil donné. S'ils ne le font pas, cela signifie qu'il y a un bloc de silence, et (j'admets naïvement) il suffit de compter le temps où il y a du "silence". Si le temps est au moins aussi long que le min donné. la durée du silence, (encore naïvement) le milieu de cet intervalle actuel est pris comme moment de division.
  5. En fait, le programme ne fait rien avec le fichier source et crée à la place un fichier de commandes qui peut être exécuté qui indique à ffmpeg de prendre les segments délimités par ces "silences" et de les enregistrer dans des fichiers séparés.
  6. L'utilisateur peut ensuite exécuter le fichier batch de sortie, peut-être filtrer à travers des micro-intervalles répétitifs avec de minuscules morceaux de silence au cas où il y aurait de longues pauses entre les chansons.
  7. Cette solution fonctionne à la fois et rapidement (aucune des autres solutions de ce fil n'a fonctionné pour moi).

Le petit code:

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

Vous pouvez essayer de l'utiliser pour diviser l'audio sur le silence sans avoir à explorer les possibilités pour le seuil de silence

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
    )

Notez que la valeur silence_thresh n'a pas besoin d'être ajustée après l'avoir utilisée.

De plus, si vous souhaitez diviser l'audio en définissant la longueur minimale du morceau audio, vous pouvez l'ajouter après le code mentionné ci-dessus.

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)

maintenant nous utilisons output_chunks pour un traitement ultérieur

0
droidmainiac