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.
- Cosa fa e cosa non fa
- Flusso logico
- Installazione
- Utilizzo
- Flag disponibili
- Struttura del progetto
- Come i moduli dipendono l'uno dall'altro
- Documentazione di ogni modulo
- Silero VAD — come funziona il rilevamento vocale
- I tagli con ffmpeg — filter_complex
- Qualità video e audio
- Come estendere il software
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
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
Repository GitHub: https://github.com/AmosLoVerde/autospeechcut
Servono tre cose: Python 3.10+, ffmpeg, e le librerie Python.
1. Installa Python 3.10+
Se non è già presente, scaricalo da https://python.org oppure tramite Homebrew:
brew install python2. Installa ffmpeg
brew install ffmpegHomebrew installa automaticamente anche ffprobe, incluso nel pacchetto.
3. Clona il repository
git clone https://github.com/AmosLoVerde/autospeechcut.git
cd autospeechcut4. Crea un ambiente virtuale e installa le dipendenze
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txtSe hai un Mac con Apple Silicon (M1/M2/M3/M4), PyTorch sfrutterà automaticamente il chip tramite MPS. Nessuna configurazione aggiuntiva.
1. Installa Python 3.10+
Su Ubuntu 22.04 e versioni successive Python 3.10+ è già incluso. Per verificare:
python3 --versionSe la versione è precedente alla 3.10:
sudo apt update && sudo apt install python3.10 python3.10-venv python3-pip2. Installa ffmpeg
sudo apt update && sudo apt install ffmpeg3. Clona il repository
git clone https://github.com/AmosLoVerde/autospeechcut.git
cd autospeechcut4. Crea un ambiente virtuale e installa le dipendenze
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txtSe 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.txtper le librerie restanti.
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 --version2. Installa ffmpeg
Il modo più semplice è tramite winget:
winget install ffmpegIn alternativa, tramite Chocolatey (se installato):
choco install ffmpegOppure 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 -version3. Clona il repository
git clone https://github.com/AmosLoVerde/autospeechcut.git
cd autospeechcut4. Crea un ambiente virtuale e installa le dipendenze
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txtSu Windows, se
pythonnon viene riconosciuto prova conpy -3. Se PowerShell blocca l'attivazione del venv, esegui prima:Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser.
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
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.
# 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 -hPrima 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| Flag | Descrizione |
|---|---|
file.mp4 |
Percorso diretto al file MP4 da elaborare |
-d, --dir <cartella> |
Elabora tutti gli MP4 in una cartella (batch) |
| 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
| 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. |
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. |
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.
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 risultatifmt_duration(93.5) -> "1m 33.5s", usata per ETA e tempi di elaborazionetimecode_to_sec("01:02:05.400") -> 3725.4, usata inffmpeg_engine.pyper leggere il progresso da ffmpeg
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,
}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 |
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.mp4e ritorna:
{
"duration": float, # durata totale in secondi
"video_stream": dict, # codec, risoluzione, fps...
"audio_stream": dict | None # None se il file non ha audio
}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.
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.
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.
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à:
h264_videotoolbox— Mac (Apple Silicon o Intel)h264_nvenc— GPU NVIDIAh264_qsv— Intel Quick Synch264_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.
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.
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 TrueRitorna True se il file è stato elaborato, False se è stato saltato per qualsiasi motivo (dry-run, nessun taglio necessario, annullato dall'utente).
L'entry point. In ordine:
- Stampa il banner con
print_header() - Parsa gli argomenti con
parse_args() - Costruisce la lista dei file da elaborare, in batch esclude automaticamente i
*_cut.mp4già presenti per non ri-elaborare output precedenti - Chiama
check_dependencies()una sola volta - Se
--hw-accel, chiamadetect_hw_encoder()una sola volta - Cicla sui file chiamando
process_single()per ognuno - 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.
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.
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.
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.
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 |
AAC a 320 kbps e 48kHz. 320 kbps AAC è il punto di trasparenza percettiva. 48kHz è il sample rate standard del mondo broadcast e professionale.
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 resultIn 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.
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"))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.
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"]