it-swarm.com.de

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

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-Videoeditor 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 wie folgt 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 von ABCDEFG dargestellt werden könnte, würde ich gerne ein neues Video erstellen, das aus ACDFG besteht.

43
Matias

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 verkette ich sie mit dem Concat Demuxer, der hier erklärt wird .

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 Erzeugen der verschiedenen Videos. Mit dieser Lösung verschwinden die Synchronisierungsprobleme.

2
Matias

Nun, Sie können immer noch den Filter trim 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 mit dem Filter concat zu einem Stream out1 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
44
ptQa

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 einbinden, 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 besteht darin, -t und -ss zu verwenden, um Videos von jedem gewünschten Segment abzurufen und dann alle Teile für Ihre endgültige Version zusammenzufügen.

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. Der -t gibt an, wie lang jeder Clip ist. Wenn also c 30 Sekunden lang ist, können Sie 30 oder 0:00:30 eingeben. Die Option -ss 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 mit folgendem ace-files.txt:

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 in abe-files.txt zusammen, kopiert ihre Audio- und Videocodecs und erstellt eine Datei ace.tvshow, die nur die Abschnitte a, c und e enthalten sollte. Denken Sie dann daran, ace-files.txt, a.tvshow, c.tvshow und e.tvshow zu löschen.

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

13
xbakesx

Ich habe ein Skript erstellt, um das Bearbeiten 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
2
rocuinneagain