¿Cómo puedo usar ffmpeg para dividir video MPEG en fragmentos de 10 minutos?

63

A menudo es necesario que la comunidad de desarrolladores de código abierto o activo publique grandes segmentos de video en línea. (Videos de encuentros, campamentos, charlas tecnológicas ...) Siendo que soy un desarrollador y no un camarógrafo, no deseo desembolsar el rasguño adicional en una cuenta premium de Vimeo. ¿Cómo puedo tomar un video de charla técnica MPEG de 12.5 GB (1:20:00) y dividirlo en segmentos de 00:10:00 para cargarlo fácilmente en sitios para compartir videos?

Gabriel
fuente
Un agradecimiento especial a @StevenD por acomodar las nuevas etiquetas.
Gabriel

Respuestas:

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

Envolver esto en un script para hacerlo en un bucle no sería difícil.

Tenga en cuenta que si intenta calcular el número de iteraciones en función de la duración de salida de una ffprobellamada, esto se estima a partir de la tasa de bits promedio al inicio del clip y el tamaño del archivo del clip a menos que proporcione el -count_framesargumento, lo que ralentiza considerablemente su funcionamiento .

Otra cosa a tener en cuenta es que la posición de la -ssopción en la línea de comando es importante . Donde lo tengo ahora es lento pero preciso. La primera versión de esta respuesta dio la alternativa rápida pero imprecisa . El artículo vinculado también describe una alternativa en su mayoría rápida pero precisa, que paga con un poco de complejidad.

Aparte de eso, no creo que realmente quieras cortar exactamente 10 minutos para cada clip. Eso pondrá cortes justo en el medio de las oraciones, incluso las palabras. Creo que deberías usar un editor o reproductor de video para encontrar puntos de corte naturales a solo 10 minutos de diferencia.

Suponiendo que su archivo está en un formato que YouTube puede aceptar directamente, no tiene que volver a codificar para obtener segmentos. Simplemente pase las compensaciones del punto de corte natural a ffmpeg, diciéndole que pase el A / V codificado sin tocarlo usando el códec "copiar":

$ 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
...

El -c copyargumento le dice que copie todas las secuencias de entrada (audio, video y potencialmente otras, como subtítulos) en la salida tal como está. Para programas de A / V simples, es equivalente a las banderas más detalladas -c:v copy -c:a copyo las banderas de estilo antiguo -vcodec copy -acodec copy. Usaría el estilo más detallado cuando quiera copiar solo una de las transmisiones, pero vuelva a codificar la otra. Por ejemplo, hace muchos años había una práctica común con los archivos QuickTime para comprimir el video con video H.264 pero dejar el audio como PCM sin comprimir ; si se encuentra con un archivo de este tipo hoy, podría modernizarlo -c:v copy -c:a aacpara reprocesar solo la transmisión de audio, dejando el video intacto.

El punto de inicio para cada comando anterior después del primero es el punto de inicio del comando anterior más la duración del comando anterior.

Warren Young
fuente
1
"cortar exactamente 10 minutos para cada clip" es un buen punto.
Chris
quizás al usar el parámetro -show_packets puedes hacerlo más preciso.
rogerdpack
¿Cómo poner arriba en un bucle?
kRazzy R
Yo uso este cmd ya se dividió en la derecha, pero ahora el video y el audio no están en SYNC ninguna ayuda
Sunil Chaudhary
@SunilChaudhary: los formatos de archivo A / V son complejos y no todo el software los implementa de la misma manera, por lo que la información ffmpegnecesita mantener la sincronización entre las transmisiones de audio y video puede no estar presente en el archivo fuente. Si esto le está sucediendo, existen métodos más complicados de división que evitan el problema.
Warren Young
53

Aquí está la solución de una línea :

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

Tenga en cuenta que esto no le ofrece divisiones precisas, pero debe adaptarse a sus necesidades. En cambio, se cortará en el primer fotograma después del tiempo especificado después segment_time, en el código anterior sería después de la marca de 20 minutos.

Si encuentra que solo se puede reproducir el primer fragmento, intente agregar -reset_timestamps 1como se menciona en los comentarios.

ffmpeg -i input.mp4 -c copy -map 0 -segment_time 00:20:00 -f segment -reset_timestamps 1 output%03d.mp4
Jon
fuente
2
En realidad, le ofrece divisiones muy precisas, si valora la calidad del video. En lugar de dividirse en función de un tiempo particular, se divide en el fotograma clave más cercano después del tiempo solicitado, por lo que cada nuevo segmento siempre comienza con un fotograma clave.
Malvineous
3
¿Cuáles son las unidades? 8s? 8min? 8h?
user1133275
1
@ user1133275 su segundo
Jon
2
En Mac, descubrí que esto daba como resultado N fragmentos de video de salida, pero solo el primero de ellos era un MP4 válido y visible. Los otros fragmentos N-1 eran video en blanco (todo negro) sin audio. Para que funcione, necesitaba agregar el indicador reset_timestamps así: ffmpeg -i input.mp4 -c copy -map 0 -segment_time 8 -f segmento -reset_timestamps 1 salida% 03d.mp4.
jarmod 05 de
3
descubrí que agregar -reset_timestamps 1soluciona el problema para mí
jlarsch
6

Enfrenté el mismo problema anteriormente y armé un script simple de Python para hacer exactamente eso (usando FFMpeg). Disponible aquí: https://github.com/c0decracker/video-splitter , y pegado a continuación:

#!/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
fuente
1
La próxima vez haz esto por favor en un comentario. Las respuestas solo de enlace realmente no son del agrado aquí, y lo mismo si anuncia su sitio. Si es un proyecto de código abierto con código fuente, tal vez sea una excepción, pero ahora arriesgué mis privilegios de revisión al no votar por la eliminación de su respuesta. Y sí, no puede publicar comentarios, pero después de reunir 5 votos a favor (lo que parece muy rápido en su caso) lo hará.
user259412
2
Hola y bienvenidos al sitio. Por favor, no publique enlaces solo respuestas. Si bien su script en sí mismo probablemente sea una gran respuesta, un enlace a él no es una respuesta. Es una señal que apunta a una respuesta. Más sobre eso aquí . Como amablemente le diste el enlace, seguí adelante e incluí el guión en el cuerpo de tu respuesta. Si se opone a eso, elimine la respuesta por completo.
terdon
4

Si desea crear realmente los mismos fragmentos, debe forzar ffmpeg para crear i-frame en el primer cuadro de cada fragmento para que pueda usar este comando para crear un fragmento de 0.5 segundos.

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
fuente
Esta debería ser la respuesta aceptada. Gracias por compartir amigo!
Stephan Ahlf
4

Una forma alternativa más legible sería

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
*/

Aquí está la fuente y la lista de los comandos FFmpeg utilizados comúnmente.

Niket Pathak
fuente
3

Tenga en cuenta que la puntuación exacta del formato alternativo es -ss mm:ss.xxx. Luché durante horas tratando de usar el intuitivo pero incorrecto mm:ss:xxen vano.

$ man ffmpeg | grep -C1 position

-ss position
Busca la posición de tiempo dada en segundos. La sintaxis "hh: mm: ss [.xxx]" también es compatible.

Referencias aquí y aquí .

Mark Hudson
fuente
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
fuente
0

Simplemente use lo que está integrado en ffmpeg para hacer exactamente esto.

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

Esto lo dividirá en mandriles de aproximadamente 2 segundos, se dividirá en los fotogramas clave relevantes y generará los archivos cam_out_h2640001.mp4, cam_out_h2640002.mp4, etc.

John Allard
fuente
No estoy seguro de entender por qué se vota esta respuesta. Parece que funciona bien.
Costin