Wie kann ich mit FFmpeg mehrere Segmente aus einem Video entfernen?

51

Ich versuche, einige Abschnitte eines Videos mit FFmpeg zu löschen.

Stellen Sie sich zum Beispiel vor, Sie hätten eine Sendung im Fernsehen aufgenommen und wollten die Werbespots ausschneiden. Dies ist mit einem GUI-Video-Editor einfach. Sie markieren einfach den Anfang und das Ende jedes zu entfernenden Clips und wählen Löschen. Ich versuche das gleiche über die Kommandozeile mit FFmpeg zu machen.

Ich weiß, wie man ein einzelnes Segment zu einem neuen Video schneidet :

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

Dadurch wird ein fünf Sekunden langer Clip geschnitten und als neue Videodatei gespeichert. Wie kann ich jedoch das Gegenteil tun und das gesamte Video ohne den angegebenen Clip speichern und wie kann ich angeben, dass mehrere Clips entfernt werden sollen?

Wenn mein Video beispielsweise durch ABCDEFG dargestellt werden könnte, würde ich gerne ein neues Video erstellen, das aus ACDFG besteht.

Matias
quelle
möglich Duplikat Splitting Video in mehreren Episoden mit ffmpeg , überprüft auch vielleicht aus FFMpeg Mit einem Video in 2 Minuten - Clips schneiden
Ƭᴇcʜιᴇ007
1
Das ist nicht das, wonach ich frage, ich möchte alles in einer "Episode" haben, aber dies hätte andere Abschnitte als das Originalvideo.
Matias
@Matias, wenn du fragst, wie man ein paar Clips aus dem Video ausschneidet und den Rest so lässt, wäre das eine Sache, aber du möchtest ein paar Clips daraus nehmen und sie mit Clips aus anderen Videos kombinieren, die das machen Dies ist keine separate, einzigartige Frage. Sie müssen die anderen Fragen beantworten, um die einzelnen Segmente zu erhalten, und sie dann kombinieren.
Synetech
1
@Synetech danke für deine Antworten. Ich möchte sie nicht mit Clips aus anderen Videos kombinieren. Ich möchte nur einige Teile aus den Videos entfernen. Wenn mein Video beispielsweise durch ABCDEFG dargestellt werden könnte, würde ich gerne ein neues Video erstellen, das aus ACDFG besteht.
Matias
@Synetech Das war nicht ich, es war Tog, der missverstanden haben muss.
Matias

Antworten:

47

Nun, Sie können immer noch den trimFilter dafür verwenden. Angenommen, Sie möchten die Segmente 30-40 Sek. (10 Sek.) Und 50-80 Sek. (30 Sek.) Ausschneiden:

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

Was habe ich hier gemacht? Ich habe die ersten 30 Sekunden, 40-50 Sekunden und 80 Sekunden bis zum Ende getrimmt und sie dann out1mit dem concatFilter zu einem Strom kombiniert .

Über Setpts: Wir brauchen dies, weil das Trimmen die Bildanzeigezeit nicht verändert, und wenn wir den 10-Sekunden-Decoderzähler ausschneiden, werden für diese 10 Sekunden keine Frames angezeigt.

Wenn Sie auch Audio haben möchten, müssen Sie dasselbe für Audio-Streams tun. Der Befehl sollte also lauten:

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
ptQa
quelle
Tolle Sache! Warum brauchen wir Setpts? Könnten Sie die Erklärung hinzufügen?
Rajib
Ok, siehe aktualisierte Antwort.
ptQa
4
+1 Dies sollte wirklich als Antwort gewählt werden. Es eignet sich eher für "mehrere Segmente" und macht die Ausführung mehrerer Befehle nacheinander überflüssig. (es sei denn, ein anderer Weg ist schneller)
Knossos
1
Wie würden Sie Untertiteldaten pflegen, wenn Sie Frames überspringen?
Andrew Scagnelli
1
Das ist sehr unmenschlich.
Golopot
15

Ich kann die Lösung von ptQa nie zum Laufen bringen, hauptsächlich, weil ich nie herausfinden kann, was die Fehler der Filter bedeuten oder wie sie behoben werden können. Meine Lösung scheint etwas umständlicher zu sein, da sie ein Durcheinander hinterlassen kann. Wenn Sie sie jedoch in ein Skript einschleusen, kann die Bereinigung automatisiert werden. Ich mag diesen Ansatz auch deshalb, weil, wenn in Schritt 4 etwas schief geht, die Schritte 1 bis 3 ausgeführt werden und die Wiederherstellung nach Fehlern etwas effizienter ist.

Die grundlegende Strategie verwendet -tund -ssVideos von jedem Segment Sie wollen, dann verbinden sich alle Teile für die endgültige Version zu bekommen.

Angenommen, Sie haben 6 Segmente ABCDEF, die alle 5 Sekunden lang sind, und Sie möchten A (0-5 Sekunden), C (10-15 Sekunden) und E (20-25 Sekunden).

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

oder

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

Damit werden die Dateien a.tvshow, c.tvshow und e.tvshow erstellt. Das -tsagt, wie lang jeder Clip ist. Wenn c 30 Sekunden lang ist, können Sie 30 oder 0:00:30 eingeben. Die -ssOption gibt an, wie weit das Quellvideo übersprungen werden soll, sodass es sich immer auf den Anfang der Datei bezieht.

Sobald Sie eine Reihe von Videodateien haben, erstelle ich eine Datei ace-files.txtwie die folgende:

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

Notieren Sie sich die "Datei" am Anfang und den Namen der zu maskierenden Datei.

Dann der Befehl:

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

Das fasst alle Dateien abe-files.txtzusammen, kopiert ihre Audio- und Videocodecs und erstellt eine Datei, ace.tvshowdie nur die Abschnitte a, c und e enthalten sollte. Dann erinnere mich nur zu löschen ace-files.txt, a.tvshow, c.tvshowund e.tvshow.

Haftungsausschluss : Ich habe keine Ahnung, wie (in) effizient dies im Vergleich zu den anderen Ansätzen ist, ffmpegaber für meine Zwecke funktioniert es besser. Hoffe es hilft jemandem.

xbakesx
quelle
4
Dieser ist sehr verständlich für Neulinge wie ich, danke
Juanpastas
4
Wenn Sie bash verwenden, können Sie die Erstellung einer Datei ( ace-files.txtin Ihrem Beispiel) vermeiden, indem Sie :ffmpeg -f concat -i <(printf "file '%s'\n" $(pwd)/prefix_*.tvshow) -c copy output.tvshow
bufh am
Dies war sehr hilfreich, aber ich musste die einfachen Anführungszeichen aus den Dateinamen entfernen, da dies sonst nicht funktionierte.
Tchaymore
Bei der Verkettung von Videos werden ein kurzes schwarzes Video und stummgeschaltetes Audio zwischen ihnen angezeigt (ca. 25 ms). Irgendwelche Ideen, um das loszuwerden?
Antonio Bonifati "Farmboy"
Hmm ... Ich würde nur Ihre Mathematik auf die Zeitverschiebungen und -sprünge überprüfen und sicherstellen, dass Sie keine Lücke von 25 ms hinterlassen ... das habe ich nicht erlebt.
xbakesx
5

Ich habe ein Skript erstellt, um die Bearbeitung von aufgezeichnetem Fernsehen zu beschleunigen. Das Skript fragt Sie nach den Anfangs- und Endzeiten der Segmente, die Sie behalten möchten, und teilt sie in Dateien auf. Sie haben folgende Möglichkeiten:

  • Nehmen Sie ein oder mehrere Segmente.
  • Sie können die Segmente in einer resultierenden Datei kombinieren.
  • Nach dem Beitritt können Sie die Teiledateien behalten oder löschen.
  • Sie können die Originaldatei behalten oder durch Ihre neue Datei ersetzen.

Video davon in Aktion: hier

Lass mich wissen was du denkst.

 #!/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
rocuinneagain
quelle
2
Dieses Skript ist großartig! Nur eine Änderung, da Sie absolute Pfade für die Verkettung verwenden: add safe=0, dh ffmpeg -f concat -safe 0 -i ...Siehe ffmpeg.org/ffmpeg-all.html#Options-36
pkSML
2

Obwohl die Antwort von ptQa zu funktionieren scheint, habe ich eine andere Lösung entwickelt, die bewiesen hat, dass sie gut funktioniert.

Im Wesentlichen schneide ich ein Video für jeden Teil des Originalvideos aus, den ich in mein Ergebnis aufnehmen möchte. Später habe ich verketten sie mit der Concat Demuxer erklärt hier .

Die Lösung ist die gleiche, die ich zuerst ausprobiert und Synchronisierungsprobleme vorgestellt habe. Was ich hinzugefügt habe, ist der Befehl -avoid_negative_ts 1 beim Generieren der verschiedenen Videos. Mit dieser Lösung verschwinden die Synchronisierungsprobleme.

Matias
quelle
1

Für diejenigen, die Probleme haben, dem Ansatz von ptQa zu folgen, gibt es einen etwas rationaleren Weg, dies zu tun. Anstatt jeden Schritt des Weges zu konzentrieren, mache sie einfach alle am Ende.

Definieren Sie für jeden Eingang ein A / V-Paar:

//Input1:
[0:v]trim=start=10:end=20,setpts=PTS-STARTPTS,format=yuv420p[0v];
[0:a]atrim=start=10:end=10,asetpts=PTS-STARTPTS[0a];
//Input2:
[0:v]trim=start=30:end=40,setpts=PTS-STARTPTS,format=yuv420p[1v];
[0:a]atrim=start=30:end=40,asetpts=PTS-STARTPTS[1a];
//Input3:
[0:v]trim=start=30:end=40,setpts=PTS-STARTPTS,format=yuv420p[2v];
[0:a]atrim=start=30:end=40,asetpts=PTS-STARTPTS[2a];

Definieren Sie so viele Paare, wie Sie benötigen, und fassen Sie sie dann in einem Durchgang zusammen, wobei n = Gesamtanzahl der Eingaben ist.

[0v][0a][1v][1a][2v][2a]concat=n=3:v=1:a=1[outv][outa] -map [outv] -map [outa] out.mp4

Dies kann leicht in einer Schleife aufgebaut werden.

Ein vollständiger Befehl, der zwei Eingaben verwendet, könnte folgendermaßen aussehen:

 -y -i in.mp4 -filter_complex 
[0:v]trim=start=10.0:end=15.0,setpts=PTS-STARTPTS,format=yuv420p[0v];
[0:a]atrim=start=10.0:end=15.0,asetpts=PTS-STARTPTS[0a];
[0:v]trim=start=65.0:end=70.0,setpts=PTS-STARTPTS,format=yuv420p[1v];
[0:a]atrim=start=65.0:end=70.0,asetpts=PTS-STARTPTS[1a];[0v][0a][1v]
[1a]concat=n=2:v=1:a=1[outv][outa] -map [outv] -map [outa] out.mp4
Shawnblais
quelle