Вопрос или проблема
У меня есть длинный файл MKV, который я хочу разделить на отдельные главы.
Запуск ffmpeg -i long.mkv
дает мне всю информацию о главах, встроенных в файл:
Duration: 01:23:45.80, start: 0.000000, bitrate: 8116 kb/s
Chapter #0.0: start 0.000000, end 235.000000
Metadata:
title : Chapter 01
Chapter #0.1: start 235.000000, end 450.160000
Metadata:
title : Chapter 02
Chapter #0.2: start 450.160000, end 789.400000
...
В файле 10 глав – я хочу в итоге получить 10 отдельных файлов.
Похоже, что -map_chapters
может сделать что-то похожее, но я не могу найти документацию по этому поводу.
разделение MKV видео на главы с использованием mkvmerge
mkvmerge -o output.mkv --split chapters:all input.mkv
https://www.bunkus.org/videotools/mkvtoolnix/doc/mkvmerge.html
Я не могу найти надежный способ сделать это с помощью ffmpeg / avconv, но я могу найти способ сделать это с помощью HandBrakeCLI.
HandBrakeCLI -c 3 -i whatever.mkv -o 3.mkv
Этот код извлечет 3-ю главу из mkv
.
решение в лоб, хехе:
ffmpeg -i long.mkv | grep 'start.*end.*[0-9]*' | sed -r 's/.*#[0-9]\.([0-9]*).* ([0-9]*\.[0-9]*).*( [0-9]*\.[0-9]*)/ ffmpeg -i long.mkv -ss \2 -to\3 -acodec copy -vcodec copy chapter\1.mkv/g;'
Вы можете добавить xargs для выполнения вывода в ковбойском стиле:
| xargs -I cmd bash -c 'cmd'
Это мое решение, оно хорошо работает на ubuntu-16.04.02-LTS. Оно основано на другом решении, но улучшено в части обработки глав и созданных файлов для каждой главы.
Вот пример выполнения:
$ mkv-split-chapters some-mkv-file.mkv
Filename: some-mkv-file
Extension: mkv
Filedir: .
ffmpeg -i some-mkv-file.mkv -ss 0.000000 -to 394.800000 -acodec copy -vcodec copy ./some-mkv-file-#00.mkv
[...]
ffmpeg -i some-mkv-file.mkv -ss 394.800000 -to 767.160000 -acodec copy -vcodec copy ./some-mkv-file-#01.mkv
[...]
ffmpeg -i some-mkv-file.mkv -ss 757.160000 -to 1216.720000 -acodec copy -vcodec copy ./some-mkv-file-#02.mkv
[...]
Вот скрипт:
$ cat /usr/local/bin/mkv-split-chapters
#!/bin/bash
file="$1"
if [ -z "$file" ]; then
echo "Missing file argument!"
exit 1
fi
filename=$(basename "$file")
fileextension="${filename##*.}"
filename="${filename%.*}"
filedir=$(dirname "$file")
echo "Filename: $filename"
echo "Extension: $fileextension"
echo "Filedir: $filedir"
ffmpeg -i $file 2>&1 | grep 'Chapter' | grep 'start' | grep ', end' | awk "{
chapter=\$2
# replace : with nil
gsub(/:/, \"\", chapter)
start=\$4
# remove everything but 0-9.
gsub(/[^0123456789\.]/, \"\", start)
end=\$6
command=sprintf(\"ffmpeg -i $file -ss %s -to %s -acodec copy -vcodec copy $filedir/$filename-%s.$fileextension\n\", start, end, chapter)
print(command)
system(command)
}"
Скрипт также доступен здесь:
Вы можете использовать программу с графическим интерфейсом с открытым исходным кодом под названием LosslessCut
. Она позволяет без потерь разделять популярные форматы, такие как mp4 и mkv, мгновенно, без перекодирования видео. Работает очень хорошо.
Улучшая ответ @gabriel_agm, вот моя вспомогательная функция. Моя версия ffmpeg использует двоеточие вместо точки для разделения title:chapter, и выводит полезную информацию на stderr. Я также добавляю извлечение субтитров.
split_by_chapter() {
for word in "${@}" ;
do
ffmpeg -i "${word}" 2>&1 | sed -n -r -e "/start.*end.*[0-9]*/{s/.*#[0-9]*:([0-9]*).* ([0-9]*\.[0-9]*).*( [0-9]*\.[0-9]*)/ffmpeg -i ${word} -ss \2 -to\3 -acodec copy -vcodec copy -scodec copy ${word}-chapter\1.mkv \;/g;p;}"
done
}
Чтобы выполнить вывод, вам нужно сделать следующее:
$( split_by_chapter file1.mkv file2.mkv file3.mkv )
Что, естественно, создаст файлы вида:
$ ls file*mkv
file1-chapter0.mkv file1-chapter1.mkv file1-chapter2.mkv
file2-chapter0.mkv file2-chapter1.mkv file3-chapter0.mkv
Ограничения из других комментариев о ограничениях ключевых кадров все еще применимы, но это допустимо.
FFmpeg не может напрямую разделить файлы по главам, так что вам придется либо использовать другой инструмент (например, mkvmerge
, если это mkv, см. ответ Endoro), либо использовать скрипт для выполнения этого в два этапа: сначала извлечь временные метки глав с помощью ffprobe
, затем передать временные метки в ffmpeg
, чтобы разделить файл.
Учтите, что другие ответы полагаются на разбор читаемого человеком вывода ffmpeg, что менее надежно.
Вы можете получить информацию о главах в формате json с помощью ffprobe -hide_banner -v warning -output_format json -show_chapters $video_with_chapters
-hide_banner
скрывает информацию о версии/сборке, которую ffmpeg/ffprobe обычно печатает при каждом вызове-v warning
показывает только предупреждения или более серьезные сообщения в stderr-output_format json
выводит данные в формате json-show_chapters
показывает информацию о главах в stdout в соответствии сoutput_format
stdout будет выглядеть примерно так:
{
"chapters": [
{
"id": 3673034544977082062,
"time_base": "1/1000000000",
"start": 0,
"start_time": "0.000000",
"end": 270000000000,
"end_time": "270.000000",
"tags": {
"title": "Chapter 01"
}
},
{
"id": -8793060959530591199,
"time_base": "1/1000000000",
"start": 270000000000,
"start_time": "270.000000",
"end": 300000000000,
"end_time": "300.000000",
"tags": {
"title": "Chapter 02"
}
}
]
}
Затем, чтобы вырезать одну главу, передайте start_time в -ss
(перейти к этой позиции) и end_time в -to
(декодировать до этой позиции) как входные параметры (до -i
):
ffmpeg -ss 270.000000 -to 300.000000 -i long.mkv -c copy output-chapter-2.mkv
Вот полный скрипт:
#!/usr/bin/env bash
set -e
video_with_chapters="$1"
video_basename="$(basename -- "$video_with_chapters")"
output_prefix="chapter-"
output_extension=".${video_basename##*.}"
chapters_json="$(ffprobe -hide_banner -v warning -output_format json -show_chapters "$video_with_chapters")"
chapter_count="$(echo "$chapters_json" | jq ".chapters|length")"
if (( "$chapter_count" == 0 )); then
echo "$0: no chapters in $video_with_chapters" >&2
exit 1
fi
for ((i=0; i < "$chapter_count"; i++ )); do
chapter_start="$(echo "$chapters_json" | jq ".chapters[$i].start_time" -r)"
chapter_end="$(echo "$chapters_json" | jq ".chapters[$i].end_time" -r)"
if [[ "$chapter_start" == "$chapter_end" ]]; then
// Некоторые файлы имеют главы в виде диапазонов временных меток, в то время как другие файлы имеют пустые диапазоны, и главы отмечают точку во времени.
if (( "$i" + 1 == "$chapter_count" )); then
chapter_end=
else
chapter_end="$(echo "$chapters_json" | jq ".chapters[$((i+1))].end_time" -r)"
fi
fi
input_args=(-ss "$chapter_start")
if [[ -n "$chapter_end" ]]; then
input_args+=(-to "$chapter_end")
fi
output_fn="${output_prefix}$(printf "%03d" "$i")${output_extension}"
ffmpeg \
-hide_banner -v warning \
"${input_args[@]}" -i "$video_with_chapters" \
-c copy "$output_fn"
done
echo "Разделение на главы завершено" >&2
Более подробная версия доступна на https://github.com/shelvacu/ffmpeg-chapter-split
Ответ или решение
Разделение MKV файла на отдельные главы может быть важной задачей для пользователей, которым необходимо извлечь или организовать видеофрагменты в зависимости от содержания. Это особенно актуально в случаях, когда видеофайл содержит длинный видеоконтент, например сериалы или лекции, разбитые на главы.
Теоретическая часть
Формат MKV (Matroska Video) известен своей мощностью и гибкостью, включая возможность хранения нескольких видео- и аудиопотоков, субтитров и метаданных в одном файле. Потому, разделение файла на основании его встроенных глав может быть весьма полезным, особенно для управления и воспроизведения видеоконтента.
Программа FFmpeg широко известна в сообществе за ее способность обрабатывать файлы различных форматов, включая MKV. В то время как FFmpeg предоставляет всеобъемлющие возможности по манипуляции медиафайлами, ее инструменты напрямую не предназначены для разделения MKV-файлов по главам. Однако с помощью правильного подхода, как используется в некоторых скриптах и методах, описанных ниже, можно все же осуществить желаемую задачу.
Примеры
-
С помощью mkvmerge:
Использованиеmkvmerge
— это наиболее прямой и надежный способ разделения файла MKV на отдельные главы. Команда следующего вида:mkvmerge -o output.mkv --split chapters:all input.mkv
Данная команда автоматически разделяет входной файл
input.mkv
по всем главам, создавая отдельные файлы для каждой главы. -
С помощью FFmpeg:
Еще один способ решения задачи заключается в использовании FFmpeg для извлечения временных меток глав и последующего разделения:-
Получение информации с помощью
ffprobe
:ffprobe -hide_banner -v warning -print_format json -show_chapters long.mkv
Это выводит информацию о главах в формате JSON, затем с помощью скрипта можно извлечь временные коды начала и конца каждой главы.
-
Разделение файлов:
ffmpeg -ss <начало> -to <конец> -i long.mkv -c copy output-chapter-n.mkv
Этот метод требует создания отдельного скрипта для автоматизации процесса извлечения временных меток и вызова FFmpeg для каждого диапазона времени.
-
-
С использованием скриптов:
Для Unix-подобных систем можно использовать shell-скрипты, которые автоматически извлекают все главы и создают отдельные файлы:split_by_chapter() { for file in "${@}" ; do ffmpeg -i "${file}" 2>&1 | sed -n -r -e "/start.*end.*[0-9]*/{...}" done }
Этот метод требует базового понимания работы sed и bash, но предоставляет мощный инструмент для автоматизации процесса.
Применение
Разделение MKV на главы полезно в различных сценариях:
-
Создание обучающих материалов: Лекции или презентации могут быть представлены в более управляемом формате, что облегчает использование и просмотр материала.
-
Проект постпроцесса: Видео могут быть разделены на более мелкие части, чтобы их было проще редактировать или использовать для отдельных проектов.
-
Архивация и организация: Организация мультимедийных библиотек может быть более эффективной, если использовать структуры, основанные на главах.
В конечном итоге, выбор инструмента и метода зависит от предпочтений пользователя и величины файла, но всегда есть возможность автоматизации и оптимизации этого процесса с помощью правильных инструментов. Это позволит не только повысить продуктивность, но и улучшит общее качество управления медиаконтентом.