Skip to content

Repository files navigation

AutoSpeechCut

Rimozione automatica di silenzi e rumori da video MP4 tramite Deep Learning (Silero VAD) + ffmpeg. Qualità video sempre preservata al 100%. Supporto batch, GPU e help interattivo.


Indice

  1. Cosa fa e cosa non fa
  2. Flusso logico
  3. Installazione
  4. Utilizzo
  5. Flag disponibili
  6. Struttura del progetto
  7. Come i moduli dipendono l'uno dall'altro
  8. Documentazione di ogni modulo
  9. Silero VAD — come funziona il rilevamento vocale
  10. I tagli con ffmpeg — filter_complex
  11. Qualità video e audio
  12. Come estendere il software

1. Cosa fa e cosa non fa

AutoSpeechCut prende un file MP4, usa una rete neurale (Silero VAD) per capire dove c'è voce umana, e applica tagli ffmpeg per rimuovere tutto il resto.

La qualità del video nei segmenti conservati è identica all'originale: nessun filtro, nessuna elaborazione del segnale, solo tagli.

Cosa rimuove:

  • Silenzi lunghi tra una frase e l'altra
  • Rumori di fondo senza parlato (ambiente)
  • Qualsiasi intervallo dove non c'è voce umana

Cosa non fa:

  • Non filtra o riduce rumori nel segnale audio — non è un de-noiser
  • Non trascrive e non sottotitola
  • Non cambia risoluzione, frame rate o codec

2. Flusso logico

Esegui python3 main.py video.mp4

[1] Metadati video
    ffprobe legge durata, codec video, codec audio, risoluzione, fps

[2] Estrazione audio
    ffmpeg estrae la traccia audio come WAV mono a 16kHz
    (file temporaneo, eliminato automaticamente al termine)

[3] Voice Activity Detection
    Silero VAD analizza il WAV a finestre di ~32ms ciascuna
    e assegna a ognuna la probabilità che contenga voce umana
    -> output: lista di intervalli {start, end} dove c'è parlato

[4] Calcolo dei segmenti da mantenere
    Dai timestamp VAD si costruisce la lista dei segmenti da tenere:
    * si aggiunge un margine (±0.4s) attorno a ogni segmento parlato
    * si uniscono i segmenti vicini tra loro
    * si tagliano solo i gap >= min-silence (default: 2s)
    -> output: lista (start, end) dei segmenti da mantenere

[5] Applicazione dei tagli
    ffmpeg riceve un filter_complex con trim/atrim per ogni segmento
    e concat per unirli in sequenza in un unico file
    -> output: video_cut.mp4 nella stessa cartella dell'originale

Schema visivo:

video.mp4
    │
    ├─[ffprobe]──────────────────► metadati (durata, codec, fps)
    │
    ├─[ffmpeg]───────────────────► audio_16k.wav  (temporaneo)
    │                                   │
    │                              [Silero VAD]
    │                                   │
    │                         speech_timestamps[]
    │                                   │
    │                       [compute_keep_segments]
    │                                   │
    │                          keep_segments[]
    │                                   │
    └─[ffmpeg filter_complex]───────────┘
              │
              ▼
         video_cut.mp4

3. Installazione

Repository GitHub: https://github.com/AmosLoVerde/autospeechcut

Servono tre cose: Python 3.10+, ffmpeg, e le librerie Python.


macOS

1. Installa Python 3.10+

Se non è già presente, scaricalo da https://python.org oppure tramite Homebrew:

brew install python

2. Installa ffmpeg

brew install ffmpeg

Homebrew installa automaticamente anche ffprobe, incluso nel pacchetto.

3. Clona il repository

git clone https://github.com/AmosLoVerde/autospeechcut.git
cd autospeechcut

4. Crea un ambiente virtuale e installa le dipendenze

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

Se hai un Mac con Apple Silicon (M1/M2/M3/M4), PyTorch sfrutterà automaticamente il chip tramite MPS. Nessuna configurazione aggiuntiva.


Linux (Ubuntu / Debian e derivate)

1. Installa Python 3.10+

Su Ubuntu 22.04 e versioni successive Python 3.10+ è già incluso. Per verificare:

python3 --version

Se la versione è precedente alla 3.10:

sudo apt update && sudo apt install python3.10 python3.10-venv python3-pip

2. Installa ffmpeg

sudo apt update && sudo apt install ffmpeg

3. Clona il repository

git clone https://github.com/AmosLoVerde/autospeechcut.git
cd autospeechcut

4. Crea un ambiente virtuale e installa le dipendenze

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

Se hai una GPU NVIDIA e vuoi usare CUDA, prima installa le dipendenze PyTorch con il comando corretto per il tuo sistema da https://pytorch.org, poi esegui pip install -r requirements.txt per le librerie restanti.


Windows

1. Installa Python 3.10+

Scaricalo da https://python.org/downloads. Durante l'installazione, spunta la casella "Add Python to PATH".

Per verificare da terminale (PowerShell o Prompt dei comandi):

python --version

2. Installa ffmpeg

Il modo più semplice è tramite winget:

winget install ffmpeg

In alternativa, tramite Chocolatey (se installato):

choco install ffmpeg

Oppure manualmente: scarica il pacchetto da https://ffmpeg.org/download.html, estrai lo zip e aggiungi la cartella bin alla variabile d'ambiente PATH.

Dopo l'installazione, verifica aprendo un nuovo terminale:

ffmpeg -version

3. Clona il repository

git clone https://github.com/AmosLoVerde/autospeechcut.git
cd autospeechcut

4. Crea un ambiente virtuale e installa le dipendenze

python -m venv venv
venv\Scripts\activate
pip install -r requirements.txt

Su Windows, se python non viene riconosciuto prova con py -3. Se PowerShell blocca l'attivazione del venv, esegui prima: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser.


Struttura del progetto dopo il clone

autospeechcut/
├── main.py               <- file da eseguire
├── requirements.txt      <- dipendenze Python
├── utils.py
├── help.py
├── cli.py
├── checks.py
├── audio.py
├── segments.py
├── report.py
├── hardware.py
├── ffmpeg_engine.py
└── pipeline.py

Verifica automatica delle dipendenze

Ogni volta che lo script viene avviato, checks.py verifica in automatico che tutte le dipendenze siano presenti. Se manca qualcosa, lo script si ferma e mostra esattamente cosa installare:

  ✗ Dipendenze mancanti — impossibile continuare

  Librerie Python mancanti:
      • torch
      • soundfile

  Per installarle tutte in una volta:
      pip install -r requirements.txt

Non serve controllare manualmente: se qualcosa non va, lo script lo dice subito.


4. Utilizzo

# File singolo — chiede conferma prima di applicare i tagli
python3 main.py video.mp4

# File singolo senza conferma interattiva
python3 main.py video.mp4 --no-confirm

# Mostra i tagli calcolati senza modificare nulla
python3 main.py video.mp4 --dry-run

# Tutti gli MP4 in una cartella (batch)
python3 main.py -d ./registrazioni

# Batch automatico con accelerazione GPU
python3 main.py -d ./registrazioni --hw-accel --no-confirm

# Help generale
python3 main.py -h

# Help dettagliato su una flag specifica
python3 main.py --threshold -h
python3 main.py --min-silence -h

Workflow consigliato per la prima volta

Prima di applicare qualsiasi taglio, conviene sempre fare un dry-run per vedere cosa verrebbe rimosso.

# 1. Analisi senza modifiche — zero rischi
python3 main.py video.mp4 --dry-run

# 2. Se taglia troppo, aumenta min-silence
python3 main.py video.mp4 --min-silence 3.0 --dry-run

# 3. Se taglia poco, abbassa min-silence o alza threshold
python3 main.py video.mp4 --threshold 0.55 --min-silence 1.5 --dry-run

# 4. Quando i tagli calcolati sembrano corretti, applica
python3 main.py video.mp4 --threshold 0.55 --min-silence 1.5 --no-confirm

5. Flag disponibili

Sorgente — obbligatoria, una delle due

Flag Descrizione
file.mp4 Percorso diretto al file MP4 da elaborare
-d, --dir <cartella> Elabora tutti gli MP4 in una cartella (batch)

Parametri del VAD

Flag Default Tipo Descrizione
--threshold 0.45 float 0.0–1.0 Soglia di confidenza per considerare un segmento come parlato. Sopra = voce, sotto = non-parlato da tagliare.
--min-silence 2.0 secondi Durata minima di un gap senza parlato per tagliarlo. I gap più brevi vengono conservati come pause naturali.
--margin 0.4 secondi Cuscinetto di silenzio lasciato ai bordi di ogni segmento parlato, per evitare tagli bruschi.
--min-speech 200 ms Durata minima di un segmento parlato perché venga mantenuto. Filtra i falsi positivi brevissimi.

Guida pratica a --threshold:

0.30  ->  permissivo  [include anche voci basse, microfoni lontani]
0.45  ->  default     [bilancia bene la maggior parte delle registrazioni]
0.60  ->  selettivo   [solo parlato netto e chiaro]
0.75  ->  rigido      [solo voce forte e vicina al microfono]

Se nel risultato finale mancano parole o frasi, allora il threshold è troppo alto. Se rimangono respiri o rumori tra le frasi, allora è troppo basso.

Guida pratica a --min-silence:

1.0s  ->  tutorial, screencast, ritmo serrato
2.0s  ->  default, parlato normale
3.5s  ->  interviste, chi parla lentamente con pause
6.0s  ->  intervento minimo, solo silenzi molto lunghi

Funzionalità e controllo

Flag Descrizione
--hw-accel Usa la GPU per il VAD (MPS su Mac, CUDA su NVIDIA) e un encoder hardware per ffmpeg. Nessuna perdita di qualità, solo più velocità.
--no-confirm Non chiede "Procedere? [S/n]", applica i tagli subito dopo l'analisi.
--dry-run Esegue l'analisi completa e mostra i tagli calcolati, senza creare nessun file.
-h Help generale. Aggiunto dopo una flag specifica (es. --threshold -h) mostra la pagina dettagliata di quella flag.

6. Struttura del progetto

Ogni file ha una responsabilità unica e non si sovrappone agli altri.

File Cosa contiene
main.py Entry point. Gestisce la lista dei file, le dipendenze, l'hardware e il ciclo di elaborazione.
utils.py Fondamenta condivise: colori ANSI, funzioni di stampa, formattazione del tempo. Non importa nessun altro modulo interno.
help.py Tutte le pagine help e il meccanismo che intercetta -h prima di argparse.
cli.py Parsing degli argomenti da riga di comando.
checks.py Verifica che librerie e ffmpeg siano installati. Legge i metadati del video con ffprobe.
audio.py Estrazione del WAV dal video, caricamento come tensore PyTorch, inferenza Silero VAD.
segments.py Calcolo dei segmenti da mantenere a partire dai timestamp VAD.
report.py Stampa i box con il riepilogo dei tagli e il risultato finale.
hardware.py Rileva e testa gli encoder hardware disponibili su ffmpeg.
ffmpeg_engine.py Costruisce il filter_complex e gestisce l'esecuzione di ffmpeg con la barra di avanzamento.
pipeline.py Orchestratore: chiama tutti i moduli nell'ordine corretto per elaborare un file singolo.

7. Come i moduli dipendono l'uno dall'altro

                      main.py
                    /    |    \
              cli.py  checks  hardware
                |      .py      .py
            help.py
                |
            utils.py  <-── tutti importano da qui
                               ▲
            pipeline.py ───────┤
           /  |   |   |   \    │
       audio seg. rep. ffmpeg  │
        .py  .py  .py  _engine │

La regola fondamentale è che nessun modulo importa quello che lo usa. utils.py non conosce nessuno degli altri. pipeline.py importa quasi tutto, ma nessuno importa pipeline. main.py è l'unico che conosce tutto.

Questo garantisce che modificare un modulo non rischi di rompere moduli che non gli fanno riferimento. Se cambi segments.py, audio.py e report.py non sono toccati.


8. Documentazione di ogni modulo


utils.py

La base da cui tutti gli altri moduli importano. Se si cambia qualcosa qui, allora il cambiamento ha effetto ovunque -> è il file che va toccato meno.

class C contiene le costanti dei codici ANSI per i colori del terminale. Sono sequenze di escape come "\033[92m" che il terminale interpreta come comandi di colore; non contengono caratteri visibili.

col(color, text) -> str avvolge text con il codice colore e aggiunge C.RESET alla fine, in modo che il colore non si propaghi al testo successivo.

print_header() -> None stampa il banner CYAN all'avvio. Va chiamata una sola volta da main(), le funzioni help non la richiamano.

Funzioni print_*, tutte le stampe stilizzate usate nel resto del codice:

Funzione Colore Produce
print_step(n, total, msg) BLUE [2/5] Estrazione audio
print_info(msg) GRAY Dettagli tecnici indentati
print_ok(msg) GREEN ✓ messaggio
print_warn(msg) YELLOW ⚠ avviso
print_err(msg) RED ✗ errore su stderr

Funzioni di formattazione tempo:

  • fmt_time(3725.4) -> "01:02:05.400", usata nei box dei risultati
  • fmt_duration(93.5) -> "1m 33.5s", usata per ETA e tempi di elaborazione
  • timecode_to_sec("01:02:05.400") -> 3725.4, usata in ffmpeg_engine.py per leggere il progresso da ffmpeg

help.py

Contiene tutte le pagine help e il meccanismo che decide quale pagina mostrare.

Componenti grafici delle pagine help — quattro funzioni private che costruiscono gli elementi visivi:

_box(title, color)   ->  ╔════════════════════╗
                         ║      title         ║
                         ╚════════════════════╝

_sec(title)          ->  ▸ TITOLO SEZIONE

_flag_row(flag, meta, desc, default)  ->  --threshold   <0.0-1.0>   descrizione  [0.45]

_ex(cmd, note)       ->    $ python3 main.py video.mp4  # nota opzionale

La larghezza di _box() è w = 68 (66 ═ interni), identica al banner di print_header(). Tutte le box (banner e pagine help) hanno la stessa larghezza nel terminale. Se mai si modifica la larghezza del banner in utils.py, va aggiornato anche w in _box().

dispatch_help() -> None viene chiamata come prima cosa in parse_args(), prima che argparse esamini qualsiasi argomento. Legge sys.argv direttamente e decide:

sys.argv vuoto o solo -h   ->  help_general()    ->  sys.exit(0)
<flag_nota> -h             ->  help_<flag>()     ->  sys.exit(0)
flag sconosciuta + -h      ->  help_general()    ->  sys.exit(0)
nessun -h trovato          ->  ritorna (argparse prosegue normalmente)

_HELP_MAP è il dizionario che mappa ogni flag alla sua funzione help dedicata. Quando si aggiunge una nuova flag, va aggiunto anche qui:

_HELP_MAP = {
    "-d":            help_d,
    "--dir":         help_d,
    "--threshold":   help_threshold,
    "--min-silence": help_min_silence,
    "--margin":      help_margin,
    "--min-speech":  help_min_speech,
    "--hw-accel":    help_hw_accel,
    "--no-confirm":  help_no_confirm,
    "--dry-run":     help_dry_run,
}

cli.py

Una sola funzione pubblica: parse_args() -> argparse.Namespace.

Come prima cosa chiama dispatch_help(). Se c'è -h, il processo termina lì. Altrimenti, configura argparse con add_help=False (per non conflittare con il nostro -h personalizzato), definisce la sorgente come gruppo mutuamente esclusivo (input oppure -d), e aggiunge tutti i parametri.

argparse converte automaticamente i trattini in underscore: --min-silence diventa args.min_silence, --no-confirm diventa args.no_confirm, e così via.

Attributi del Namespace ritornato:

Attributo Tipo Default
args.input str | None None
args.dir str | None None
args.threshold float 0.45
args.min_silence float 2.0
args.margin float 0.4
args.min_speech int 200
args.hw_accel bool False
args.no_confirm bool False
args.dry_run bool False

checks.py

check_dependencies() -> None tenta un __import__() dinamico per torch, torchaudio, soundfile e numpy. Per ffmpeg e ffprobe esegue ffmpeg -version e controlla il returncode. Se manca qualcosa, stampa cosa installare e termina con sys.exit(1). Viene chiamata una sola volta all'avvio, in main().

get_video_info(video_path) -> dict esegue:

ffprobe -v quiet -print_format json -show_streams -show_format video.mp4

e ritorna:

{
    "duration":     float,        # durata totale in secondi
    "video_stream": dict,         # codec, risoluzione, fps...
    "audio_stream": dict | None   # None se il file non ha audio
}

audio.py

extract_audio_wav(video_path, output_wav, sr=16000) -> None

Esegue:

ffmpeg -y -i video.mp4 -vn -acodec pcm_s16le -ar 16000 -ac 1 audio_16k.wav
  • -vn -> scarta il video, serve solo l'audio
  • -acodec pcm_s16le -> WAV non compresso a 16-bit
  • -ar 16000 -> 16kHz è il sample rate su cui Silero VAD è stato addestrato
  • -ac 1 -> mono

Il file viene creato in una tempfile.TemporaryDirectory che pipeline.py elimina automaticamente al termine del blocco with.

load_audio(audio_path, target_sr=16000) -> torch.Tensor

Carica il WAV con soundfile invece di torchaudio.load. Il motivo: torchaudio >= 2.9 ha rimosso il vecchio backend I/O e richiederebbe torchcodec come dipendenza aggiuntiva. soundfile non ha questo problema e produce lo stesso risultato — un tensore float32 mono.

run_vad(audio_path, threshold, min_speech_ms, min_silence_ms, sr, device) -> (list[dict], float)

Carica il modello da torch.hub (prima esecuzione: scarica 30MB da GitHub, poi usa la cache in ~/.cache/torch/hub/). Chiama get_speech_timestamps() e ritorna la lista di {'start': float, 'end': float} in secondi più la durata totale dell'audio.


segments.py

compute_keep_segments(speech_timestamps, total_duration, margin, min_silence, min_segment_len) -> list[tuple[float, float]]

Trasforma i timestamp grezzi del parlato in segmenti da mantenere nel video. L'algoritmo ha 5 passi:

Passo 1 — Aggiungi i margini:

Parlato VAD:   |===|         |===|
Dopo margin:  |=====|       |=====|

Ogni segmento viene allargato di ±margin secondi per non tagliare troppo vicino alla voce.

Passo 2 — Unisci i sovrapposti: Se due segmenti si toccano o si sovrappongono dopo l'allargamento, vengono fusi in uno solo.

Passo 3 — Decidi quali gap tagliare:

gap >= min_silence  ->  taglia (è il silenzio/rumore da rimuovere)
gap <  min_silence  ->  conserva (è una pausa naturale tra frasi)

Passo 4 — Gestisci inizio e fine del video: Se il gap all'inizio è breve, si include dall'istante 0. Se il gap alla fine è breve, si estende fino alla fine.

Passo 5 — Scarta i segmenti troppo brevi: Segmenti più corti di min_segment_len (0.5s) vengono eliminati perché potrebbero causare artefatti nel concat di ffmpeg.


report.py

Gestisce la stampa dei box con i risultati. Il problema principale da risolvere è l'allineamento del bordo destro ║: con valori di lunghezza variabile (nomi file, MB, percentuali), un padding fisso farebbe slittare il bordo. _build_box_row() calcola il padding esatto a runtime:

content = f"  {label} : {value}"
padding = _BOX_WIDTH - len(content)   # spazi esatti per chiudere il box

_BOX_WIDTH = 64 è la larghezza interna (tra i due ║).

print_cut_report(keep_segments, total_duration) mostra durata originale, totale tagliato e durata finale stimata. Appare dopo l'analisi VAD in tutti i casi, anche in dry-run.

print_success_box(output_path, video_path, keep_segments, total_duration) mostra il box "✓ Completato" con nome file, MB originali e finali, percentuale rimossa. Appare solo dopo un'elaborazione reale.


hardware.py

detect_hw_encoder() -> dict | None

Cerca il miglior encoder hardware disponibile. Viene chiamata una sola volta in main() se --hw-accel è attivo. Il risultato viene passato a ogni chiamata di run_ffmpeg().

Ordine di priorità:

  1. h264_videotoolbox — Mac (Apple Silicon o Intel)
  2. h264_nvenc — GPU NVIDIA
  3. h264_qsv — Intel Quick Sync
  4. h264_amf — AMD

Per ogni candidato: verifica che il codec compaia nella lista degli encoder di ffmpeg, poi esegue un test reale (encode di 1 frame vuoto con quel codec). Il test reale è importante perché un codec può essere elencato ma non funzionare se i driver non sono aggiornati. Se nessun encoder HW supera il test, ritorna None e si usa libx264 su CPU.


ffmpeg_engine.py

build_filter_complex(keep_segments, has_audio) -> str

Genera la stringa da passare a ffmpeg come -filter_complex. Per 3 segmenti produce:

[0:v]trim=start=10.400000:end=45.200000,setpts=PTS-STARTPTS[v0];
[0:a]atrim=start=10.400000:end=45.200000,asetpts=PTS-STARTPTS[a0];
[0:v]trim=start=48.100000:end=123.500000,setpts=PTS-STARTPTS[v1];
[0:a]atrim=start=48.100000:end=123.500000,asetpts=PTS-STARTPTS[a1];
[v0][a0][v1][a1]concat=n=2:v=1:a=1[vout][aout]

Il setpts=PTS-STARTPTS (e asetpts per l'audio) è il dettaglio critico: ogni segmento tagliato mantiene i timestamp originali del file sorgente. Senza il reset, il concat vedrebbe dei "buchi" temporali tra un segmento e l'altro. Il reset porta ogni pezzo a partire da 0, e il concat li unisce in sequenza senza interruzioni.

run_ffmpeg(video_path, output_path, keep_segments, video_info, hw_accel) -> None

Costruisce e avvia il comando ffmpeg. Legge process.stderr riga per riga in tempo reale: quando trova una riga con time= e speed=, calcola percentuale e ETA e aggiorna la barra di avanzamento sovrascrivendo la riga corrente con \r.


pipeline.py

Il modulo orchestratore: non contiene logica propria, chiama i moduli nel giusto ordine.

process_single(video_path, args, hw_accel, torch_device, file_index, file_total) -> bool

video_info        = get_video_info(video_path)         # Step 1
extract_audio_wav(video_path, audio_wav)               # Step 2
speech_timestamps = run_vad(audio_wav, ...)            # Step 3
keep_segments     = compute_keep_segments(...)         # Step 4
print_cut_report(keep_segments, total_duration)
#   -> dry-run?         return False
#   -> nessun taglio?   return False
#   -> conferma utente  (se --no-confirm è False)
run_ffmpeg(video_path, output_path, ...)               # Step 5
print_success_box(...)
return True

Ritorna True se il file è stato elaborato, False se è stato saltato per qualsiasi motivo (dry-run, nessun taglio necessario, annullato dall'utente).


main.py

L'entry point. In ordine:

  1. Stampa il banner con print_header()
  2. Parsa gli argomenti con parse_args()
  3. Costruisce la lista dei file da elaborare, in batch esclude automaticamente i *_cut.mp4 già presenti per non ri-elaborare output precedenti
  4. Chiama check_dependencies() una sola volta
  5. Se --hw-accel, chiama detect_hw_encoder() una sola volta
  6. Cicla sui file chiamando process_single() per ognuno
  7. Stampa il riepilogo batch se i file sono più di uno

In modalità batch, se un file causa un errore (sys.exit(1) da qualche modulo interno), main() intercetta l'eccezione SystemExit, logga l'errore e continua con il file successivo. In modalità file singolo, l'errore viene rilanciato e lo script termina.


9. Silero VAD - come funziona il rilevamento vocale

I metodi tradizionali per rilevare il silenzio analizzano l'energia del segnale: se il volume è sotto una certa soglia, si considera silenzio. Funzionano in ambienti silenziosi, ma falliscono quando c'è rumore di fondo, perché il rumore ha energia ma non è voce.

Silero VAD è una rete neurale LSTM addestrata su milioni di campioni in molte lingue. Ha imparato a riconoscere la voce umana in quanto tale, indipendentemente dal volume e da cosa c'è intorno. Distingue voce da rumore anche quando il rumore è quasi alla stessa ampiezza della voce.

La rete analizza l'audio a finestre di 32ms (512 campioni a 16kHz) e per ognuna produce un numero tra 0.0 e 1.0, la probabilità che in quei 32ms ci sia voce umana. --threshold è la soglia di decisione: sopra quel valore il segmento viene considerato parlato, sotto no.

La prima volta che lo script gira, PyTorch scarica il modello da GitHub (30MB) e lo salva in ~/.cache/torch/hub/. Le esecuzioni successive usano la cache locale senza connessione internet.


10. I tagli con ffmpeg — filter_complex

filter_complex è il sistema di filtri di ffmpeg per applicare trasformazioni a video e audio in una singola passata. È la scelta giusta per questo caso perché garantisce sincronizzazione audio/video frame-accurate. Approcci alternativi (creare file separati per ogni segmento e poi concatenarli) introducono rischi di disincronizzazione ai punti di giuntura.

Il filtro che costruiamo dice a ffmpeg: "taglia questo segmento video e audio, poi questo, poi questo, e uniscili in sequenza". Il punto critico è il reset dei timestamp con setpts=PTS-STARTPTS. Ogni segmento tagliato ha timestamp che partono dal suo punto di inizio nell'originale. Se non vengono azzerati, il concat vedrebbe buchi temporali tra un segmento e l'altro. Il reset porta ogni segmento a partire da 0, così il concat può unirli senza interruzioni.

I timestamp sono scritti con 6 cifre decimali (10.400000), che corrisponde a una precisione al microsecondo, la massima supportata da ffmpeg.


11. Qualità video e audio

Video su CPU

Viene usato libx264 -crf 0. CRF 0 è il lossless matematico: ogni pixel del frame decodificato è identico all'originale. Il file risultante sarà più grande dell'originale (che era già compresso con perdita), ma la qualità è la massima possibile.

Video con GPU (--hw-accel)

Gli encoder hardware non supportano CRF 0, ma usano impostazioni equivalenti:

Encoder Parametri
Apple VideoToolbox -q:v 65 — qualità 65/100, visivamente identico al lossless
NVIDIA NVENC -cq 18 -preset p7 — p7 è il preset di massima qualità NVENC
Intel QuickSync -global_quality 18
AMD AMF -qp_i 18 -qp_p 18

Audio

AAC a 320 kbps e 48kHz. 320 kbps AAC è il punto di trasparenza percettiva. 48kHz è il sample rate standard del mondo broadcast e professionale.


12. Come estendere il software

Aggiungere una nuova flag — esempio pratico

Supponiamo di voler aggiungere --max-duration che limita la durata totale del video di output. Questi sono esattamente i file da toccare, nell'ordine:

1. cli.py — dichiara la flag:

parser.add_argument("--max-duration", type=float, default=None)

2. cli.py — aggiorna il commento di parse_args():

# max_duration -> float | None  (default None)

3. help.py — aggiungi la riga nella tabella di help_general():

print(_flag_row("--max-duration", "<secondi>", "Limita la durata totale dell'output", ""))

4. help.py — crea la funzione help dedicata:

def help_max_duration() -> None:
    print(); print()
    print(_box("FLAG  --max-duration  ·  Durata Massima Output", C.YELLOW))
    print(_sec("COS'È"))
    print("    Taglia il video di output alla durata specificata in secondi.")
    print(_sec("ESEMPI"))
    print(_ex(f"python3 {SCRIPT} video.mp4 --max-duration 600", "massimo 10 minuti"))
    print()

5. help.py — aggiungila a _HELP_MAP:

"--max-duration": help_max_duration,

6. help.py — aggiungila all'elenco di help contestuale in help_general():

for flag in [..., "--max-duration"]:

7. pipeline.py — usa il valore dopo compute_keep_segments():

if args.max_duration is not None:
    keep_segments = trim_to_max_duration(keep_segments, args.max_duration)

8. segments.py — implementa la funzione:

def trim_to_max_duration(
    keep_segments: list[tuple[float, float]],
    max_duration: float,
) -> list[tuple[float, float]]:
    """Tronca la lista di segmenti alla durata massima specificata."""
    result = []
    accumulated = 0.0
    for start, end in keep_segments:
        seg_dur = end - start
        if accumulated + seg_dur > max_duration:
            end = start + (max_duration - accumulated)
            result.append((start, end))
            break
        result.append((start, end))
        accumulated += seg_dur
    return result

Aggiungere un encoder hardware

In hardware.py, aggiungi un dizionario alla lista candidates. La posizione determina la priorità:

{
    "codec":       "nome_codec_ffmpeg",
    "quality_arg": ["-parametro", "valore"],
    "extra":       [],
    "label":       "Nome Leggibile (GPU)",
},

Il test reale viene eseguito automaticamente dal codice esistente.

Supportare altri formati video in batch

In main.py, la ricerca usa .glob("*.mp4"). Per aggiungere altri formati:

EXTENSIONS = ("*.mp4", "*.mov", "*.mkv", "*.avi")
all_files = []
for ext in EXTENSIONS:
    all_files.extend(dir_path.glob(ext))
mp4_files = sorted(f for f in all_files if not f.stem.endswith("_cut"))

Cambiare il modello VAD

In audio.py, il modello si carica con:

model, utils = torch.hub.load(
    repo_or_dir="snakers4/silero-vad",
    model="silero_vad",
    ...
)

Puoi sostituirlo con qualsiasi modello, purché il risultato finale sia una lista di {'start': float, 'end': float}, è il formato che compute_keep_segments() si aspetta.

Cambiare il codec di output

In ffmpeg_engine.py, nella sezione CPU di run_ffmpeg():

video_codec   = "libx265"   # HEVC invece di H.264
quality_flags = ["-crf", "0", "-x265-params", "lossless=1"]

About

CLI Python basata su AI per rimuovere automaticamente silenzi e segmenti senza parlato dai video, utilizzando Silero VAD e FFmpeg.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Contributors

Languages