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


63

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 i graffi 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?


Un ringraziamento speciale a @StevenD per aver ospitato i nuovi tag.
Gabriel,

Risposte:


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

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

Fai attenzione che se provi a calcolare il numero di iterazioni in base alla durata dell'output di una ffprobechiamata, questo è stimato dalla velocità in bit media all'inizio della clip e dalle dimensioni del file della clip a meno che tu non dia l' -count_framesargomento, il che rallenta notevolmente il suo funzionamento .

Un'altra cosa da tenere presente è che la posizione -ssdell'opzione sulla riga di comando è importante . 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 a ffmpeg, dicendogli di far passare l'A / V codificato non toccato 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
...

L' -c copyargomento gli 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 copyo ai flag di 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 -c:v copy -c:a aacper 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.


1
"tagliare esattamente 10 minuti per ogni clip" è un buon punto.
Chris,

magari usando il parametro -show_packets puoi renderlo più preciso.
rogerdpack,

come mettere sopra in un ciclo?
kRazzy R,

Io uso questo cmd ya si è diviso a destra ma ora il video e l'audio non sono in SYNC alcun aiuto
Sunil Chaudhary

@SunilChaudhary: i formati di file A / V sono complessi e non tutti i software li implementano allo stesso modo, quindi le informazioni ffmpegdevono mantenere la sincronizzazione tra i flussi audio e video potrebbero non essere presenti nel file sorgente. Se ti sta succedendo, ci sono metodi più complicati di divisione che evitano il problema.
Warren Young,

53

Ecco la soluzione a una linea :

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. Taglia invece 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 1come indicato nei commenti.

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

2
In realtà ti dà divisioni molto accurate, se apprezzi la qualità del video. Invece di suddividere in base a un determinato momento, si divide sul fotogramma chiave più vicino dopo il tempo richiesto, quindi ogni nuovo segmento inizia sempre con un fotogramma chiave.
Malvineous,

3
quali sono le unità? 8s? 8 minuti? 8h?
user1133275

1
@utente1133275 secondo
Jon

2
Su Mac, ho scoperto che questo portava a blocchi video in uscita N, ma solo il primo era un MP4 valido e visualizzabile. Gli altri pezzi N-1 erano video in bianco (tutto nero) senza audio. Per farlo funzionare, ho dovuto aggiungere il flag reset_timestamps in questo modo: ffmpeg -i input.mp4 -c copy -map 0 -segment_time 8 -f segment -reset_timestamps 1 output% 03d.mp4.
jarmod,

3
-reset_timestamps 1ho scoperto che l'aggiunta risolve il problema per me
jlarsch,

6

Ho affrontato lo stesso problema in precedenza e ho creato un semplice script Python per farlo (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)

1
La prossima volta fallo per favore in un commento. Le risposte solo link non sono molto apprezzate qui, e lo stesso se pubblicizzi il tuo sito. Se si tratta di un progetto opensource con codice sorgente, forse è un'eccezione, ma ora ho rischiato i miei privilegi di revisione non votando per la rimozione della tua risposta. E sì, non puoi pubblicare commenti, ma dopo aver raccolto 5 voti (che sembrano molto veloci nel tuo caso) lo farai.
user259412

2
Ciao e benvenuto nel sito. Si prega di non pubblicare risposte solo link. Mentre il tuo stesso script probabilmente darebbe un'ottima risposta, un link non è una risposta. È un cartello che indica una risposta. Maggiori informazioni qui . Dato che hai gentilmente fornito il link, sono andato avanti e ho incluso la sceneggiatura nel corpo della tua risposta. Se ci si oppone, si prega di eliminare la risposta del tutto.
terdon

4

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

Questa dovrebbe essere la risposta accettata. Grazie per aver condiviso amico!
Stephan Ahlf,

4

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

Ecco la fonte e l'elenco dei comandi FFmpeg comunemente usati.


3

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

$ man ffmpeg | grep -C1 position

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

Riferimenti qui e qui .


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

0

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.


Non sono sicuro di capire perché questa risposta sia stata votata in basso. Sembra che funzioni bene.
Costin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.