Saltar al contenido

Dividir archivos de audio usando la detección de silencio

Siéntete libre de divulgar nuestra web y códigos en tus redes, necesitamos de tu ayuda para aumentar nuestra comunidad.

Solución:

Encontré que pydub es la herramienta más fácil para realizar este tipo de manipulación de audio de forma sencilla y con código compacto.

Puede instalar pydub con

pip install pydub

Es posible que deba instalar ffmpeg / avlib si es necesario. Consulte este enlace para obtener más detalles.

Aquí hay un fragmento que hace lo que pidió. Algunos de los parámetros como silence_threshold y target_dBFS Es posible que necesite algunos ajustes para satisfacer sus necesidades. En general, pude dividir mp3 archivos, aunque tuve que probar diferentes valores para silence_threshold.

Retazo

# 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 chunk0.mp3.".format(i))
    normalized_chunk.export(
        ".//chunk0.mp3".format(i),
        bitrate = "192k",
        format = "mp3"
    )

Si su audio original es estéreo (2 canales), sus fragmentos también serán estéreo. Puede verificar el audio original de esta manera:

>>> song.channels
2

Puede intentar usar esto para dividir audio en silencio sin la molestia de explorar posibilidades para el umbral de silencio

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
    )

Tenga en cuenta que no es necesario ajustar el valor de silent_thresh después de usar esto.

Además, si desea dividir el audio configurando la longitud mínima del fragmento de audio, puede agregar esto después del código mencionado anteriormente.

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)

ahora usamos output_chunks para su posterior procesamiento

Después de haber probado todas estas soluciones y ninguna de ellas funcionó para mí, he encontrado una solución que funcionó para mí y es relativamente rápida.

Requisitos previos:

  1. Funciona con ffmpeg
  2. Se basa en el código de Vincent Berthiaume de esta publicación (https://stackoverflow.com/a/37573133/2747626)
  3. Requiere numpy (aunque no necesita mucho de numpy y una solución sin numpy probablemente sería relativamente fácil de escribir y aumentaría aún más la velocidad)

Modo de funcionamiento, justificación:

  1. Las soluciones proporcionadas aquí se basaron en IA, o fueron extremadamente lentas, o cargaron todo el audio en la memoria, lo cual no era factible para mis propósitos (quería dividir la grabación de todos los Conciertos de Brandenburgo de Bach en canciones particulares, los 2 LP son 2 horas de duración, estéreo de 16 bits a 44 kHz, 1,4 GB de memoria y muy lento). Desde el principio, cuando me topé con esta publicación, me decía a mí mismo que debe haber una manera simple, ya que se trata de una simple operación de filtro de umbral que no necesita mucha sobrecarga y se puede lograr en pequeños fragmentos de audio a la vez. Un par de meses después, me topé con https://stackoverflow.com/a/37573133/2747626, lo que me dio la idea de lograr la división de audio de manera relativamente eficiente.
  2. Los argumentos de la línea de comando dan la fuente mp3 (o lo que sea que ffmpeg pueda leer), la duración del silencio y el valor del umbral de ruido. Para mi grabación en LP de Bach, los juncos de 1 segundo de 0.01 de amplitud completa funcionaron.
  3. Deja ffmpeg Convierta la entrada a un PCM de 22 kHz de 16 bits sin pérdidas y devuélvala a través de subprocess.Popen, con la ventaja de que ffmpeg lo hace muy rápido y en pequeños trozos que no ocupan mucha memoria.
  4. De vuelta en Python, 2 temporales numpy las matrices del último búfer y antes del último se concatenan y se comprueban si superan el umbral dado. Si no lo hacen, significa que hay un bloque de silencio, y (lo admito ingenuamente) simplemente cuenta el tiempo en el que hay "silencio". Si el tiempo es al menos tan largo como el min. duración del silencio, (de nuevo ingenuamente) la mitad de este intervalo actual se toma como el momento de división.
  5. El programa en realidad no hace nada con el archivo fuente y en su lugar crea un archivo por lotes que se puede ejecutar que le dice ffmpeg para tomar segmentos delimitados por estos "silencios" y guardarlos en archivos separados.
  6. Luego, el usuario puede ejecutar el archivo por lotes de salida, tal vez filtrar a través de algunos microintervalos repetidos con pequeños fragmentos de silencio en caso de que haya largas pausas entre canciones.
  7. Esta solución funciona y es rápida (ninguna de las otras soluciones en este hilo funcionó para mí).

El pequeño código:

import subprocess as sp
import sys
import numpy

FFMPEG_BIN = "ffmpeg.exe"

print 'ASplit.py   '

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"rn' % (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"rn' % (src, opos, pos, src, part))
f.close()

Recuerda algo, que tienes concesión de parafrasear tu experiencia si te ayudó.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *