Как я могу удалить несколько сегментов из видео, используя FFmpeg?

51

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

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

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

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

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

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

Matias
источник
возможно дублирование видео в нескольких эпизодах с помощью ffmpeg , также, возможно, стоит
Ƭᴇcʜιᴇ007
1
Это не то, что я спрашиваю, я хотел бы получить все это в одном «эпизоде», но это будет иметь различные разделы из оригинального видео.
Матиас
@Matias, если вы спрашивали, как вырезать несколько клипов из видео и оставить остальные как есть, это было бы одно, но вы хотите взять из него несколько клипов и объединить их с клипами из других видео, что делает это не отдельный, уникальный вопрос. Вы должны сделать то, что задали другие вопросы, чтобы получить отдельные сегменты, а затем объединить их.
Synetech
1
@Synetech спасибо за ваши ответы. Я не хочу сочетать их с клипами из других видео. Я просто хочу удалить некоторые части из видео. Например, если мое видео может быть представлено ABCDEFG, я хотел бы создать новое, которое будет состоять из ACDFG.
Матиас
@Synetech Это не я, а Тог, должно быть, неправильно поняли.
Матиас

Ответы:

47

Ну, вы все еще можете использовать trimфильтр для этого. Вот пример, давайте предположим, что вы хотите вырезать сегменты 30-40 секунд (10 секунд) и 50-80 секунд (30 секунд):

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фильтром.

Об установках: нам это нужно, потому что обрезка не изменяет время отображения изображения, и когда мы вырезаем 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
ptQa
источник
Великий! Зачем нам нужны сетты? Не могли бы вы добавить объяснение?
Раджиб
Хорошо, смотрите обновленный ответ.
ptQa
4
+1 Это действительно должно быть выбрано в качестве ответа. Он больше подходит для «нескольких сегментов» и устраняет необходимость выполнять несколько команд одну за другой. (если другой способ не является более эффективным по скорости)
Кносс
1
Как вы будете поддерживать данные субтитров при пропуске кадров?
Эндрю Сканелли
1
Это очень нечеловечески.
Голопот
15

Я никогда не смогу заставить работать решение 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'

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

Тогда команда:

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но для моих целей это работает лучше. Надеюсь, это кому-нибудь поможет.

xbakesx
источник
4
это очень понятно для новичков, как я, спасибо
juanpastas
4
Обратите внимание, что если вы используете bash, вы можете избежать создания файла ( ace-files.txtв вашем примере) с помощью:ffmpeg -f concat -i <(printf "file '%s'\n" $(pwd)/prefix_*.tvshow) -c copy output.tvshow
bufh
1
Это было действительно полезно, но мне пришлось удалить одинарные кавычки вокруг имен файлов, или это не сработало.
Чаймор
При объединении видео между ними есть короткое черное видео и звук с глушением, около 25 мс. Есть идеи, чтобы избавиться от этого?
Антонио Бонифати 'Farmboy'
1
Спасибо, @bufh, мне было интересно, как это сделать! Просто примечание, которое может помочь другим: как объяснено ниже @pkSML , вам также необходимо добавить -safe 0команду ffmpeg, если вы используете абсолютные пути к файлам, чтобы это работало. Или вы можете удалить $(pwd)/часть подкоманды bash.
Валдириус
5

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

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

Видео об этом в действии: здесь

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

 #!/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
источник
2
Этот сценарий великолепен! Всего лишь одна модификация, поскольку вы используете абсолютные пути для конкатенации: добавьте safe=0, т.е. ffmpeg -f concat -safe 0 -i ...смотрите ffmpeg.org/ffmpeg-all.html#Options-36
pkSML
2

Хотя ответ, предоставленный ptQa, кажется, работает, я разработал другое решение, которое доказало свою работоспособность.

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

Решение такое же, какое я попробовал первым и представил проблемы с синхронизацией. Я добавил команду -avoid_negative_ts 1 при создании разных видео. При таком решении проблемы с синхронизацией исчезают.

Matias
источник
1

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

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

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

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

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

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

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

 -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
источник