Come posso rimuovere più segmenti da un video usando FFmpeg?


46

Sto cercando di eliminare alcune sezioni di un video usando FFmpeg.

Ad esempio, immagina di aver registrato uno spettacolo in televisione e di voler tagliare gli spot pubblicitari. Questo è semplice con un editor video GUI; è sufficiente contrassegnare l'inizio e la fine di ogni clip da rimuovere e selezionare Elimina. Sto cercando di fare la stessa cosa dalla riga di comando con FFmpeg.

So come tagliare un singolo segmento in un nuovo video in questo modo:

ffmpeg -i input.avi -ss 00:00:20 -t 00:00:05 -map 0 -codec copy output.avi

Ciò taglia una clip di cinque secondi e la salva come nuovo file video, ma come posso fare il contrario e salvare l'intero video senza la clip specificata e come posso specificare più clip da rimuovere?

Ad esempio, se il mio video potesse essere rappresentato da ABCDEFG, vorrei crearne uno nuovo che consistesse in ACDFG.



1
Non è quello che sto chiedendo, vorrei ottenere tutto in un "episodio", ma questo avrebbe sezioni diverse dal video originale.
Matias,

@Matias, se ti chiedessi come tagliare alcune clip dal video e lasciare il resto così com'è, sarebbe una cosa, ma vuoi prendere alcune clip da esso e combinarle con clip di altri video che rendono questa non è una domanda separata, unica. Devi fare quello che fanno le altre domande per ottenere i segmenti separati, quindi combinarli.
Synetech,

1
@Synetech grazie per le tue risposte. Non voglio combinarli con clip di altri video. Voglio solo rimuovere alcune parti dai video. Ad esempio, se il mio video potesse essere rappresentato da ABCDEFG, vorrei crearne uno nuovo che consistesse in ACDFG.
Matias,

@Synetech Non ero io, era Tog che doveva aver frainteso.
Matias,

Risposte:


45

Bene, puoi ancora usare il trimfiltro per quello. Ecco un esempio, supponiamo che tu voglia tagliare i segmenti 30-40 sec (10 sec) e 50-80 sec (30 sec):

ffmpeg -i in.ts -filter_complex \
"[0:v]trim=duration=30[a]; \
 [0:v]trim=start=40:end=50,setpts=PTS-STARTPTS[b]; \
 [a][b]concat[c]; \
 [0:v]trim=start=80,setpts=PTS-STARTPTS[d]; \
 [c][d]concat[out1]" -map [out1] out.ts

Cosa ho fatto qui? Ho tagliato i primi 30 secondi, 40-50 secondi e 80 secondi fino alla fine, quindi li ho combinati in streaming out1con il concatfiltro.

Informazioni sui setpt: ne abbiamo bisogno perché il trim non modifica il tempo di visualizzazione delle immagini e quando tagliamo il contatore decodificatore di 10 secondi non viene visualizzato alcun frame per questi 10 secondi.

Se vuoi avere anche l'audio, devi fare lo stesso per i flussi audio. Quindi il comando dovrebbe essere:

ffmpeg -i utv.ts -filter_complex \
"[0:v]trim=duration=30[av];[0:a]atrim=duration=30[aa];\
 [0:v]trim=start=40:end=50,setpts=PTS-STARTPTS[bv];\
 [0:a]atrim=start=40:end=50,asetpts=PTS-STARTPTS[ba];\
 [av][bv]concat[cv];[aa][ba]concat=v=0:a=1[ca];\
 [0:v]trim=start=80,setpts=PTS-STARTPTS[dv];\
 [0:a]atrim=start=80,asetpts=PTS-STARTPTS[da];\
 [cv][dv]concat[outv];[ca][da]concat=v=0:a=1[outa]" -map [outv] -map [outa] out.ts

Grande! Perché abbiamo bisogno di setpt? Potresti aggiungere la spiegazione?
Rajib,

Ok, vedi la risposta aggiornata.
ptQa

4
+1 Questo dovrebbe davvero essere selezionato come risposta. È più adatto per "più segmenti" e rimuove la necessità di eseguire più comandi uno dopo l'altro. (a meno che un altro modo non sia più efficiente in termini di velocità)
Knossos,

1
Come gestiresti i dati dei sottotitoli quando salti i frame?
Andrew Scagnelli,

1
Questo è molto disumano.
Golopot

13

Non riesco mai a far funzionare la soluzione di ptQa, soprattutto perché non riesco mai a capire cosa significano gli errori dai filtri o come risolverli. La mia soluzione sembra un po 'più scomoda perché può lasciare un disastro, ma se lo si lancia in uno script, la pulizia può essere automatizzata. Mi piace anche questo approccio perché se qualcosa va storto nel passaggio 4, si finisce con i passaggi 1-3 completati, quindi il recupero dagli errori è un po 'più efficiente.

La strategia di base sta utilizzando -te -ssper ottenere video di ogni segmento desiderato, quindi unire tutte le parti per la versione finale.

Supponi di avere 6 segmenti ABCDEF ogni 5 secondi e desideri A (0-5 secondi), C (10-15 secondi) ed E (20-25 secondi).

ffmpeg -i abcdef.tvshow -t 5 a.tvshow -ss 10 -t 5 c.tvshow -ss 20 -t 5 e.tvshow

o

ffmpeg -i abcdef.tvshow -t 0:00:05 a.tvshow -ss 0:00:10 -t 0:00:05 c.tvshow -ss 0:00:20 -t 0:00:05 e.tvshow

Ciò renderà i file a.tvshow, c.tvshow ed e.tvshow. La -tdice quanto tempo ogni clip è, quindi, se c è lungo 30 secondi si poteva passare a 30 o 0:00:30. L' -ssopzione indica fino a che punto saltare il video di origine, quindi è sempre relativo all'inizio del file.

Quindi una volta che hai un sacco di file video, creo un file ace-files.txtcome questo:

file 'a.tvshow'
file 'c.tvshow'
file 'e.tvshow'

Nota il "file" all'inizio e il nome del file di escape successivo.

Quindi il comando:

ffmpeg -f concat -i ace-files.txt -c copy ace.tvshow

Ciò concede tutti i file abe-files.txtinsieme, copiando i loro codec audio e video e crea un file ace.tvshowche dovrebbe essere solo le sezioni a, c ed e. Poi basta ricordarsi di eliminare ace-files.txt, a.tvshow, c.tvshowe e.tvshow.

Disclaimer : non ho idea di quanto sia (in) efficiente rispetto agli altri approcci in termini di, ffmpegma per i miei scopi funziona meglio. Spero che aiuti qualcuno.


3
questo è molto comprensibile per i neofiti come me, grazie
juanpastas,

2
Nota se stai usando bash, puoi evitare la creazione di un file ( ace-files.txtnel tuo esempio) con:ffmpeg -f concat -i <(printf "file '%s'\n" $(pwd)/prefix_*.tvshow) -c copy output.tvshow
bufh

Questo è stato davvero utile, ma ho dovuto rimuovere le virgolette singole dai nomi dei file o non ha funzionato.
Per il

Quando si concatenano i video, c'è un breve video nero e un audio silenziato tra loro, circa 25 ms. Qualche idea per sbarazzarsene?
Antonio Bonifati 'Farmboy',

Hmm ... Verificherei la tua matematica sugli offset e sui salti di tempo, e mi assicurerei di non lasciare un gap di 25ms ... Non l'ho provato.
xbakesx,

3

Ho realizzato una sceneggiatura per velocizzare la modifica della TV registrata. Lo script ti chiede l'ora di inizio e di fine dei segmenti che vuoi conservare e li divide in file. Ti offre opzioni, puoi:

  • Prendi uno o più segmenti.
  • È possibile combinare i segmenti in un file risultante.
  • Dopo l'adesione è possibile conservare o eliminare i file delle parti.
  • È possibile conservare il file originale o sostituirlo con il nuovo file.

Video in azione: qui

Fatemi sapere cosa ne pensate.

 #!/bin/bash
/bin/date >>segmenter.log

function segment (){
while true; do
    echo "Would you like to cut out a segment ?"
    echo -e "1) Yes\n2) No\n3) Quit"
    read CHOICE
    if [ "$CHOICE" == "3" ]; then
        exit
    elif [ "$CHOICE" == "2" ]; then
        clear
        break
    elif [ "$CHOICE" == "1" ]; then
        clear
        ((segments++))
        echo "What time does segment $segments start ?"
        read SEGSTART
        clear
        echo -e "Segment $segments start set to $SEGSTART\n"  
        echo "What time does segment $segments end ?"
        read SEGEND
        clear
        echo -e "Segment $segments end set to $SEGEND\n"
        break
    else
        clear
        echo -e "Bad option"
        segment "$segments"
    fi
done
if [ "$CHOICE" == "1" ]; then
    echo "Cutting file $file video segment $segments starting at $SEGSTART and ending at $SEGEND"
    ffmpeg -i "$file" -ss $SEGSTART -to  $SEGEND -map 0:0 -map 0:1 -c:a copy -c:v copy  "$filename-part$segments.$extension"  >> segmenter.log 2>&1
    clear
    echo -e "Cut file $filename-part$segments.$extension starting at $SEGSTART and ending at $SEGEND\n"                             
    segment "$segments"
fi
}

file="$1"
filename="${file%.*}"
extension="${file##*.}"
clear
segments=0
segment "$segments"
clear
if (("$segments"==1)); then
mv $filename"-part1."$extension "$filename-segmented.$extension"
elif (("$segments">1)); then
echo "Would you like to join the segments into one file ?"      
       OPTIONS="Yes No Quit"
       select opt in $OPTIONS; do
       clear
        if [ "$opt" == "Quit" ]; then
            exit
        elif [ "$opt" == "Yes" ]; then
            clear
            echo "Joining segments"
            ffmpeg -f concat -i <(for f in $filename"-part"*$extension;         do echo "file '$(pwd)/$f'"; done) -c:a copy -c:v copy "$filename-segmented.$extension" >>         segmenter.log 2>&1
            clear
            echo "Would you like to delete the part files ?"
            select opt in $OPTIONS; do
            clear
            if [ "$opt" == "Quit" ]; then
                exit
            elif [ "$opt" == "Yes" ]; then
                for f in $filename"-part"*$extension; do rm $f; done
                break
            elif [ "$opt" == "No" ]; then
                break
            else
                clear
                echo -e "Bad option\n"
            fi
            done
            break
        clear
        elif [ "$opt" == "No" ]; then
            exit
        else
            clear
            echo -e "Bad option\n"
        fi
    done
fi
echo "Would you like to replace the original file with the result of your changes ?"
OPTIONS="Yes No Quit"
select opt in $OPTIONS; do
    clear
    if [ "$opt" == "Quit" ]; then
        exit
    elif [ "$opt" == "Yes" ]; then
        rm $file
        mv "$filename-segmented.$extension" $file
        break
    elif [ "$opt" == "No" ]; then
        break
    else
        clear
        echo -e "Bad option\n"
    fi
done

2

Anche se la risposta fornita da ptQa sembra funzionare, ho sviluppato un'altra soluzione che ha funzionato bene.

In sostanza, quello che faccio è tagliare un video per ogni parte del video originale che voglio includere nel mio risultato. Più tardi, li concateno con il Dematro Concat spiegato qui .

La soluzione è la stessa che ho provato prima e ho presentato problemi di sincronizzazione. Quello che ho aggiunto è il comando -avoid_negative_ts 1 quando si generano i diversi video. Con questa soluzione, i problemi di sincronizzazione scompaiono.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.