Vai al contenuto

Come ho realizzato un tool per dividere un file audio

Scritto da:

carlopeluso

In questi giorni ho sviluppato una piccola soluzione web per lo split dei file musicali, in particolare dei Full Album ovvero interi dischi rinchiusi in un unico file MP3. Questo strumento si può comunque anche adoperare nel caso si voglia tagliare un qualsiasi file audio in piu parti.

Lo stack operativo (tutto realizzato in ambito Windows):

  • un server NodeJs con installati i seguenti moduli NPM:
    • express
    • websocket server
    • chrome-launcher
  • la libreria command line FFmpeg
  • un client scritto in Javascript che utilizza i seguenti moduli:
  • uno strumento per la build dell’eseguibile chiamato PKG

Il core: FFmpeg.

L’idea è quella di elaborare il file audio passato in input attraverso i comandi di FFmpeg, nello specifico:

ffmpeg -i <input> -ss <time> -to <time> -c copy <output>

che operativamente si traduce in:

ffmpeg -i input.mp3 -ss 00:01:04 -to 00:01:33 -y -c copy output.mp3

questo comando ci permetterà di tagliare l’audio in un segmento (da minuto 1:04 a minuto 1:33) ma risulta molto complicato andare a cercare i punti dove tagliare manualmente.

TIP: esiste anche un’alternativa a FFmpeg chiamata SOX,  che permette di effettuare lo split in automatico impostando una soglia di silenzio.

Ho testato il seguente codice, studiando la documentazione di SOX ma per il momento lo metto da parte perche ho riscontrato degli errori di split. In futuro ne farò una versione meglio tarata.

sox --norm %1 %1.mp3 silence -l 0 2 1.6 0.2% : newfile : restart

Il server NodeJs

Per evitare di eseguire a mano il comando FFMPEG ho implementato un piccolo server che funge da wrapper per il comando, attraverso il modulo child_process e il metodo spawn, che permettono di eseguire i comandi CMD in modo programmatico.

var process = require('child_process');

e di conseguenza il wrapper che esegue il comando:

const ffmpeg = process.spawn("<command>", [], { shell: true })

ffmpeg.on('error', (error) => { console.log("Errore.") })

Abbiamo bisogno di eseguire il comando piu volte, uno per ogni segmento che abbiamo deciso di splittare, ciascuno con il suo timestamp di inizio e timestamp di fine.

La web application

Attraverso l’uso del noto framework per Node, Express, ho realizzato una piccola interfaccia grafica che rispondesse all’obiettivo di visualizzare l’intero audio e permettere all’utente di inserire, togliere dei marker per suddividere l’audio, oppure di identificare in automatico la presenza o meno di silenzi.

Attraverso Express, l’applicazione si presenta con una GUI in HTML che mostra la visualizzazione a waveform del file passato in input, utilizzando l’ottima libreria wavesurfer.js, opportunamente personalizzata con il plugin regions, che gestisce i markers.

L’applicazione prevede una prima fase di file upload, che ho volutamente omesso. Viene implementata con una semplice richiesta XHR verso il server Express e salvando il file in una cartella temporanea.

La libreria wavesurfer.js permette nativamente di inserire e rimuovere “regioni” (markers) sia programmaticamente che direttamente su eventi di click e doppioclick sul visual. Nel mio caso, dopo l’inizializzazione della libreria:

var wavesurfer = WaveSurfer.create({ ... });

l’evento sul click genera un nuovo marker:

wavesurfer.drawer.on('dblclick', function (event, progress) {
   wavesurfer.addRegion({
      start: <start>,
      end: <end>,
   });
});

…dove start ed end sono espressi in secondi.

Questo è il punto in cui permettiamo all’utente di inserire i marker manualmente, che successivamente verranno poi inviati a NodeJs per costruire i vari comandi per ogni segmento da splittare.

Riconoscere automaticamente il silenzio

Dal momento che wavesurfer ci permette di visualizzare il grafico waveform, possiamo facilmente ottenere il valore numerico dell’ampiezza dell’audio istante dopo istante: se questo valore rasenta lo zero probabilmente è un silenzio che verrà “candidato” tra i possibili punti di split.

var peaks = wavesurfer.backend.getPeaks(1024)
var silences = [];
Array.prototype.forEach.call(peaks, function (val, index) {
    if (val < SOGLIA && val >= 0) {
        silences.push({ pos: index * coef, value: val });
    }
});

In questo modo tramite il metodo getPeaks (dove 1024 è la resa della Trasformata di Fourier che restituisce in output la gamma di valori per frequenza), possiamo valutare il valore ogni istante di tempo ed inserirlo nell’array silences[] se è inferiore ad un valore soglia che rasenta lo zero. La soglia è a sua volta impostata dall’utente tramite uno slider, che per comodità ho chiamato “Sensivity”.

Illustro brevemente come ho raffinato l’algoritmo, per ogni Peak :

  • Esiste un valore di silenzio (valore < soglia) ?
    • ✔ Se si, esiste un altro valore di silenzio entro un secondo ? (perche le pause tra i brani di un disco sono mediamente intorno al secondo)
      • ✘ Se no, ignora il valore di silenzio poiche anomalo e vai al prossimo Peak.
      • ✔ Se ne esiste piu di uno entro un secondo, allora raggruppa i valori. Inserisci un marker sul grafico con le coordinate in secondi di quello con il valore piu vicino allo 0.
    • ✘ Se no, procedi con il prossimo Peak.

Finale

Una volta inserite tutte le regioni automaticamente, oppure a mano,  possiamo inviarle al server prelevando l’array di regioni create:

var regions = wavesurfer.regions.list;

ed usando un comando websocket per comunicare con il server (non è necessario utilizzare websocket, basta una semplice chiamata HTTP):

webSocket.send(JSON.stringify(regions));

Articolo precedente

Semplici gallerie immagini animate in puro CSS

Articolo successivo

Un software che cerca su google al posto mio

Unisciti alla discussione

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *