it-swarm.it

Come posso usare ffmpeg per dividere il video MPEG in blocchi di 10 minuti?

Spesso c'è bisogno nella comunità di sviluppatori attivi o open source di pubblicare grandi segmenti video online. (Video Meet-up, campeggi, conferenze tecnologiche ...) Essendo che sono uno sviluppatore e non un videografo, non ho alcun desiderio di sborsare il graffio extra su un account Vimeo premium. In che modo, quindi, posso prendere un video di conversazione tecnica MPEG da 12,5 GB (1:20:00) e suddividerlo in segmenti 00:10:00 per caricarlo facilmente su siti di condivisione video?

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

Avvolgere questo in uno script per farlo in un ciclo non sarebbe difficile.

Fai attenzione se provi a calcolare il numero di iterazioni in base alla durata emessa da una chiamata ffprobe che questo è stimato dalla media velocità in bit all'inizio della clip e dimensioni del file della clip a meno che non si dia -count_frames argomento, che rallenta notevolmente il suo funzionamento.

Un'altra cosa da tenere presente è che la posizione di -ss opzione sulla riga di comando argomenti . Dove l'ho ora è lento ma preciso. La prima versione di questa risposta ha dato l'alternativa veloce ma imprecisa . L'articolo collegato descrive anche un'alternativa per lo più veloce ma ancora accurata, che si paga con un po 'di complessità.

A parte questo, non penso che tu voglia davvero tagliare esattamente 10 minuti per ogni clip. Ciò metterà tagli proprio nel mezzo di frasi, anche di parole. Penso che dovresti usare un editor video o un lettore per trovare punti di taglio naturali a meno di 10 minuti di distanza.

Supponendo che il tuo file sia in un formato che YouTube può accettare direttamente, non è necessario ricodificare per ottenere segmenti. Basta passare gli offset del punto di taglio naturale su ffmpeg, dicendogli di passare l'A/V codificato senza toccarlo utilizzando il codec "copia":

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

Il -c copy argomento dice di copiare tutti i flussi di input (audio, video e potenzialmente altri, come i sottotitoli) nell'output così com'è. Per semplici programmi A/V, è equivalente ai flag più dettagliati -c:v copy -c:a copy o le bandiere vecchio stile -vcodec copy -acodec copy. Utilizzeresti lo stile più dettagliato quando desideri copiare solo uno dei flussi, ma ricodificando l'altro. Ad esempio, molti anni fa c'era una pratica comune con i file QuickTime per comprimere il video con il video H.264 ma lasciare l'audio come PCM non compresso ; se hai incontrato un file del genere oggi, potresti modernizzarlo con -c:v copy -c:a aac per rielaborare solo il flusso audio, lasciando intatto il video.

Il punto iniziale per ogni comando sopra dopo il primo è il punto iniziale del comando precedente più la durata del comando precedente.

72
Warren Young

Ecco la soluzione a una riga :

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

Si noti che questo non ti dà divisioni accurate, ma dovrebbe adattarsi alle tue esigenze. Verrà invece tagliato al primo fotogramma dopo il tempo specificato dopo segment_time, nel codice sopra sarebbe dopo il segno dei 20 minuti.

Se scopri che è giocabile solo il primo pezzo, prova ad aggiungere -reset_timestamps 1 come indicato nei commenti.

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

Di fronte allo stesso problema in precedenza e messo insieme un semplice Python per fare proprio questo (usando FFMpeg). Disponibile qui: https://github.com/c0decracker/video-splitter e incollato di seguito:

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

Un modo alternativo più leggibile sarebbe

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

Here l'origine e l'elenco dei comandi FFmpeg comunemente usati.

4
Niket Pathak

Se vuoi creare blocchi davvero uguali, devi forzare ffmpeg a creare i-frame sul primo frame di ogni blocco in modo da poter usare questo comando per creare blocchi di 0,5 secondi.

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
4

Nota che la punteggiatura esatta del formato alternativo è -ss mm:ss.xxx. Ho lottato per ore cercando di usare l'intuitivo-ma-sbagliato mm:ss:xx inutilmente.

$ man ffmpeg | grep -C1 position

-ss posizione
Cerca la posizione temporale indicata in secondi. È supportata anche la sintassi "hh: mm: ss [.xxx]".

Riferimenti qui e qui .

3
Mark Hudson

Basta usare ciò che è incorporato in ffmpeg per fare esattamente questo.

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

Questo lo dividerà in mandrini di circa 2 secondi, diviso nei relativi fotogrammi chiave e verrà emesso nei file cam_out_h2640001.mp4, cam_out_h2640002.mp4, ecc.

1
John Allard
#!/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
0
Alex_Shilli