Bagaimana cara menghapus beberapa segmen dari video menggunakan FFmpeg?

51

Saya mencoba menghapus beberapa bagian video menggunakan FFmpeg.

Misalnya, bayangkan jika Anda merekam acara di televisi dan ingin memotong iklannya. Ini sederhana dengan editor video GUI; Anda cukup menandai awal dan akhir dari setiap klip yang akan dihapus, dan pilih hapus. Saya mencoba melakukan hal yang sama dari command-line dengan FFmpeg.

Saya tahu cara memotong satu segmen ke video baru seperti:

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

Ini memotong klip lima detik dan menyimpannya sebagai file video baru, tetapi bagaimana saya bisa melakukan yang sebaliknya dan menyimpan seluruh video tanpa klip yang ditentukan, dan bagaimana saya bisa menentukan beberapa klip untuk dihapus?

Misalnya, jika video saya dapat diwakili oleh ABCDEFG, saya ingin membuat yang baru yang terdiri dari ACDFG.

Matias
sumber
1
Bukan itu yang saya tanyakan, saya ingin mendapatkan semuanya dalam satu "episode", tetapi ini akan memiliki bagian yang berbeda dari video aslinya.
Matias
@Matias, jika Anda bertanya bagaimana cara memotong beberapa klip dari video dan membiarkan sisanya apa adanya, itu akan menjadi satu hal, tetapi Anda ingin mengambil beberapa klip dari itu dan menggabungkannya dengan klip dari video lain yang membuat ini bukan pertanyaan unik dan terpisah. Anda harus melakukan apa yang diminta pertanyaan lain untuk mendapatkan segmen yang terpisah, lalu menggabungkannya.
Synetech
1
@Synetech, terima kasih atas jawaban Anda. Saya tidak ingin menggabungkannya dengan klip dari video lain. Saya hanya ingin menghapus beberapa bagian dari video. Misalnya, jika video saya dapat diwakili oleh ABCDEFG, saya ingin membuat yang baru yang terdiri dari ACDFG.
Matias
@ Sinetech Itu bukan saya, itu Tog yang pasti salah paham.
Matias

Jawaban:

47

Nah, Anda masih bisa menggunakan trimfilter untuk itu. Berikut ini contohnya, mari kita asumsikan bahwa Anda ingin memotong segmen 30-40 detik (10 detik) dan 50-80 detik (30 detik):

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

Apa yang saya lakukan di sini? Saya memangkas 30 detik pertama, 40-50 detik dan 80 detik untuk mengakhiri, dan kemudian menggabungkannya ke dalam aliran out1dengan concatfilter.

Tentang setpts: kita memerlukan ini karena trim tidak mengubah waktu tampilan gambar, dan ketika kita memotong penghitung decoder 10 detik tidak melihat bingkai untuk 10 detik ini.

Jika Anda ingin memiliki audio juga, Anda harus melakukan hal yang sama untuk stream audio. Jadi perintahnya adalah:

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
sumber
Yang bagus! Mengapa kita membutuhkan setpts? Bisakah Anda menambahkan penjelasannya?
Rajib
Oke, lihat jawaban yang diperbarui.
ptQa
4
+1 Ini benar-benar harus dipilih sebagai jawabannya. Ini lebih cocok untuk "beberapa segmen" dan menghilangkan keharusan untuk melakukan beberapa perintah satu demi satu. (kecuali cara lain lebih efisien kecepatan)
Knossos
1
Bagaimana cara Anda menjaga data subtitle saat Anda melewatkan frame?
Andrew Scagnelli
1
Ini sangat tidak manusiawi.
golopot
15

Saya tidak pernah bisa mendapatkan solusi ptQa untuk bekerja, terutama karena saya tidak pernah tahu apa arti kesalahan dari filter atau cara memperbaikinya. Solusi saya tampaknya agak clunkier karena dapat meninggalkan kekacauan, tetapi jika Anda melemparkannya ke dalam skrip, pembersihan dapat dilakukan secara otomatis. Saya juga menyukai pendekatan ini karena jika ada yang salah pada langkah 4, Anda berakhir dengan langkah 1-3 sehingga pemulihan dari kesalahan sedikit lebih efisien.

Strategi dasar menggunakan -tdan -ssuntuk mendapatkan video dari setiap segmen yang Anda inginkan, kemudian bergabung bersama semua bagian untuk versi final Anda.

Katakanlah Anda memiliki 6 segmen ABCDEF setiap 5 detik dan Anda menginginkan A (0-5 detik), C (10-15 detik) dan E (20-25 detik) Anda akan melakukan ini:

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

atau

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

Itu akan membuat file a.tvshow, c.tvshow dan e.tvshow. Yang -tmengatakan berapa lama setiap klip, jadi jika c adalah 30 detik, Anda bisa lulus dalam 30 atau 0:00:30. The -sspilihan mengatakan seberapa jauh untuk melewati ke sumber video, sehingga selalu relatif terhadap awal file.

Kemudian setelah Anda memiliki banyak file video saya membuat file ace-files.txtseperti ini:

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

Catat "file" di awal dan nama file yang lolos setelah itu.

Lalu perintahnya:

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

Itu menggabungkan semua file abe-files.txtbersama-sama, menyalin codec audio dan video mereka dan membuat file ace.tvshowyang seharusnya hanya bagian a, c dan e. Kemudian hanya ingat untuk menghapus ace-files.txt, a.tvshow, c.tvshowdan e.tvshow.

Penafian : Saya tidak tahu bagaimana efisien dalam hal ini dibandingkan dengan pendekatan lain dalam hal ffmpegtetapi untuk tujuan saya itu bekerja lebih baik. Semoga ini bisa membantu seseorang.

xbakesx
sumber
4
yang ini sangat dimengerti untuk pemula seperti saya, terima kasih
juanpastas
4
Catatan jika Anda menggunakan bash, Anda dapat menghindari pembuatan file ( ace-files.txtdalam contoh Anda) dengan:ffmpeg -f concat -i <(printf "file '%s'\n" $(pwd)/prefix_*.tvshow) -c copy output.tvshow
bufh
Ini sangat membantu tetapi saya harus menghapus tanda kutip tunggal dari sekitar nama file atau tidak berhasil.
tchaymore
Saat menggabungkan video, ada video hitam pendek dan audio yang dibungkam di antara mereka, sekitar 25 ms. Ada ide untuk menyingkirkan itu?
Antonio Bonifati 'Farmboy'
Hmm ... Saya baru saja memeriksa matematika Anda tentang offset waktu dan lompatan, dan pastikan Anda tidak meninggalkan celah 25ms ... Saya belum mengalaminya.
xbakesx
5

Saya membuat skrip untuk mempercepat pengeditan rekaman TV. Script meminta Anda untuk awal dan akhir kali segmen yang ingin Anda simpan dan bagi menjadi beberapa file. Ini memberi Anda opsi, Anda dapat:

  • Ambil satu atau beberapa segmen.
  • Anda dapat menggabungkan segmen menjadi satu file yang dihasilkan.
  • Setelah bergabung, Anda dapat menyimpan atau menghapus file bagian.
  • Anda dapat menyimpan file asli atau menggantinya dengan file baru Anda.

Video aksi itu: di sini

Biarkan aku tahu apa yang Anda pikirkan.

 #!/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
sumber
2
Script ini bagus! Hanya satu modifikasi karena Anda menggunakan jalur absolut untuk penggabungan: tambahkan safe=0, yaitu ffmpeg -f concat -safe 0 -i ...Lihat ffmpeg.org/ffmpeg-all.html#Options-36
pkSML
2

Meskipun jawaban yang diberikan oleh ptQa tampaknya berhasil, saya telah mengembangkan solusi lain yang terbukti bekerja dengan baik.

Pada dasarnya, apa yang saya lakukan adalah memotong satu video untuk setiap bagian dari video asli yang ingin saya sertakan pada hasil saya. Kemudian, saya menggabungkannya dengan Concat Demuxer yang dijelaskan di sini .

Solusinya sama dengan yang saya coba dulu dan sajikan masalah sinkronisasi. Apa yang saya tambahkan adalah perintah -avoid_negative_ts 1 saat membuat video yang berbeda. Dengan solusi ini, masalah sinkronisasi menghilang.

Matias
sumber
1

Bagi mereka yang mengalami kesulitan dalam mengikuti pendekatan ptQa, ada cara yang sedikit lebih efisien untuk melakukannya. Daripada menyatukan setiap langkah, lakukan saja semuanya di akhir.

Untuk setiap input, tentukan pasangan 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];

Tentukan pasangan sebanyak yang Anda butuhkan, kemudian gabungkan semuanya dalam satu pass, di mana n = jumlah input total.

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

Ini dapat dengan mudah dibangun dalam satu lingkaran.

Perintah lengkap yang menggunakan 2 input mungkin terlihat seperti ini:

 -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
sumber