Trascrizione audio difficile con faster-whisper medium

Per il 90 percento dell’audio che mi capita di trascrivere, il modello small di Whisper basta e avanza. Poi c’è quell’altro 10 percento: il webinar tecnico ripreso con un microfono da pochi euro montato sul laptop del relatore, la sessione con un secondo relatore in collegamento remoto su una linea ballerina che disturba per metà del tempo, la tavola rotonda dove tre persone si parlano sopra e nessuno tiene il microfono fermo. In quei casi il small comincia ad arrancare, e i refusi non sono più refusi: sono frasi inventate che sembrano plausibili. È il momento del medium.

Il medium di faster-whisper pesa più o meno tre volte il small (intorno a 1,5 GB), e richiede più o meno tre volte la CPU per produrre lo stesso minuto di trascrizione. Vale la pena tirarlo fuori solo quando l’audio è davvero compromesso, perché altrimenti il guadagno qualitativo non giustifica l’attesa.

Setup con faster-whisper

L’installazione è identica a quella che uso per il small: stesso virtualenv, stesso runtime, cambia solo il nome del modello che chiedo a WhisperModel. Su Debian 13 la base è quella di sempre.


sudo apt install -y ffmpeg python3-venv
python3 -m venv ~/.venvs/whisper
source ~/.venvs/whisper/bin/activate
pip install --upgrade pip faster-whisper

Lo script che mi tengo per i casi tosti ha qualche parametro in più rispetto al fratello minore. Aumento beam_size per dare al decoder margine di esplorazione, e attivo condition_on_previous_text=False per evitare che un errore si propaghi sui segmenti successivi (è il classico effetto “loop di hallucination” che Whisper fa quando il VAD non aiuta).


from faster_whisper import WhisperModel

model = WhisperModel(
    "medium",
    device="cpu",
    compute_type="int8",
    cpu_threads=8,
    num_workers=1,
)

segments, info = model.transcribe(
    "webinar-rumoroso.mp4",
    language="it",
    beam_size=8,
    best_of=5,
    vad_filter=True,
    vad_parameters=dict(min_silence_duration_ms=500),
    condition_on_previous_text=False,
)

with open("webinar-rumoroso.txt", "w") as f:
    for seg in segments:
        f.write(f"[{seg.start:7.2f}] {seg.text.strip()}\n")

Solita regola: cpu_threads=8. Sul portatile a 12 core posso lasciare lavorare il modello in background mantenendo la macchina reattiva per quello che faccio in primo piano. Saturare tutti i thread mi ha già regalato due episodi di thermal throttling, e da allora la regola è ferma.

Per file molto lunghi conviene spezzare a monte con ffmpeg in chunk di 10-15 minuti: il decoder lavora meglio su segmenti gestibili, e in caso di crash si recupera solo la parte interrotta.


ffmpeg -i webinar-rumoroso.mp4 -f segment -segment_time 900 -c copy chunk-%03d.mp4

Un esempio reale

Due settimane fa ho recuperato un webinar tecnico di 70 minuti su osservabilità di cluster Kubernetes. Il relatore aveva fatto la sessione dal salotto di casa, microfono integrato del laptop, riverbero della stanza, condizionatore acceso in sottofondo, e per giunta i 12 minuti centrali con audio mono sbilanciato sul canale destro perché aveva mosso le cuffie. Una bestia.

Il small su quel file mi aveva prodotto una trascrizione piena di frasi inventate, intere sezioni dove Whisper si era impuntato a ripetere la stessa parola per dieci secondi e poi era ripartito a caso. Il medium con i parametri sopra ci ha messo circa 40 minuti di elaborazione, ma il risultato era usabile. Ho dovuto correggere a mano qualche nome di tool (Loki diventava ogni tanto “loki” lowercase, OpenTelemetry diventato “open telemetri” per qualche secondo) e una manciata di sigle, ma le frasi erano frasi vere, i timestamp tornavano, e ho potuto estrarre con grep tutti i passaggi dove il relatore parlava di cardinality limits sui label di Prometheus che era esattamente quello che mi serviva per gli appunti.

Cosa fa bene

Audio sporco, voci sovrapposte, riverbero, microfoni mediocri: il salto qualitativo rispetto al small è netto. Gestisce meglio le interruzioni e le ripartenze di frase. Sui termini tecnici inglesi pronunciati con accento italiano ha più tenuta. Quando il relatore cambia tono o velocità improvvisamente, il decoder si adatta senza perdere i pezzi.

Cosa fa meno bene

Tempi di elaborazione che diventano scomodi: su CPU a 8 thread, su una macchina di qualche anno, conta più o meno 30-40 minuti di calcolo per ogni ora di audio. Se non è la macchina su cui lavoro in primo piano va benissimo; se è il portatile su cui devo continuare a scrivere durante la giornata, lo lancio la sera. Sui nomi propri continua a inventare quando l’audio è davvero brutto, ma è limite intrinseco del modello, non un suo bug.

Privacy – vantaggio del modello locale

Stessa storia del fratello small, e qui ci tengo ancora di più: per principio l’audio che do in pasto al medium non passa mai per un’API esterna. Sul mio host parte la trascrizione, finita la trascrizione il file resta sull’host, fine.

Cache pesi in ~/.cache/huggingface/hub/models--Systran--faster-whisper-medium/, eliminabile con rm -rf quando voglio fare spazio. Confronto con l’API Whisper di OpenAI: il file audio andrebbe caricato, e la policy di retention dipende dal piano contrattuale; qui non c’è nessun caricamento e nessun contratto.

Licenze: il modello Whisper è MIT (OpenAI), faster-whisper come libreria è MIT, runtime CTranslate2 MIT. Stack permissivo, riutilizzabile dentro qualsiasi tooling interno senza vincoli copyleft.

In pratica

Il medium è la mia rete di sicurezza per i file che il small non riesce a domare. Lo tengo nel virtualenv accanto al small, lo invoco cambiando solo una stringa nel codice, lo lascio macinare la sera. Per i webinar tecnici brutti e rumorosi è quello che fa la differenza fra una trascrizione inutilizzabile e una bozza da cui posso ricostruire i contenuti senza riascoltare nulla.


Immagine generata con ComfyUI Mac M1 / RealVisXL V5 Lightning.