Como posso usar o ffmpeg para dividir o vídeo MPEG em pedaços de 10 minutos?

63

Geralmente, é necessário que a comunidade de desenvolvedores de código aberto ou ativo publique grandes segmentos de vídeo online. (Vídeos de reunião, acampamentos, palestras sobre tecnologia ...) Como sou desenvolvedor e não cinegrafista, não tenho nenhum desejo de fazer um arranhão extra em uma conta premium do Vimeo. Como, então, pego um vídeo de conversa técnica MPEG de 12,5 GB (1:20:00) e o divido em segmentos 00:10:00 para facilitar o upload para sites de compartilhamento de vídeo?

Gabriel
fonte
Agradecimentos especiais a @StevenD por acomodar as novas tags.
Gabriel

Respostas:

69
$ ffmpeg -i source-file.foo -ss 0 -t 600 first-10-min.m4v
$ ffmpeg -i source-file.foo -ss 600 -t 600 second-10-min.m4v
$ ffmpeg -i source-file.foo -ss 1200 -t 600 third-10-min.m4v
...

Agrupar isso em um script para fazer isso em um loop não seria difícil.

Lembre-se de que, se você tentar calcular o número de iterações com base na duração da saída de uma ffprobechamada, isso é estimado a partir da taxa de bits média no início do clipe e do tamanho do arquivo do clipe, a menos que você dê o -count_framesargumento, o que diminui consideravelmente sua operação .

Outra coisa a ter em atenção é que a posição da -ssopção na linha de comando é importante . Onde eu tenho agora é lento, mas preciso. A primeira versão desta resposta deu a alternativa rápida, mas imprecisa . O artigo vinculado também descreve uma alternativa mais rápida, mas ainda precisa, que você paga com um pouco de complexidade.

Tudo isso de lado, eu não acho que você realmente queira cortar exatamente 10 minutos para cada clipe. Isso colocará cortes bem no meio das frases, até das palavras. Eu acho que você deveria usar um editor de vídeo ou player para encontrar pontos de corte naturais com apenas 10 minutos de intervalo.

Supondo que seu arquivo esteja em um formato que o YouTube possa aceitar diretamente, você não precisará recodificar para obter segmentos. Apenas passe as compensações naturais do ponto de corte para ffmpeg, dizendo-lhe para passar o A / V codificado intocado usando o codec "copy":

$ ffmpeg -i source.m4v -ss 0 -t 593.3 -c copy part1.m4v
$ ffmpeg -i source.m4v -ss 593.3 -t 551.64 -c copy part2.m4v
$ ffmpeg -i source.m4v -ss 1144.94 -t 581.25 -c copy part3.m4v
...

O -c copyargumento diz para copiar todos os fluxos de entrada (áudio, vídeo e potencialmente outros, como legendas) na saída como estão. Para programas simples de A / V, é equivalente aos sinalizadores mais detalhados -c:v copy -c:a copyou sinalizadores de estilo antigo -vcodec copy -acodec copy. Você usaria o estilo mais detalhado quando desejar copiar apenas um dos fluxos, mas recodifique o outro. Por exemplo, há muitos anos, havia uma prática comum com os arquivos do QuickTime para compactar o vídeo com o vídeo H.264, mas deixar o áudio como PCM descompactado ; se você leu hoje um arquivo como esse, pode modernizá-lo -c:v copy -c:a aacpara reprocessar apenas o fluxo de áudio, deixando o vídeo intocado.

O ponto inicial de todos os comandos acima após o primeiro é o ponto inicial do comando anterior mais a duração do comando anterior.

Warren Young
fonte
11
"cortar exatamente 10 minutos para cada clipe" é um bom ponto.
Chris
talvez usando o parâmetro -show_packets você possa torná-lo mais preciso.
Rogerdpack
como colocar acima em um loop?
Krazzy R
i Utilize este cmd ya lo dividido em direito, mas agora o vídeo eo áudio não estão em SYNC qualquer ajuda
Sunil Chaudhary
@SunilChaudhary: Os formatos de arquivo A / V são complexos e nem todos os softwares os implementam da mesma maneira, de modo que as informações ffmpegnecessárias para manter a sincronização entre os fluxos de áudio e vídeo podem não estar presentes no arquivo de origem. Se isso estiver acontecendo com você, existem métodos mais complicados de dividir que evitam o problema.
Warren Young
53

Aqui está a solução de uma linha :

ffmpeg -i input.mp4 -c copy -map 0 -segment_time 00:20:00 -f segment output%03d.mp4

Observe que isso não fornece divisões precisas, mas deve atender às suas necessidades. Em vez disso, ele será cortado no primeiro quadro após o tempo especificado depois segment_time, no código acima, após a marca de 20 minutos.

Se você achar que apenas o primeiro pedaço é reproduzível, tente adicionar -reset_timestamps 1conforme mencionado nos comentários.

ffmpeg -i input.mp4 -c copy -map 0 -segment_time 00:20:00 -f segment -reset_timestamps 1 output%03d.mp4
Jon
fonte
2
Na verdade, oferece divisões muito precisas, se você valoriza a qualidade do vídeo. Em vez de dividir com base em um horário específico, ele é dividido no quadro-chave mais próximo após o tempo solicitado, para que cada novo segmento sempre comece com um quadro-chave.
Malvineous
3
quais são as unidades? 8s? 8min? 8h?
user1133275
11
@ user1133275 sua segunda
Jon
2
No Mac, descobri que isso resultava em N pedaços de vídeo de saída, mas apenas o primeiro deles era um MP4 visível e válido. Os outros pedaços N-1 eram de vídeo em branco (todos pretos) sem áudio. Para fazê-lo funcionar, eu precisava adicionar o sinalizador reset_timestamps da seguinte maneira: ffmpeg -i input.mp4 -c copy -map 0 -segment_time 8 -f segment -reset_timestamps 1 output% 03d.mp4.
jarmod
3
descobriram que a adição -reset_timestamps 1corrige o problema para mim
jlarsch
6

Enfrentou o mesmo problema anteriormente e montou um script Python simples para fazer exatamente isso (usando FFMpeg). Disponível aqui: https://github.com/c0decracker/video-splitter e colado abaixo:

#!/usr/bin/env python
import subprocess
import re
import math
from optparse import OptionParser
length_regexp = 'Duration: (\d{2}):(\d{2}):(\d{2})\.\d+,'
re_length = re.compile(length_regexp)
def main():
    (filename, split_length) = parse_options()
    if split_length <= 0:
        print "Split length can't be 0"
        raise SystemExit
    output = subprocess.Popen("ffmpeg -i '"+filename+"' 2>&1 | grep 'Duration'",
                              shell = True,
                              stdout = subprocess.PIPE
    ).stdout.read()
    print output
    matches = re_length.search(output)
    if matches:
        video_length = int(matches.group(1)) * 3600 + \
                       int(matches.group(2)) * 60 + \
                       int(matches.group(3))
        print "Video length in seconds: "+str(video_length)
    else:
        print "Can't determine video length."
        raise SystemExit
    split_count = int(math.ceil(video_length/float(split_length)))
    if(split_count == 1):
        print "Video length is less then the target split length."
        raise SystemExit
    split_cmd = "ffmpeg -i '"+filename+"' -vcodec copy "
    for n in range(0, split_count):
        split_str = ""
        if n == 0:
            split_start = 0
        else:
            split_start = split_length * n
            split_str += " -ss "+str(split_start)+" -t "+str(split_length) + \
                         " '"+filename[:-4] + "-" + str(n) + "." + filename[-3:] + \
                         "'"
    print "About to run: "+split_cmd+split_str
    output = subprocess.Popen(split_cmd+split_str, shell = True, stdout =
                              subprocess.PIPE).stdout.read()
def parse_options():
    parser = OptionParser()
    parser.add_option("-f", "--file",
                      dest = "filename",
                      help = "file to split, for example sample.avi",
                      type = "string",
                      action = "store"
    )
    parser.add_option("-s", "--split-size",
                      dest = "split_size",
                      help = "split or chunk size in seconds, for example 10",
                      type = "int",
                      action = "store"
    )
    (options, args) = parser.parse_args()
    if options.filename and options.split_size:
        return (options.filename, options.split_size)
    else:
        parser.print_help()
        raise SystemExit
if __name__ == '__main__':
    try:
        main()
    except Exception, e:
        print "Exception occured running main():"
        print str(e)
c0decracker
fonte
11
Da próxima vez faça isso por favor em um comentário. As respostas somente para links não são realmente desejadas aqui, e o mesmo se você anunciar seu site. Se for um projeto de código-fonte aberto com código-fonte, talvez seja uma exceção, mas agora arrisquei meus privilégios de revisão por não votar na remoção de sua resposta. E sim, você não pode postar comentários, mas depois de coletar 5 votos positivos (o que parece muito rápido no seu caso), você o fará.
user259412
2
Olá e bem-vindo ao site! Por favor, não poste apenas respostas para o link. Embora seu próprio script provavelmente seja uma ótima resposta, um link para ele não é uma resposta. É uma placa apontando para uma resposta. Mais sobre isso aqui . Como você gentilmente deu o link, fui em frente e incluí o script no corpo da sua resposta. Se você se opuser a isso, exclua a resposta completamente.
terdon
4

Se você deseja criar realmente os mesmos pedaços, deve forçar o ffmpeg a criar o i-frame no primeiro quadro de todos os pedaços, para que você possa usar este comando para criar pedaços de 0,5 segundo.

ffmpeg -hide_banner  -err_detect ignore_err -i input.mp4 -r 24 -codec:v libx264  -vsync 1  -codec:a aac  -ac 2  -ar 48k  -f segment   -preset fast  -segment_format mpegts  -segment_time 0.5 -force_key_frames  "expr: gte(t, n_forced * 0.5)" out%d.mkv
alireza akbaribayat
fonte
Essa deve ser a resposta aceita. Obrigado por compartilhar companheiro!
Stephan Ahlf
4

Uma maneira alternativa mais legível seria

ffmpeg -i input.mp4 -ss 00:00:00 -to 00:10:00 -c copy output1.mp4
ffmpeg -i input.mp4 -ss 00:10:00 -to 00:20:00 -c copy output2.mp4

/**
* -i  input file
* -ss start time in seconds or in hh:mm:ss
* -to end time in seconds or in hh:mm:ss
* -c codec to use
*/

Aqui está a fonte e a lista dos comandos FFmpeg usados ​​com frequência.

Niket Pathak
fonte
3

Observe a pontuação exata do formato alternativo -ss mm:ss.xxx. Eu lutei por horas tentando usar o intuitivo, mas errado, mm:ss:xxsem sucesso.

$ man ffmpeg | grep -C1 position

-ss position
Procure a posição de tempo especificada em segundos. A sintaxe "hh: mm: ss [.xxx]" também é suportada.

Referências aqui e aqui .

Mark Hudson
fonte
0
#!/bin/bash

if [ "X$1" == "X" ]; then
    echo "No file name for split, exiting ..."
    exit 1
fi

if [ ! -f "$1" ]; then
    echo "The file '$1' doesn't exist. exiting ..."
    exit 1
fi

duration=$(ffmpeg -i "$1" 2>&1 | grep Duration | sed 's/^.*Duration: \(.*\)\..., start.*$/\1/' | awk -F: '{ print ($1 * 3600) + ($2 * 60) + $3 }')      #'
split_time=${split_time:-55}
time=0
part=1

filename=${file%%.*}
postfix=${file##*.}

while [ ${time} -le ${duration} ]; do

echo    ffmpeg -i "$1" -vcodec copy -ss ${time} -t ${split_time} "${filename}-${part}.${postfix}"
    (( part++ ))
    (( time = time + split_time ))

done
Alex_Shilli
fonte
0

Basta usar o que está embutido no ffmpeg para fazer exatamente isso.

ffmpeg -i invid.mp4 -threads 3 -vcodec copy -f segment -segment_time 2 cam_out_h264%04d.mp4

Isso o dividirá em mandris de aproximadamente 2 segundos, divididos nos quadros-chave relevantes e resultará nos arquivos cam_out_h2640001.mp4, cam_out_h2640002.mp4, etc.

John Allard
fonte
Não sei se entendi por que esta resposta foi votada. Parece funcionar bem.
Costin