Jak mogę użyć ffmpeg do podzielenia wideo MPEG na 10-minutowe fragmenty?


63

W społeczności Open Source lub aktywnej społeczności programistów często istnieje potrzeba publikowania dużych segmentów wideo w Internecie. (Filmy wideo o spotkaniach, obozy, rozmowy techniczne ...) Ponieważ jestem programistą, a nie filmowcem, nie chcę rozwiązywać dodatkowych problemów na koncie premium Vimeo. Jak w takim razie wziąć wideo instruktażowe MPEG o pojemności 12,5 GB (1:20:00) i pokroić je na segmenty 00:10:00 w celu łatwego przesłania do witryn udostępniania wideo?


Specjalne podziękowania dla @StevenD za dostosowanie nowych tagów.
Gabriel

Odpowiedzi:


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

Zawinięcie tego w skrypt, aby zrobić to w pętli, nie byłoby trudne.

Uwaga: jeśli spróbujesz obliczyć liczbę iteracji na podstawie czasu trwania ffprobepołączenia, że ​​jest to szacowane na podstawie średniej przepływności na początku klipu i rozmiaru pliku klipu, chyba że podasz -count_framesargument, który znacznie spowalnia jego działanie .

Kolejną rzeczą, o której należy pamiętać, jest to, że pozycja -ssopcji w wierszu poleceń ma znaczenie . To, co mam teraz, jest powolne, ale dokładne. Pierwsza wersja tej odpowiedzi dała szybką, ale niedokładną alternatywę. W linkowanym artykule opisano również najczęściej szybką, ale wciąż dokładną alternatywę, za którą płacisz z odrobiną złożoności.

Poza tym, nie sądzę, że naprawdę chcesz wycinać dokładnie 10 minut dla każdego klipu. To spowoduje umieszczenie cięć w środku zdań, a nawet słów. Myślę, że powinieneś używać edytora wideo lub odtwarzacza, aby znaleźć naturalne punkty odcięcia w odstępie 10 minut.

Zakładając, że Twój plik ma format, który YouTube może bezpośrednio zaakceptować, nie musisz ponownie kodować, aby uzyskać segmenty. Po prostu przekaż naturalne przesunięcia punktu odcięcia do ffmpeg, mówiąc mu, aby przekazało zakodowane A / V przez nietknięte za pomocą kodeka „kopiuj”:

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

-c copyArgumentem informuje go skopiować wszystkie strumienie wejściowe (audio, wideo i potencjalnie innych, takich jak napisami) do wyjścia jak jest. W przypadku prostych programów A / V jest to odpowiednik bardziej szczegółowych flag -c:v copy -c:a copylub flag w starym stylu -vcodec copy -acodec copy. Możesz użyć bardziej pełnego stylu, jeśli chcesz skopiować tylko jeden ze strumieni, ale ponownie zakodować drugi. Na przykład wiele lat temu powszechna była praktyka z plikami QuickTime do kompresji wideo za pomocą wideo H.264, ale pozostawienie dźwięku jako nieskompresowanego PCM ; jeśli dzisiaj natknąłeś się na taki plik, możesz go zmodernizować, -c:v copy -c:a aacaby ponownie przetworzyć tylko strumień audio, pozostawiając nienaruszony film.

Punktem początkowym każdego polecenia powyżej po pierwszym jest punkt początkowy poprzedniego polecenia plus czas trwania poprzedniego polecenia.


1
dobrym pomysłem jest „cięcie dokładnie na 10 minut dla każdego klipu”.
Chris

może za pomocą parametru -show_packets możesz zwiększyć dokładność.
rogerdpack,

jak umieścić powyżej w pętli?
kRazzy R

Używam tego cmd ya ona podzielona na prawo ale teraz wideo i audio nie są na SYNC żadnej pomocy
Sunil Chaudhary

@SunilChaudhary: Formaty plików A / V są złożone i nie wszystkie programy implementują je w ten sam sposób, więc informacje ffmpegmuszą zachować synchronizację między strumieniami audio i wideo w pliku źródłowym. Jeśli tak się dzieje, istnieją bardziej skomplikowane metody podziału, które pozwalają uniknąć problemu.
Warren Young,

53

Oto jedno liniowe rozwiązanie :

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

Pamiętaj, że nie daje to dokładnych podziałów, ale powinno pasować do twoich potrzeb. Zamiast tego będzie ciąć w pierwszej klatce po upływie określonego czasu segment_time, w powyższym kodzie będzie to po znaku 20 minut.

Jeśli stwierdzisz, że można odtwarzać tylko pierwszy fragment, spróbuj dodać, -reset_timestamps 1jak wspomniano w komentarzach.

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

2
W rzeczywistości zapewnia bardzo dokładne podziały, jeśli cenisz jakość wideo. Zamiast dzielić na podstawie określonego czasu, dzieli się na najbliższą klatkę kluczową po żądanym czasie, więc każdy nowy segment zawsze zaczyna się od klatki kluczowej.
Malvineous

3
jakie są jednostki? 8s? 8min? 8h?
user1133275,

1
@ user1133275 jego drugi
Jon

2
Na komputerze Mac odkryłem, że spowodowało to fragmenty wyjściowe wideo N, ale tylko pierwszy z nich był prawidłowym, widocznym MP4. Pozostałe fragmenty N-1 były puste wideo (wszystkie czarne) bez dźwięku. Aby działało, musiałem dodać flagę reset_timestamps w następujący sposób: ffmpeg -i input.mp4 -c copy -map 0 -segment_time 8 -f segment -reset_timestamps 1 wyjście% 03d.mp4.
jarmod

3
okazało się, że dodanie -reset_timestamps 1poprawia dla mnie problem
jlarsch

6

Wcześniej napotkałem ten sam problem i stworzyliśmy prosty skrypt Pythona, aby to zrobić (używając FFMpeg). Dostępne tutaj: https://github.com/c0decracker/video-splitter i wklejone poniżej:

#!/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
Następnym razem zrób to w komentarzu. Odpowiedzi tylko z linkiem nie są tutaj tak naprawdę lubiane, i to samo, jeśli reklamujesz swoją witrynę. Jeśli jest to projekt typu open source z kodem źródłowym, być może jest to wyjątek, ale teraz zaryzykowałem moje uprawnienia do recenzowania, nie głosując za usunięciem twojej odpowiedzi. I tak, nie możesz dodawać komentarzy, ale po zebraniu 5 głosów pozytywnych (co w twoim przypadku wydaje się bardzo szybkie), zrobisz to.
user259412,

2
Witam i witam na stronie. Nie publikuj linków tylko odpowiedzi. Chociaż sam skrypt prawdopodobnie byłby świetną odpowiedzią, link do niego nie jest odpowiedzią. Jest to drogowskaz wskazujący odpowiedź. Więcej na ten temat tutaj . Ponieważ uprzejmie podałeś link, poszedłem dalej i umieściłem skrypt w treści twojej odpowiedzi. Jeśli sprzeciwisz się temu, usuń odpowiedź całkowicie.
terdon

4

Jeśli chcesz stworzyć naprawdę takie same fragmenty, musisz zmusić ffmpeg do utworzenia ramki i na pierwszej ramce każdego fragmentu, abyś mógł użyć tego polecenia do utworzenia 0,5-sekundowego fragmentu.

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

To powinna być zaakceptowana odpowiedź. Dzięki za udostępnienie kolego!
Stephan Ahlf

4

Alternatywny bardziej czytelny sposób byłoby

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

Oto źródło i lista często używanych poleceń FFmpeg.


3

Zwróć uwagę, że dokładna interpunkcja alternatywnego formatu to -ss mm:ss.xxx. Walczyłem godzinami, próbując użyć intuicyjnego, ale błędnego, mm:ss:xxbezskutecznie.

$ man ffmpeg | grep -C1 position

-ss pozycja
Szukaj w określonym czasie w sekundach. Obsługiwana jest również składnia „hh: mm: ss [.xxx]”.

Referencje tutaj i tutaj .


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

Po prostu użyj tego, co jest wbudowane w ffmpeg, aby zrobić dokładnie to.

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

To podzieli go na około 2-sekundowe uchwyty, podzieli na odpowiednie klatki kluczowe i wyśle ​​do plików cam_out_h2640001.mp4, cam_out_h2640002.mp4 itd.


Nie jestem pewien, czy rozumiem, dlaczego przegłosowano tę odpowiedź. Wygląda na to, że działa dobrze.
Costin
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.