Как я могу использовать ffmpeg для разделения видео MPEG на 10-минутные куски?


63

В сообществе разработчиков программного обеспечения с открытым исходным кодом или в активном сообществе разработчиков часто возникает необходимость публиковать большие видеофрагменты в Интернете. (Видео для встреч, лагеря, технические разговоры ...) Поскольку я являюсь разработчиком, а не видеографом, у меня нет желания раскошелиться на дополнительную премию в премиум-аккаунте Vimeo. Как мне тогда взять техническое видео MPEG 12,5 ГБ (1:20:00) и разделить его на сегменты 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 без изменений, используя кодек «copy»:

$ 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, да, он разделен на правильные, но теперь видео и аудио не помогают SYNC
Sunil Chaudhary

@SunilChaudhary: форматы файлов аудио / видео являются сложными, и не все программное обеспечение реализует их одинаково, поэтому информация, 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
Это на самом деле дает вам очень точное разделение, если вы цените качество видео. Вместо разделения на основе определенного времени он разделяется на ближайший ключевой кадр после запрошенного времени, поэтому каждый новый сегмент всегда начинается с ключевого кадра.
Мальвинус

3
какие подразделения? 8s? 8min? 8h?
user1133275

1
@ user1133275 его второй
Jon

2
На Mac я обнаружил, что это привело к N выходным фрагментам видео, но только 1-й из них был действительным, просматриваемым MP4. Другие фрагменты N-1 были чистым видео (все черным) без звука. Чтобы это работало, мне нужно было добавить флаг reset_timestamps следующим образом: ffmpeg -i input.mp4 -c copy -map 0 -segment_time 8 -f сегмент -reset_timestamps 1 выход% 03d.mp4.
Джармод

3
обнаружил, что добавление -reset_timestamps 1
решает

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 голосов (что кажется очень быстрым в вашем случае), вы это сделаете.
user259412

2
Привет и добро пожаловать на сайт. Пожалуйста, не размещайте ссылки только ответы. Хотя ваш сценарий, вероятно, мог бы дать отличный ответ, ссылка на него не является ответом. Это указатель, указывающий на ответ. Подробнее об этом здесь . Поскольку вы любезно дали ссылку, я включил сценарий в текст вашего ответа. Если вы возражаете против этого, пожалуйста, полностью удалите ответ.
Тердон

4

Если вы хотите создать действительно одинаковые чанки, необходимо заставить ffmpeg создавать i-кадр в первом фрейме каждого чанка, чтобы вы могли использовать эту команду для создания 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

Alternate более читаемым способом будет

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 position Поиск текущей позиции
в секундах. Синтаксис "чч: мм: сс [.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.