Como posso remover vários segmentos de um vídeo usando o FFmpeg?

Como posso remover vários segmentos de um vídeo usando o FFmpeg?

Estou tentando excluir algumas seções de um vídeo usando o FFmpeg.

Por exemplo, imagine se você gravou um programa de televisão e quis cortar os comerciais. Isso é simples com um editor de vídeo GUI; basta marcar o início e o fim de cada clipe a ser removido e selecionar excluir. Estou tentando fazer a mesma coisa na linha de comando com o FFmpeg.

Eu sei como cortar um único segmento em umnovovídeo assim:

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

Isso corta um clipe de cinco segundos e o salva como um novo arquivo de vídeo, mas como posso fazer o oposto e salvar o vídeo inteiro?semo clipe especificado e como posso especificar vários clipes a serem removidos?

Por exemplo, se meu vídeo pudesse ser representado por ABCDEFG, eu gostaria de criar um novo que consistisse em ACDFG.

Responder1

Bem, você ainda pode usar otrimfiltrar para isso. Aqui está um exemplo, vamos supor que você deseja cortar três segmentos no início e no final do vídeo, bem como no meio:

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

O que eu fiz aqui? Cortei primeiro 30 segundos, 40-50 segundos e 80 segundos até o final e depois combinei-os em um fluxo out1com oconcatfiltro, deixando 30-40 segundos (10 segundos) e 50-80 segundos (30 segundos).

Sobre setpts: precisamos disso porque o corte não modifica o tempo de exibição da imagem e, quando cortamos 10 segundos, o contador do decodificador não vê nenhum quadro nesses 10 segundos.

Se você quiser áudio também, deverá fazer o mesmo para os fluxos de áudio. Então o comando deveria ser:

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

Responder2

Nunca consigo fazer a solução do ptQa funcionar, principalmente porque nunca consigo descobrir o que significam os erros dos filtros ou como corrigi-los. Minha solução parece um pouco mais desajeitada porque pode deixar uma bagunça, mas se você colocar isso em um script, a limpeza pode ser automatizada. Também gosto dessa abordagem porque se algo der errado na etapa 4, você concluirá as etapas 1 a 3, portanto, a recuperação de erros é um pouco mais eficiente.

A estratégia básica é usar -te -ssobter vídeos de cada segmento desejado e depois juntar todas as partes para sua versão final.

Digamos que você tenha 6 segmentos ABCDEF com 5 segundos de duração cada e queira A (0-5 segundos), C (10-15 segundos) e E (20-25 segundos), você faria o seguinte:

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

ou

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

Isso criará os arquivos a.tvshow, c.tvshow e e.tvshow. Diz -ta duração de cada clipe, então se c tiver 30 segundos de duração você poderá passar em 30 ou 0:00:30. A -ssopção indica até onde pular no vídeo de origem, portanto é sempre relativo ao início do arquivo.

Então, quando você tiver vários arquivos de vídeo, eu crio um arquivo ace-files.txtcomo este:

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

Observe o "arquivo" no início e o nome do arquivo com escape depois disso.

Então o comando:

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

Isso reúne todos os arquivos abe-files.txt, copiando seus codecs de áudio e vídeo e cria um arquivo ace.tvshowque deve ser apenas as seções a, c e e. Então lembre-se de excluir ace-files.txt, a.tvshow, c.tvshowe e.tvshow.

Isenção de responsabilidade: Não tenho ideia de quão (in)eficiente isso é em comparação com outras abordagens em termos de, ffmpegmas para meus propósitos funciona melhor. Espero que ajude alguém.

Responder3

Para aqueles que estão tendo problemas para seguir a abordagem do ptQa, há uma maneira um pouco mais simplificada de fazer isso. Em vez de concatenar cada passo do caminho, basta fazer tudo no final.

Para cada entrada, defina um par 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];

Defina quantos pares você precisar e, em seguida, concatene-os todos em uma única passagem, onde n = contagem total de entradas.

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

Isso pode ser facilmente construído em um loop.

Um comando completo que usa 2 entradas pode ser assim:

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

Responder4

Fiz um roteiro para agilizar a edição da TV gravada. O script solicita os horários de início e término dos segmentos que você deseja manter e os divide em arquivos. Dá opções, você pode:

  • Pegue um ou vários segmentos.
  • Você pode combinar os segmentos em um arquivo resultante.
  • Após ingressar você pode manter ou excluir os arquivos da peça.
  • Você pode manter o arquivo original ou substituí-lo pelo novo arquivo.

Diz-me o que pensas.

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

informação relacionada