Wie kann ich mit ffmpeg MPEG-Videos in 10-Minuten-Blöcke aufteilen?

63

In der Open Source- oder aktiven Entwickler-Community besteht häufig die Notwendigkeit, große Videosegmente online zu veröffentlichen. (Meet-up-Videos, Campouts, Technikgespräche ...) Da ich ein Entwickler und kein Videofilmer bin, habe ich keine Lust, die zusätzlichen Kratzer auf einem Premium-Vimeo-Konto herauszuschleudern. Wie nehme ich dann ein 12,5 GB (1:20:00) großes MPEG-Tech-Talk-Video und schneide es in 00:10:00 Segmente, um es einfach auf Videoplattformen hochzuladen?

Gabriel
quelle
Besonderer Dank geht an @StevenD für die Aufnahme der neuen Tags.
Gabriel

Antworten:

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

Es wäre nicht schwer, dies in ein Skript zu packen, um es in einer Schleife zu tun.

Wenn Sie versuchen, die Anzahl der Iterationen auf der Grundlage der von einem ffprobeAufruf ausgegebenen Dauer zu berechnen , wird dies aus der durchschnittlichen Bitrate am Anfang des Clips und der Dateigröße des Clips geschätzt, es sei denn, Sie geben das -count_framesArgument an, das seine Operation erheblich verlangsamt .

Eine andere Sache zu beachten ist , dass die Position der -ssOption auf die Kommandozeile Angelegenheiten . Wo ich es jetzt habe, ist langsam aber genau. Die erste Version dieser Antwort ergab die schnelle, aber ungenaue Alternative. Der verlinkte Artikel beschreibt auch eine meist schnelle, aber dennoch genaue Alternative, die Sie mit ein wenig Aufwand bezahlen.

Abgesehen davon denke ich nicht, dass Sie wirklich genau 10 Minuten für jeden Clip schneiden möchten. Das wird Schnitte genau in die Mitte von Sätzen setzen, sogar Wörter. Ich denke, Sie sollten einen Video-Editor oder -Player verwenden, um natürliche Schnittpunkte zu finden, die nur 10 Minuten voneinander entfernt sind.

Angenommen, deine Datei hat ein Format, das YouTube direkt akzeptieren kann, dann musst du nicht neu codieren, um Segmente zu erhalten. ffmpegÜbergeben Sie einfach die natürlichen Schnittpunkt-Offsets an , und weisen Sie sie an, die codierten A / V-Dateien mit dem Codec "copy" unberührt weiterzuleiten:

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

Das -c copyArgument weist es an, alle Eingabestreams (Audio, Video und möglicherweise andere, z. B. Untertitel) unverändert in die Ausgabe zu kopieren. Für einfache A / V-Programme entspricht dies den ausführlicheren Flags -c:v copy -c:a copyoder den Flags im alten Stil -vcodec copy -acodec copy. Sie würden den ausführlicheren Stil verwenden, wenn Sie nur einen der Streams kopieren, aber den anderen neu codieren möchten. Vor vielen Jahren gab es beispielsweise bei QuickTime-Dateien die gängige Praxis, das Video mit H.264-Video zu komprimieren, das Audio jedoch als unkomprimiertes PCM zu belassen . Wenn Sie heute auf eine solche Datei gestoßen sind, können Sie sie so modernisieren -c:v copy -c:a aac, dass nur der Audiostream erneut verarbeitet wird und das Video unberührt bleibt.

Der Startpunkt für jeden Befehl über dem ersten ist der Startpunkt des vorherigen Befehls plus der Dauer des vorherigen Befehls.

Warren Young
quelle
1
"Schneiden bei genau 10 Minuten für jeden Clip" ist ein guter Punkt.
Chris
Vielleicht können Sie den Parameter -show_packets verwenden, um ihn genauer zu machen.
Rogerdpack
wie man oben in eine Schleife setzt?
kRazzy R
i Verwenden Sie diese cmd ya es aufgeteilt in rechts, aber jetzt das Video und Audio sind nicht auf SYNC keine Hilfe
Sunil Chaudhary
@SunilChaudhary: A / V-Dateiformate sind komplex und werden nicht von allen Programmen auf dieselbe Weise implementiert, sodass die Informationen ffmpegzur Aufrechterhaltung der Synchronisation zwischen Audio- und Videostream möglicherweise nicht in der Quelldatei vorhanden sind. In diesem Fall gibt es kompliziertere Aufteilungsmethoden, mit denen Sie das Problem umgehen können.
Warren Young
53

Hier ist die einzeilige Lösung :

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

Bitte beachten Sie, dass dies keine genauen Aufteilungen ergibt, sondern Ihren Anforderungen entsprechen sollte. Es wird stattdessen beim ersten Frame nach der angegebenen Zeit abgeschnitten segment_time, im obigen Code nach der 20-Minuten-Marke.

Wenn Sie feststellen, dass nur der erste Block wiedergegeben werden kann, fügen Sie ihn -reset_timestamps 1wie in den Kommentaren beschrieben hinzu.

ffmpeg -i input.mp4 -c copy -map 0 -segment_time 00:20:00 -f segment -reset_timestamps 1 output%03d.mp4
Jon
quelle
2
Tatsächlich erhalten Sie sehr genaue Aufteilungen, wenn Sie Wert auf Videoqualität legen. Anstatt nach einer bestimmten Zeit aufzuteilen, wird nach der angeforderten Zeit der nächste Keyframe aufgeteilt, sodass jedes neue Segment immer mit einem Keyframe beginnt.
Malvineous
3
Was sind die Einheiten? 8s? 8min? 8h?
user1133275
1
@ user1133275 seine zweite
Jon
2
Unter Mac stellte ich fest, dass dies zu N ausgegebenen Videoblöcken führte, aber nur der erste von ihnen war ein gültiger, sichtbarer MP4. Die anderen N-1-Stücke waren leere Videos (alle schwarz) ohne Audio. Damit es funktioniert, musste ich das Flag reset_timestamps wie folgt hinzufügen: ffmpeg -i input.mp4 -c copy -map 0 -segment_time 8 -f segment -reset_timestamps 1 output% 03d.mp4.
Jarmod
3
festgestellt, dass das Hinzufügen -reset_timestamps 1das Problem für mich behebt
jlarsch
6

Wir hatten früher das gleiche Problem und stellten ein einfaches Python-Skript zusammen, um genau das zu erreichen (mit FFMpeg). Verfügbar hier: https://github.com/c0decracker/video-splitter und eingefügt unten:

#!/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
quelle
1
Mach das nächste Mal bitte einen Kommentar dazu. Nur-Link-Antworten werden hier nicht wirklich gemocht, und dasselbe gilt, wenn Sie für Ihre Website werben. Wenn es sich um ein Open Source-Projekt mit Quellcode handelt, ist dies möglicherweise eine Ausnahme, aber ich habe jetzt meine Überprüfungsrechte riskiert, indem ich nicht für das Entfernen Ihrer Antwort gestimmt habe. Und ja, Sie können keine Kommentare posten, aber nachdem Sie 5 positive Stimmen gesammelt haben (was in Ihrem Fall sehr schnell zu sein scheint), werden Sie es tun.
user259412
2
Hallo und willkommen auf der Seite. Bitte posten Sie nicht nur Antworten. Während Ihr Skript selbst wahrscheinlich eine gute Antwort liefern würde, ist ein Link darauf keine Antwort. Es ist ein Wegweiser , der auf eine Antwort verweist . Mehr dazu hier . Da Sie den Link freundlicherweise angegeben haben, habe ich das Skript in den Text Ihrer Antwort aufgenommen. Wenn Sie dem widersprechen, löschen Sie bitte die Antwort vollständig.
Terdon
4

Wenn Sie wirklich dieselben Chunks erstellen möchten, müssen Sie ffmpeg zwingen, einen i-Frame auf dem ersten Frame jedes Chunks zu erstellen, damit Sie diesen Befehl zum Erstellen eines 0,5-Sekunden-Chunks verwenden können.

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
quelle
Dies sollte die akzeptierte Antwort sein. Danke, dass du Kumpel teilst!
Stephan Ahlf
4

Eine Alternative mehr lesbar Weg wäre,

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

Hier ist die Quelle und Liste der am häufigsten verwendeten FFmpeg-Befehle.

Niket Pathak
quelle
3

Beachten Sie, dass die genaue Zeichensetzung des alternativen Formats lautet -ss mm:ss.xxx. Ich kämpfte stundenlang darum, das Intuitive-aber-Falsche nutzlos zu gebrauchen mm:ss:xx.

$ man ffmpeg | grep -C1 position

-ss Position
Sucht nach einer bestimmten Zeitposition in Sekunden. Die Syntax "hh: mm: ss [.xxx]" wird ebenfalls unterstützt.

Referenzen hier und hier .

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

Verwenden Sie einfach das, was in ffmpeg integriert ist, um genau dies zu tun.

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

Dadurch wird es in ca. 2 Sekunden lange Spannfutter aufgeteilt, an den relevanten Keyframes aufgeteilt und in die Dateien cam_out_h2640001.mp4, cam_out_h2640002.mp4 usw. ausgegeben.

John Allard
quelle
Ich verstehe nicht, warum diese Antwort abgelehnt wurde. Es scheint gut zu funktionieren.
Costin