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

51

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

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

Eu sei como cortar um único segmento para um novo vídeo como este:

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 sem o clipe especificado, e como posso especificar vários clipes a serem removidos?

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

Matias
fonte
possível duplicata de vídeo Splitting em vários episódios com ffmpeg , também, talvez, confira Usando FFMpeg para cortar um vídeo em 2 clipes minutos
Ƭᴇcʜιᴇ007
11
Não é isso que estou perguntando, gostaria de colocar tudo em um "episódio", mas isso teria seções diferentes do vídeo original.
Matias
@ Matias, se você estivesse perguntando como cortar alguns clipes do vídeo e deixar o resto como está, isso seria uma coisa, mas você deseja tirar alguns clipes dele e combiná-los com clipes de outros vídeos, o que torna essa não é uma pergunta separada e única. Você precisa fazer o que as outras perguntas fizeram para obter os segmentos separados e combiná-los.
21413 Synetech
11
@ Synetech, obrigado por suas respostas. Não quero combiná-los com clipes de outros vídeos. Eu só quero remover algumas partes dos vídeos. Por exemplo, se meu vídeo pudesse ser representado pelo ABCDEFG, gostaria de criar um novo que consistisse em ACDFG.
Matias
@ Synetech Não fui eu, foi Tog quem deve ter entendido errado.
Matias

Respostas:

47

Bem, você ainda pode usar o trimfiltro para isso. Aqui está um exemplo, vamos assumir que você deseja cortar segmentos de 30 a 40 segundos (10 segundos) e 50 a 80 segundos (30 segundos):

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 os primeiros 30 segundos, 40-50 segundos e 80 segundos até o fim e depois os combinei em fluxo out1com o concatfiltro.

Sobre ajustes: precisamos disso porque o ajuste não modifica o tempo de exibição da imagem e, quando cortamos o contador do decodificador de 10 segundos, não vemos quadros por esses 10 segundos.

Se você quiser ter áudio também, precisará fazer o mesmo para fluxos de áudio. Portanto, o comando deve 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
ptQa
fonte
Muito bom! Por que precisamos de setpts? Você poderia adicionar a explicação?
Rajib
Ok, veja a resposta atualizada.
ptQa
4
+1 Isso realmente deve ser selecionado como a resposta. É mais adequado para "vários segmentos" e remove a necessidade de executar vários comandos, um após o outro. (a menos que outra maneira seja mais eficiente em termos de velocidade) #
Knossos
11
Como você manteria os dados das legendas ao pular quadros?
Andrew Scagnelli
11
Isso é muito desumano.
golopot
15

Eu 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 complicada porque pode deixar para trás uma bagunça, mas se você a jogar em um script, a limpeza poderá ser automatizada. Também gosto dessa abordagem, porque se algo der errado na etapa 4, você terminará as etapas 1 a 3, para que a recuperação dos erros seja um pouco mais eficiente.

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

Digamos que você tenha 6 segmentos ABCDEF a cada 5 segundos e deseje A (0-5 segundos), C (10-15 segundos) e E (20-25 segundos):

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 fará com que os arquivos a.tvshow, c.tvshow e e.tvshow. Ele -tdiz quanto tempo cada clipe tem; portanto, se c tiver 30 segundos, você poderá passar 30 ou 0:00:30. A -ssopção diz até onde pular no vídeo de origem, por isso é sempre relativo ao início do arquivo.

Então, quando você tem um monte de arquivos de vídeo, eu faço 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 de escape depois disso.

Então o comando:

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

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

Isenção de responsabilidade : não tenho idéia de quão (in) eficiente isso é comparado a outras abordagens em termos de, ffmpegmas para meus propósitos funciona melhor. Espero que ajude alguém.

xbakesx
fonte
4
este é muito compreensível para iniciantes como eu, graças
juanpastas
4
Observe que se você estiver usando o bash, poderá evitar a criação de um arquivo ( ace-files.txtno seu exemplo) com:ffmpeg -f concat -i <(printf "file '%s'\n" $(pwd)/prefix_*.tvshow) -c copy output.tvshow
bufh 31/03/16
Isso foi realmente útil, mas tive que remover as aspas simples dos nomes dos arquivos ou não funcionou.
tchaymore
Ao concatenar vídeos, há um pequeno vídeo preto e áudio silenciado entre eles, cerca de 25ms. Alguma idéia para se livrar disso?
Antonio Bonifati 'Farmboy'
Hmm ... eu apenas verificaria sua matemática nos desvios e saltos de tempo, e garantiria que você não deixasse um intervalo de 25ms ... Eu não experimentei isso.
xbakesx 19/02
5

Fiz um roteiro para acelerar a edição da TV gravada. O script solicita os horários de início e fim dos segmentos que você deseja manter e os divide em arquivos. Oferece 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 de peça.
  • Você pode manter o arquivo original ou substituí-lo pelo seu novo arquivo.

Vídeo dele em ação: aqui

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
rocuinneagain
fonte
2
Esse script é ótimo! Apenas uma modificação, já que você está usando caminhos absolutos para concatenação: add safe=0, ie ffmpeg -f concat -safe 0 -i ...Veja ffmpeg.org/ffmpeg-all.html#Options-36
pkSML
2

Embora a resposta fornecida pelo ptQa pareça funcionar, desenvolvi uma outra solução que provou funcionar bem.

Basicamente, o que faço é cortar um vídeo para cada parte do vídeo original que eu quero incluir no meu resultado. Mais tarde, concatená-los com o Concat Demuxer explicado aqui .

A solução é a mesma que eu tentei primeiro e apresentei problemas de sincronização. O que eu adicionei é o comando -avoid_negative_ts 1 ao gerar os diferentes vídeos. Com esta solução, os problemas de sincronização desaparecem.

Matias
fonte
1

Para aqueles que têm problemas para seguir a abordagem do ptQa, há uma maneira um pouco mais simplificada de fazer isso. Em vez de concatinar cada etapa do caminho, faça 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=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];

Defina quantos pares você precisar e concorde todos eles em uma única passagem, em que 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:

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