Як я можу використовувати ffmpeg для поділу відео MPEG на 10 хвилин?


63

У відкритому коді чи активній спільноті розробників часто є необхідність публікувати великі відео-сегменти в Інтернеті. (Відео про зустрічі, табори, технічні переговори ...) Оскільки я розробник, а не відеограф, я не маю бажання викреслювати зайву подряпину на преміум-акаунті Vimeo. Яким чином я можу взяти 12,5 ГБ (1:20:00) технологічного відео-розмови MPEG і розрізати його на 00:10:00 сегменти для зручного завантаження на сайти обміну відео?


Особлива подяка @StevenD за розміщення нових тегів.
Габріель

Відповіді:


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

Згорнути це в сценарій, щоб зробити це в циклі, було б нелегко.

Будьте уважні, якщо ви спробуєте обчислити кількість ітерацій на основі тривалості виходу з ffprobeвиклику, що це оцінюється від середньої швидкості передачі бітів на початку кліпу та розміру файлу кліпу, якщо ви не наводите -count_framesаргумент, що значно уповільнює його роботу .

Інша річ, яку слід пам’ятати, - це те, що положення -ssпараметра в командному рядку має значення . Де я зараз є, це повільно, але точно. Перша версія цієї відповіді дала швидку, але неточну альтернативу. Зв'язана стаття також описує в основному швидку, але все-таки точну альтернативу, яку ви платите за трохи складності.

Все, що убік, я не думаю, що ви дуже хочете різати рівно 10 хвилин на кожен кліп. Це поставить скорочення прямо в середині речень, навіть слова. Я думаю, що вам слід скористатися відеоредактором або програвачем, щоб знайти природні точки зрізу, просто сором’язливі 10 хвилин.

Якщо припустити, що ваш файл у форматі, який YouTube може приймати безпосередньо, вам не потрібно повторно кодувати, щоб отримати сегменти. Просто переведіть природні зрушення в точку відсікання ffmpeg, кажучи йому пропустити закодований A / V через недоторканий, використовуючи кодек "копіювати":

$ 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 copyАргумент говорить це , щоб скопіювати всі вхідні потоки (аудіо, відео, і потенційно інші, такі як субтитри) на вихід як є. Для простих програм A / V він еквівалентний більш багатослівним прапорам -c:v copy -c:a copyабо прапорам старого стилю -vcodec copy -acodec copy. Ви б використовували більш багатослівний стиль, коли хочете скопіювати лише один з потоків, але перекодувати інший. Наприклад, багато років тому була поширена практика з файлами QuickTime компресувати відео з відео H.264, але аудіо залишати як нестиснений PCM ; якщо ви натрапили на такий файл сьогодні, ви можете його модернізувати, -c:v copy -c:a aacщоб переробити лише аудіопотік, залишивши відео не торканим.

Початковою точкою для кожної команди вище після першої є початкова точка попередньої команди плюс тривалість попередньої команди.


1
"різання рівно за 10 хвилин на кожен кліп" - хороший момент.
Кріс

можливо, використовуючи параметр -show_packets, ви можете зробити його більш точним.
rogerdpack

як покласти вище в петлю?
kRazzy R

Я використовую цей cmd ya, він розділився на права, але тепер відео та аудіо не надають SYNC ніякої допомоги
Sunil Chaudhary

@SunilChaudhary: Формати файлів A / V є складними, і не все програмне забезпечення реалізує їх однаково, так що інформація, ffmpegнеобхідна для підтримки синхронізації між аудіо- та відеопотоками, може не бути у вихідному файлі. Якщо це відбувається з вами, є більш складні методи розщеплення, які уникають проблеми.
Воррен Янг

53

Ось єдине рішення :

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

Зверніть увагу, що це не дає точної розбивки, але повинно відповідати вашим потребам. Замість цього він буде вирізаний на першому кадрі через час, зазначений після segment_time, у коді вище він буде після позначки 20 хвилин.

Якщо ви виявите, що відтворюється лише перший шматок, спробуйте додати, -reset_timestamps 1як зазначено в коментарях.

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

2
Це насправді дає дуже точні розбиття, якщо ви цінуєте якість відео. Замість того, щоб розщеплюватись за певним часом, він розбивається на найближчий ключовий кадр після запиту, тому кожен новий сегмент завжди починається з ключового кадру.
Malvineous

3
які одиниці? 8с? 8хв? 8 год?
користувач1133275

1
@ user1133275 його другий
Jon

2
На Mac я виявив, що це призвело до N вихідних відеороликів, але лише 1-й з них був дійсним MP4, який можна побачити. Інші шматки N-1 були порожніми відео (усі чорні) без звуку. Щоб він працював, мені потрібно було додати прапор reset_timestamps таким чином: ffmpeg -i input.mp4 -c копія -map 0 -segment_time 8 -f segment -reset_timestamps 1 вихід% 03d.mp4.
jarmod

3
виявив, що додавання -reset_timestamps 1виправляє проблему для мене
jlarsch

6

Раніше зіткнулися з тією ж проблемою і зібрали простий скрипт Python, щоб зробити саме це (використовуючи FFMpeg). Доступно тут: https://github.com/c0decracker/video-splitter та вставлено нижче:

#!/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
Наступного разу зробіть це, будь ласка, у коментарі. Відповіді лише на посилання тут не дуже подобаються, і те саме, якщо ви рекламуєте свій сайт. Якщо це проект із відкритим кодом із вихідним кодом, можливо, це виняток, але я зараз ризикував переглядати привілеї, не голосуючи за видалення вашої відповіді. Так, ви не можете розміщувати коментарі, але після того, як ви зібрали 5 оновлень (що здається дуже швидким у вашому випадку), ви зробите це.
користувач259412

2
Привіт і ласкаво просимо на сайт. Будь ласка, не публікуйте лише відповіді на посилання. Хоча ваш сценарій сам, мабуть, дасть чудову відповідь, посилання на нього не є відповіддю. Це покажчик, який вказує на відповідь. Більше про це тут . Оскільки ви люб’язно дали посилання, я пішов вперед і включив сценарій до основи вашої відповіді. Якщо ви заперечуєте проти цього, будь ласка, видаліть відповідь повністю.
terdon

4

Якщо ви хочете створити дійсно такі ж Chunks, потрібно змусити ffmpeg створити i-frame на першому кадрі кожного фрагмента, щоб ви могли використовувати цю команду для створення 0,5 секунди.

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

Альтернативний більш читабельним способом буде

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

Ось джерело та список часто використовуваних команд FFmpeg.


3

Зверніть увагу на точну пунктуацію альтернативного формату -ss mm:ss.xxx. Я боровся годинами, намагаючись використати інтуїтивне, але неправильне, mm:ss:xxбезрезультатно.

$ man ffmpeg | grep -C1 position

-ss позиція
Шукайте задану часову позицію в секундах. "hh: mm: ss [.xxx]" синтаксис також підтримується.

Посилання тут і тут .


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

Просто використовуйте те, що вбудовано у ffmpeg, щоб зробити це саме так.

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

Це розділить його приблизно на 2 секундні патрони, розділиться на відповідні ключові кадри та виведе у файли cam_out_h2640001.mp4, cam_out_h2640002.mp4 тощо.


Не впевнений, я розумію, чому ця відповідь знищена. Здається, це працює добре.
Костін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.