Jak mogę usunąć wiele segmentów z wideo za pomocą FFmpeg?

51

Próbuję usunąć kilka sekcji filmu za pomocą FFmpeg.

Wyobraź sobie na przykład, czy nagrałeś program telewizyjny i chciałeś wyciąć reklamy. Jest to proste dzięki edytorowi wideo GUI; wystarczy zaznaczyć początek i koniec każdego klipu, który ma zostać usunięty, i wybrać opcję usuń. Próbuję zrobić to samo z wiersza polecenia za pomocą FFmpeg.

Wiem, jak przyciąć pojedynczy segment do nowego filmu, tak:

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

Skraca to pięciosekundowy klip i zapisuje go jako nowy plik wideo, ale jak mogę zrobić coś przeciwnego i zapisać cały film bez określonego klipu i jak mogę określić wiele klipów do usunięcia?

Na przykład, jeśli moje wideo może być reprezentowane przez ABCDEFG, chciałbym stworzyć nowy, który składałby się z ACDFG.

Matias
źródło
1
Nie o to pytam, chciałbym uzyskać wszystko w jednym „odcinku”, ale miałyby to inne sekcje niż oryginalne wideo.
Matias
@Matias, jeśli pytasz, jak wyciąć kilka klipów z filmu i pozostawić resztę w niezmienionej formie, byłoby to jedno, ale chcesz wziąć kilka klipów z tego i połączyć je z klipami z innych filmów, co sprawia, że to nie jest osobne, unikalne pytanie. Musisz zrobić, co pozostałe pytania, aby uzyskać oddzielne segmenty, a następnie połączyć je.
Synetech
1
@Synetech dzięki za odpowiedzi. Nie chcę łączyć ich z klipami z innych filmów. Chcę tylko usunąć niektóre części z filmów. Na przykład, jeśli moje wideo może być reprezentowane przez ABCDEFG, chciałbym stworzyć nowy, który składałby się z ACDFG.
Matias
@ Synetech To nie byłem ja, to Tog musiał zrozumieć.
Matias

Odpowiedzi:

47

Nadal możesz do tego użyć trimfiltra. Oto przykład, załóżmy, że chcesz wyciąć segmenty 30-40 sekund (10 sekund) i 50-80 sekund (30 sekund):

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

Co ja tutaj zrobiłem Skróciłem pierwsze 30 sekund, 40-50 sekund i 80 sekund do końca, a następnie połączyłem je w strumień out1z concatfiltrem.

O ustawieniach: potrzebujemy tego, ponieważ przycinanie nie modyfikuje czasu wyświetlania obrazu, a gdy wycinamy 10 sekund, licznik dekodera nie widzi żadnych klatek przez te 10 sekund.

Jeśli chcesz także mieć dźwięk, musisz zrobić to samo dla strumieni audio. Zatem polecenie powinno brzmieć:

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
źródło
Świetnie! Dlaczego potrzebujemy ustawień? Czy możesz dodać wyjaśnienie?
Rajib
Ok, zobacz zaktualizowaną odpowiedź.
ptQa
4
+1 To naprawdę powinno być wybrane jako odpowiedź. Jest bardziej odpowiedni dla „wielu segmentów” i eliminuje konieczność wykonywania wielu poleceń jeden po drugim. (chyba że inny sposób jest bardziej wydajny)
Knossos,
1
Jak zabrałbyś się do przechowywania danych napisów podczas pomijania ramek?
Andrew Scagnelli,
1
To jest bardzo nieludzkie.
gołopot
15

Nigdy nie mogę uruchomić rozwiązania ptQa, głównie dlatego, że nigdy nie mogę dowiedzieć się, co oznaczają błędy filtrów i jak je naprawić. Moje rozwiązanie wydaje się trochę dziwniejsze, ponieważ może pozostawić bałagan, ale jeśli wrzucisz go do skryptu, czyszczenie można zautomatyzować. Podoba mi się również to podejście, ponieważ jeśli coś pójdzie nie tak w kroku 4, kończysz kroki 1-3, więc odzyskiwanie po błędach jest nieco bardziej wydajne.

Podstawową strategią jest używanie -ti -ssuzyskiwanie filmów z każdego segmentu, który chcesz, a następnie łączenie wszystkich części w ostateczną wersję.

Powiedzmy, że masz 6 segmentów ABCDEF co 5 sekund i chcesz A (0-5 sekund), C (10-15 sekund) i E (20-25 sekund), zrobiłbyś to:

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

lub

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

Spowoduje to utworzenie plików a.tvshow, c.tvshow i e.tvshow. -tMówi, jak długo każdy klip jest, więc jeśli c jest 30 sekund długo można przejść w 30 lub 0:00:30. Ta -ssopcja mówi, jak daleko należy przejść do źródłowego wideo, więc zawsze zależy od początku pliku.

Następnie, gdy masz kilka plików wideo, tworzę taki plik ace-files.txt:

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

Zanotuj „plik” na początku, a następnie nazwę pliku ucieczki.

Następnie polecenie:

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

To łączy wszystkie pliki abe-files.txtrazem, kopiując ich kodeki audio i wideo i tworzy plik, ace.tvshowktóry powinien być tylko sekcjami a, c i e. Wtedy po prostu pamiętać, aby usunąć ace-files.txt, a.tvshow, c.tvshowi e.tvshow.

Oświadczenie : Nie mam pojęcia, jak (nie) wydajne jest to w porównaniu z innymi podejściami pod względem, ffmpegale dla moich celów działa lepiej. Mam nadzieję, że to komuś pomoże.

xbakesx
źródło
4
ten jest bardzo zrozumiały dla początkujących jak ja, dzięki
juanpastas
4
Uwaga: jeśli używasz bash, możesz uniknąć tworzenia pliku ( ace-files.txtw twoim przykładzie) za pomocą:ffmpeg -f concat -i <(printf "file '%s'\n" $(pwd)/prefix_*.tvshow) -c copy output.tvshow
bufh
To było naprawdę pomocne, ale musiałem usunąć pojedyncze cudzysłowy z nazw plików, w przeciwnym razie nie działało.
tchaymore
Podczas łączenia filmów występuje krótki czarny film i wyciszony dźwięk między nimi, około 25 ms. Jakieś pomysły na pozbycie się tego?
Antonio Bonifati „Farmboy”
Hmm ... Chciałbym tylko sprawdzić twoją matematykę na temat przesunięć czasowych i przeskoków i upewnić się, że nie zostawiasz odstępu 25 ms ... Nie doświadczyłem tego.
xbakesx
5

Stworzyłem skrypt, aby przyspieszyć edycję nagranej telewizji. Skrypt prosi o godzinę rozpoczęcia i zakończenia segmentów, które chcesz zachować, i dzieli je na pliki. Daje ci opcje, możesz:

  • Weź jeden lub wiele segmentów.
  • Możesz połączyć segmenty w jeden wynikowy plik.
  • Po dołączeniu możesz zachować lub usunąć pliki części.
  • Możesz zachować oryginalny plik lub zastąpić go nowym.

Film z tego w akcji: tutaj

Powiedz mi co myślisz.

 #!/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
źródło
2
Ten skrypt jest świetny! Tylko jedna modyfikacja, ponieważ używasz absolutnych ścieżek do konkatenacji: dodaj safe=0, tj. ffmpeg -f concat -safe 0 -i ...Zobacz ffmpeg.org/ffmpeg-all.html#Options-36
pkSML
2

Mimo że odpowiedź udzielona przez ptQa wydaje się działać, opracowałem inne rozwiązanie, które sprawdziło się dobrze.

Zasadniczo to, co robię, to wyciąć jeden film dla każdej części oryginalnego filmu, który chcę dołączyć do mojego wyniku. Później łączę je z wyjaśnionym tutaj Concat Demuxer .

Rozwiązanie jest takie samo, jak próbowałem najpierw i przedstawiłem problemy z synchronizacją. Dodałem polecenie -avoid_negative_ts 1 podczas generowania różnych filmów. Dzięki temu rozwiązaniu problemy z synchronizacją znikają.

Matias
źródło
1

Dla tych, którzy mają problemy z podejściem ptQa, istnieje nieco bardziej uproszczony sposób. Zamiast łączyć każdy krok na drodze, po prostu wykonaj je wszystkie na końcu.

Dla każdego wejścia zdefiniuj parę A / V:

//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];

Zdefiniuj tyle par, ile potrzebujesz, a następnie połącz je wszystkie w jednym przejściu, gdzie n = całkowita liczba wejść.

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

Można to łatwo zbudować w pętli.

Pełne polecenie wykorzystujące 2 dane wejściowe może wyglądać następująco:

 -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
źródło