Как удалить несколько фрагментов из видео с помощью FFmpeg?

Как удалить несколько фрагментов из видео с помощью FFmpeg?

Я пытаюсь удалить несколько фрагментов видео с помощью FFmpeg.

Например, представьте, что вы записали шоу на телевидении и хотите вырезать рекламу. Это просто сделать с помощью графического видеоредактора; вы просто отмечаете начало и конец каждого клипа, который нужно удалить, и выбираете «Удалить». Я пытаюсь сделать то же самое из командной строки с помощью FFmpeg.

Я знаю, как разрезать один сегмент нановыйвидео вроде этого:

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

Это вырезает пятисекундный клип и сохраняет его как новый видеофайл, но как сделать наоборот и сохранить все видео?безуказанный клип, и как я могу указать несколько клипов для удаления?

Например, если мое видео можно представить как ABCDEFG, я хотел бы создать новое, которое состояло бы из ACDFG.

решение1

Ну, вы все еще можете использоватьtrimфильтр для этого. Вот пример, предположим, что вы хотите вырезать три сегмента в начале и конце видео, а также в середине:

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

Что я тут сделал? Я обрезал первые 30 сек, 40-50 сек и 80 сек в конце, а затем объединил их в поток out1сconcatпрофильтровать, оставив 30-40 сек (10 сек) и 50-80 сек (30 сек).

О настройках: это нам нужно, потому что обрезка не изменяет время отображения картинки, и когда мы вырезаем 10 секунд, счетчик декодера не видит ни одного кадра за эти 10 секунд.

Если вы хотите иметь также аудио, вам нужно сделать то же самое для аудиопотоков. Поэтому команда должна быть:

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

решение2

Я никогда не могу заставить работать решение ptQa, в основном потому, что я никогда не могу понять, что означают ошибки фильтров или как их исправить. Мое решение кажется немного более неуклюжим, потому что оно может оставить после себя беспорядок, но если вы добавляете его в скрипт, очистку можно автоматизировать. Мне также нравится этот подход, потому что если что-то пойдет не так на шаге 4, вы в конечном итоге получите завершенные шаги 1-3, поэтому восстановление после ошибок немного эффективнее.

Основная стратегия заключается в использовании -tи -ssдля получения видео каждого нужного вам сегмента, а затем в объединении всех частей для получения финальной версии.

Допустим, у вас есть 6 сегментов ABCDEF, каждый длиной 5 секунд, и вы хотите, чтобы A (0-5 секунд), C (10-15 секунд) и E (20-25 секунд) были разделены на сегменты. Вы можете сделать это:

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

или

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

Это создаст файлы a.tvshow, c.tvshow и e.tvshow. Это -tговорит о продолжительности каждого клипа, так что если c длится 30 секунд, вы можете передать 30 или 0:00:30. Эта -ssопция говорит о том, насколько далеко нужно перейти в исходное видео, так что это всегда относительно начала файла.

Затем, когда у вас есть куча видеофайлов, я создаю ace-files.txtтакой файл:

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

Обратите внимание на слово «file» в начале и замаскированное имя файла после него.

Затем команда:

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

Это объединяет все файлы abe-files.txtвместе, копируя их аудио- и видеокодеки и создавая файл ace.tvshow, который должен состоять только из разделов a, c и e. Затем просто не забудьте удалить ace-files.txt, a.tvshow, c.tvshowи e.tvshow.

Отказ от ответственности: Я понятия не имею, насколько это (не)эффективно по сравнению с другими подходами, ffmpegно для моих целей это работает лучше. Надеюсь, это кому-то поможет.

решение3

Для тех, у кого возникли проблемы с подходом ptQa, есть немного более рациональный способ. Вместо того, чтобы объединять каждый шаг пути, просто выполните их все в конце.

Для каждого входа определите пару A/V:

//Input1:
[0:v]trim=start=10:end=20,setpts=PTS-STARTPTS,format=yuv420p[0v];
[0:a]atrim=start=10:end=20,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];

Определите столько пар, сколько вам нужно, а затем объедините их все за один проход, где n = общее количество входных данных.

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

Это можно легко построить в цикле.

Полная команда, использующая 2 входа, может выглядеть так:

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

решение4

Я сделал скрипт для ускорения редактирования записанного ТВ. Скрипт запрашивает время начала и окончания сегментов, которые вы хотите сохранить, и разбивает их на файлы. Он дает вам опции, вы можете:

  • Возьмите один или несколько сегментов.
  • Вы можете объединить сегменты в один результирующий файл.
  • После присоединения вы можете сохранить или удалить файлы деталей.
  • Вы можете сохранить исходный файл или заменить его новым файлом.

Дайте мне знать, что вы думаете.

 #!/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

Связанный контент