Как объединить два файла MP4 с помощью FFmpeg?
Я пытаюсь объединить два файла mp4 с помощью ffmpeg. Мне нужно, чтобы это был автоматический процесс, поэтому я выбрал ffmpeg. Я конвертирую эти два файла в файлы.ts, затем объединяю их, а затем пытаюсь кодировать этот объединенный файл.ts. Файлы имеют кодировку h264 и aac, и я надеюсь сохранить качество на том же уровне или максимально приближенным к оригинальному.
ffmpeg -i part1.mp4 -vcodec copy -vbsf h264_mp4toannexb -acodec copy part1.ts
ffmpeg -i part2.mp4 -vcodec copy -vbsf h264_mp4toannexb -acodec copy part2.ts
cat part1.ts part2.ts > parts.ts
ffmpeg -y -i parts.ts -acodec copy -ar 44100 -ab 96k -coder ac -vbsf h264_mp4toannexb parts.mp4
К сожалению, я получаю следующее сообщение об ошибке от ffmpeg во время кодирования:
[h264 @ 0x1012600]sps_id out of range
[h264 @ 0x1012600]non-existing SPS 0 referenced in buffering period
[h264 @ 0x1012600]sps_id out of range
[h264 @ 0x1012600]non-existing SPS 0 referenced in buffering period
[NULL @ 0x101d600]error, non monotone timestamps 13779431 >= 13779431kbits/s
av_interleaved_write_frame(): Error while opening file
Это происходит примерно в середине кодирования, что заставляет меня думать, что вы не можете объединить два файла.ts и заставить его работать.
32 ответа
В конечном итоге я использовал mpg в качестве промежуточного формата, и это сработало (ПРИМЕЧАНИЕ: это опасный пример, -qscale 0 перекодирует видео...)
ffmpeg -i 1.mp4 -qscale 0 1.mpg
ffmpeg -i 2.mp4 -qscale 0 2.mpg
cat 1.mpg 2.mpg | ffmpeg -f mpeg -i - -qscale 0 -vcodec mpeg4 output.mp4
FFmpeg имеет три метода конкатенации.
1. Concat видео фильтр
ffmpeg -i opening.mkv -i episode.mkv -i ending.mkv \
-filter_complex "[0:v] [0:a] [1:v] [1:a] [2:v] [2:a] concat=n=3:v=1:a=1 [v] [a]" \
-map "[v]" -map "[a]" output.mkv
Обратите внимание, что этот метод выполняет перекодирование.
2. concat demuxer
$ cat mylist.txt
file '/path/to/file1'
file '/path/to/file2'
file '/path/to/file3'
$ ffmpeg -f concat -i mylist.txt -c copy output
для Windows:
(echo file 'first file.mp4' & echo file 'second file.mp4' )>list.txt
ffmpeg -safe 0 -f concat -i list.txt -c copy output.mp4
3. протокол concat
ffmpeg -i "concat:input1|input2" -codec copy output
Этот метод не работает для многих форматов, включая MP4, из-за природы этих форматов и упрощенной конкатенации, выполняемой этим методом.
Какой использовать
concat filter: используйте, если ваши входные данные не имеют одинаковых параметров (ширина, высота и т. д.), или не имеют одинаковые форматы / кодеки, или если вы хотите выполнить какую-либо фильтрацию. (Вы можете перекодировать только те входные данные, которые не совпадают, чтобы они использовали один и тот же кодек и другие параметры, а затем использовать демультиплексор concat, чтобы избежать перекодирования других входных данных).
concat demuxer: используйте, если вы хотите избежать перекодирования, и ваш формат не поддерживает конкатенацию на уровне файлов (большинство файлов, используемых обычными пользователями, не поддерживают конкатенацию на уровне файлов).
протокол concat: используется с форматами, поддерживающими конкатенацию на уровне файлов (MPEG-1, MPEG-2 PS, DV). Не используйте с MP4.
Если сомневаетесь, попробуйте демоверсию concat.
Также см
ДЛЯ MP4 ФАЙЛОВ
Для файлов .mp4 (которые я получил от DailyMotion.com: 50-минутный телевизионный эпизод, загружаемый только из трех частей, как три видеофайла.mp4), следующее было эффективным решением для Windows 7 и НЕ предполагает перекодирования файлы.
Я переименовал файлы (как file1.mp4, file2.mp4, file3.mp4) так, чтобы части были в правильном порядке для просмотра полного телевизионного эпизода.
Затем я создал простой командный файл (concat.bat) со следующим содержимым:
:: Create File List
echo file file1.mp4 > mylist.txt
echo file file2.mp4 >> mylist.txt
echo file file3.mp4 >> mylist.txt
:: Concatenate Files
ffmpeg -f concat -i mylist.txt -c copy output.mp4
Пакетный файл и ffmpeg.exe должны быть помещены в одну папку с файлами.mp4, которые нужно объединить. Затем запустите пакетный файл. Обычно это займет не более десяти секунд.
,
Приложение (2018/10/21) -
Если то, что вы искали, - это метод указания всех файлов mp4 в текущей папке без повторного ввода, попробуйте это в своем пакетном файле Windows:
:: Create File List
for %%i in (*.mp4) do echo file "%%i">> mylist.txt
Я пытался объединить три .mp3
аудио файлы в один .m4a
файл и эта команда ffmpeg работает.
Входная команда:
ffmpeg -i input1.mp3 -i input2.mp3 -i input3.mp3 -filter_complex concat=n=3:v=0:a=1 -f MOV -vn -y input.m4a
Значения: "-filter_complex concat = n = 3: v = 0: a = 1":
concat означает использование функции конкатенации медиа (соединения).
n означает подтверждение общего количества входных файлов.
v значит есть видео? используйте 0 = нет видео, 1 = содержит видео.
средство имеет аудио? используйте 0 = нет звука, 1 = содержать аудио.
-f означает принудительно установить формат файла (чтобы увидеть все поддерживаемые форматы, используйтеffmpeg -formats
)
-vn означает отключить видео (а также-an
отключил бы аудио если не хотел)
-y означает перезаписать выходные файлы (если выходной файл уже существует).
Для получения дополнительной информации: используйте ffmpeg -h full
распечатать все параметры (включая все параметры формата и кодека, очень длинные)
для файлов MP4:
Если они не совсем одинаковые (100% одинаковый кодек, то же разрешение, тот же тип) MP4-файлы, то сначала вам нужно транскодировать их в промежуточные потоки:
ffmpeg -i myfile1.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts temp1.ts
ffmpeg -i myfile2.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts temp2.ts
// now join
ffmpeg -i "concat:temp1.ts|temp2.ts" -c copy -bsf:a aac_adtstoasc output.mp4
НОТА!: Вывод будет похож на первый файл (а не второй)
Вот быстрый (занимает менее 1 минуты) и способ без потерь, не требующий промежуточных файлов:
ls Movie_Part_1.mp4 Movie_Part_2.mp4 | perl -ne 'print "file $_"' | ffmpeg -f concat -i - -c copy Movie_Joined.mp4
"Ls" содержит файлы для присоединения. "Perl" создает файл конкатенации на лету в канал. Часть "-i -" указывает ffmpeg читать из канала
(обратите внимание - в моих файлах не было пробелов или странных вещей - вам нужно соответствующее экранирование оболочки, если вы хотите реализовать эту идею с "жесткими" файлами).
Я обнаружил, что оператор pipe не работал для меня, когда я использовал вариант 3 для объединения нескольких MP4 на Mac в принятом ответе.
Следующий однострочный текст работает на Mac (High Sierra) для объединения mp4s без создания промежуточного файла.
ffmpeg -f concat -safe 0 -i <(for f in ./*.mp4; do echo "file '$PWD/$f'"; done) -c copy output.mp4
Здесь 2 чистых решения bash, использующих только ffmpeg
и не используя
- промежуточный файл
- perl, python или javascript
Однострочное решение с использованием ls
ls video1.mp4 video2.mp4 | while read line; do echo file \'$line\'; done | ffmpeg -protocol_whitelist file,pipe -f concat -i - -c copy output.mp4
Функция, которая принимает 0 или 2+ аргумента
#######################################
# Merge mp4 files into one output mp4 file
# usage:
# mergemp4 #merges all mp4 in current directory
# mergemp4 video1.mp4 video2.mp4
# mergemp4 video1.mp4 video2.mp4 [ video3.mp4 ...] output.mp4
#######################################
function mergemp4() {
if [ $# = 1 ]; then return; fi
outputfile="output.mp4"
#if no arguments we take all mp4 in current directory as array
if [ $# = 0 ]; then inputfiles=($(ls -1v *.mp4)); fi
if [ $# = 2 ]; then inputfiles=($1 $2); fi
if [ $# -ge 3 ]; then
outputfile=${@: -1} # Get the last argument
inputfiles=(${@:1:$# - 1}) # Get all arguments besides last one as array
fi
# -y: automatically overwrite output file if exists
# -loglevel quiet: disable ffmpeg logs
ffmpeg -y \
-loglevel quiet \
-f concat \
-safe 0 \
-i <(for f in $inputfiles; do echo "file '$PWD/$f'"; done) \
-c copy $outputfile
if test -f "$outputfile"; then echo "$outputfile created"; fi
}
Примечание: пробовал некоторые решения в этой теме, и ни одно меня не удовлетворило
Основной ответ не сработал для меня, потому что он дал мне ошибки, которые я описываю ниже в разделе «Устранение неполадок», поэтому вот мой собственный ответ.
Как объединить или объединить несколько видеофайлов mp4, используя
Краткое резюме
Создайте файл, содержащий список всех путей к файлам для объединения (см. пример ниже). Затем объедините все вышеперечисленные видео в одно, например:
Подробности и полный пример объединения видеозаписи с камеры безопасности Wyze
Протестировано на Ubuntu 20.04, объединив несколько моих видео с камеры безопасности Wyze в одно. (Я подключил карту micro SD камеры прямо к компьютеру).
Создайте файл с именем , содержащий список всех файлов, которые вы хотите объединить. Бывший:
file 'path/to/file1.mp4' file 'path/to/file2.mp4' file 'path/to/file3.mp4'
В моем случае я объединил 69 минут видео с 13 ноября 2022 года в 18:31 (18:31) до 13 ноября 2022 года в 19:39 (19:39). В путях камеры безопасности Wyze это означает от
record/20221113/18/31.mp4'
кrecord/20221113/19/39.mp4
, включительно. Итак, вот мой полныйinputs.txt
файл:file '/media/gabriel/3339-3730/record/20221113/18/31.mp4' file '/media/gabriel/3339-3730/record/20221113/18/32.mp4' file '/media/gabriel/3339-3730/record/20221113/18/33.mp4' file '/media/gabriel/3339-3730/record/20221113/18/34.mp4' file '/media/gabriel/3339-3730/record/20221113/18/35.mp4' file '/media/gabriel/3339-3730/record/20221113/18/36.mp4' file '/media/gabriel/3339-3730/record/20221113/18/37.mp4' file '/media/gabriel/3339-3730/record/20221113/18/38.mp4' file '/media/gabriel/3339-3730/record/20221113/18/39.mp4' file '/media/gabriel/3339-3730/record/20221113/18/40.mp4' file '/media/gabriel/3339-3730/record/20221113/18/41.mp4' file '/media/gabriel/3339-3730/record/20221113/18/42.mp4' file '/media/gabriel/3339-3730/record/20221113/18/43.mp4' file '/media/gabriel/3339-3730/record/20221113/18/44.mp4' file '/media/gabriel/3339-3730/record/20221113/18/45.mp4' file '/media/gabriel/3339-3730/record/20221113/18/46.mp4' file '/media/gabriel/3339-3730/record/20221113/18/47.mp4' file '/media/gabriel/3339-3730/record/20221113/18/48.mp4' file '/media/gabriel/3339-3730/record/20221113/18/49.mp4' file '/media/gabriel/3339-3730/record/20221113/18/50.mp4' file '/media/gabriel/3339-3730/record/20221113/18/51.mp4' file '/media/gabriel/3339-3730/record/20221113/18/52.mp4' file '/media/gabriel/3339-3730/record/20221113/18/53.mp4' file '/media/gabriel/3339-3730/record/20221113/18/54.mp4' file '/media/gabriel/3339-3730/record/20221113/18/55.mp4' file '/media/gabriel/3339-3730/record/20221113/18/56.mp4' file '/media/gabriel/3339-3730/record/20221113/18/57.mp4' file '/media/gabriel/3339-3730/record/20221113/18/58.mp4' file '/media/gabriel/3339-3730/record/20221113/18/59.mp4' file '/media/gabriel/3339-3730/record/20221113/19/00.mp4' file '/media/gabriel/3339-3730/record/20221113/19/01.mp4' file '/media/gabriel/3339-3730/record/20221113/19/02.mp4' file '/media/gabriel/3339-3730/record/20221113/19/03.mp4' file '/media/gabriel/3339-3730/record/20221113/19/04.mp4' file '/media/gabriel/3339-3730/record/20221113/19/05.mp4' file '/media/gabriel/3339-3730/record/20221113/19/06.mp4' file '/media/gabriel/3339-3730/record/20221113/19/07.mp4' file '/media/gabriel/3339-3730/record/20221113/19/08.mp4' file '/media/gabriel/3339-3730/record/20221113/19/09.mp4' file '/media/gabriel/3339-3730/record/20221113/19/10.mp4' file '/media/gabriel/3339-3730/record/20221113/19/11.mp4' file '/media/gabriel/3339-3730/record/20221113/19/12.mp4' file '/media/gabriel/3339-3730/record/20221113/19/13.mp4' file '/media/gabriel/3339-3730/record/20221113/19/14.mp4' file '/media/gabriel/3339-3730/record/20221113/19/15.mp4' file '/media/gabriel/3339-3730/record/20221113/19/16.mp4' file '/media/gabriel/3339-3730/record/20221113/19/17.mp4' file '/media/gabriel/3339-3730/record/20221113/19/18.mp4' file '/media/gabriel/3339-3730/record/20221113/19/19.mp4' file '/media/gabriel/3339-3730/record/20221113/19/20.mp4' file '/media/gabriel/3339-3730/record/20221113/19/21.mp4' file '/media/gabriel/3339-3730/record/20221113/19/22.mp4' file '/media/gabriel/3339-3730/record/20221113/19/23.mp4' file '/media/gabriel/3339-3730/record/20221113/19/24.mp4' file '/media/gabriel/3339-3730/record/20221113/19/25.mp4' file '/media/gabriel/3339-3730/record/20221113/19/26.mp4' file '/media/gabriel/3339-3730/record/20221113/19/27.mp4' file '/media/gabriel/3339-3730/record/20221113/19/28.mp4' file '/media/gabriel/3339-3730/record/20221113/19/29.mp4' file '/media/gabriel/3339-3730/record/20221113/19/30.mp4' file '/media/gabriel/3339-3730/record/20221113/19/31.mp4' file '/media/gabriel/3339-3730/record/20221113/19/32.mp4' file '/media/gabriel/3339-3730/record/20221113/19/33.mp4' file '/media/gabriel/3339-3730/record/20221113/19/34.mp4' file '/media/gabriel/3339-3730/record/20221113/19/35.mp4' file '/media/gabriel/3339-3730/record/20221113/19/36.mp4' file '/media/gabriel/3339-3730/record/20221113/19/37.mp4' file '/media/gabriel/3339-3730/record/20221113/19/38.mp4' file '/media/gabriel/3339-3730/record/20221113/19/39.mp4'
Объедините все вышеперечисленные видео в одно:
# Option 1 (preferred): into a single mp4 video with AAC audio encoding # and original video encoding time ffmpeg -f concat -safe 0 -i inputs.txt -c:v copy -c:a aac output.mp4 # Option 2: into a single .mkv video with original audio and video encoding time ffmpeg -f concat -safe 0 -i inputs.txt -c copy output.mkv
В моем случае исходные 69 файлов занимали 411,1 МБ . Вот результаты двух приведенных выше команд:
-
output.mp4
на создание ушло 11 секунд , а размер — 380,3 МБ . Звук в кодировке AAC, по-видимому, немного меньше. -
output.mkv
на создание ушло 8 секунд , а размер — 410,7 МБ .
Устранение неполадок/возможные ошибки
Если вы запустите
ffmpeg -f concat -i inputs.txt -c copy output.mp4
и получить эту ошибку:[concat @ 0x563ca0173700] Unsafe file name '/media/gabriel/3339-3730/record/20221113/18/31.mp4' inputs.txt: Operation not permitted
... это потому, что вы забыли использовать
-safe 0
.Видеть:
Если вы запустите
ffmpeg -f concat -safe 0 -i inputs.txt -c copy output.mp4
и получить эту ошибку:[mp4 @ 0x55ced96f2100] Could not find tag for codec pcm_alaw in stream #1, codec not currently supported in container Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument
...Потому что "
ffmpeg
не поддерживает PCM (pcm_alaw, pcm_s16le и т. д.) в контейнере MP4». См. здесь: кодек в настоящее время не поддерживается в контейнере и здесь . Итак, запуститеtime ffmpeg -f concat -safe 0 -i inputs.txt -c:v copy -c:a aac output.mp4
вместо этого перекодировать звук в формат AAC. Или бегиtime ffmpeg -f concat -safe 0 -i inputs.txt -c copy output.mkv
для записи в контейнер .mkv вместо контейнера .mp4.
Подробную документацию по различным способам объединения в ffmpeg можно найти здесь.
Вы можете использовать "Конкат фильтр" для быстрой конкатенации.
Он выполняет перекодирование. Эта опция лучше всего подходит, когда входы имеют разные видео / аудио форматы.
Для объединения 2 файлов:
ffmpeg -i input1.mp4 -i input2.webm \
-filter_complex "[0:v:0] [0:a:0] [1:v:0] [1:a:0] concat=n=2:v=1:a=1 [v] [a]" \
-map "[v]" -map "[a]" output.mp4
Для объединения 3 файлов:
ffmpeg -i input1.mp4 -i input2.webm -i input3.mp4 \
-filter_complex "[0:v:0] [0:a:0] [1:v:0] [1:a:0] [2:v:0] [2:a:0] concat=n=3:v=1:a=1 [v] [a]" \
-map "[v]" -map "[a]" output.mp4
Это работает как для нескольких типов входных файлов.
это работало для меня (на окнах)
ffmpeg -i "concat:input1|input2" -codec copy output
особенно
ffmpeg -i "concat:01.mp4|02.mp4" -codec copy output.mp4
питон
Используя некоторый код Python, чтобы сделать это с тем количеством mp4, которое есть в папке import glob import os
stringa = ""
for f in glob.glob("*.mp4"):
stringa += f + "|"
os.system("ffmpeg -i \"concat:" + stringa + "\" -codec copy output.mp4")
Из документации здесь: https://trac.ffmpeg.org/wiki/Concatenate
Если у вас есть файлы MP4, их можно объединить без потерь, сначала перекодировав их в транспортные потоки MPEG-2. С видео H.264 и аудио AAC может использоваться следующее:
ffmpeg -i input1.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate1.ts
ffmpeg -i input2.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate2.ts
ffmpeg -i "concat:intermediate1.ts|intermediate2.ts" -c copy -bsf:a aac_adtstoasc output.mp4
Этот подход работает на всех платформах.
Мне нужна была возможность инкапсулировать это в кроссплатформенный скрипт, поэтому я использовал fluent-ffmpeg
и придумал следующее решение:
const unlink = path =>
new Promise((resolve, reject) =>
fs.unlink(path, err => (err ? reject(err) : resolve()))
)
const createIntermediate = file =>
new Promise((resolve, reject) => {
const out = `${Math.random()
.toString(13)
.slice(2)}.ts`
ffmpeg(file)
.outputOptions('-c', 'copy', '-bsf:v', 'h264_mp4toannexb', '-f', 'mpegts')
.output(out)
.on('end', () => resolve(out))
.on('error', reject)
.run()
})
const concat = async (files, output) => {
const names = await Promise.all(files.map(createIntermediate))
const namesString = names.join('|')
await new Promise((resolve, reject) =>
ffmpeg(`concat:${namesString}`)
.outputOptions('-c', 'copy', '-bsf:a', 'aac_adtstoasc')
.output(output)
.on('end', resolve)
.on('error', reject)
.run()
)
names.map(unlink)
}
concat(['file1.mp4', 'file2.mp4', 'file3.mp4'], 'output.mp4').then(() =>
console.log('done!')
)
Основываясь на ответах rogerdpack и Ed999, я создал свою версию.sh
#!/bin/bash
[ -e list.txt ] && rm list.txt
for f in *.mp4
do
echo "file $f" >> list.txt
done
ffmpeg -f concat -i list.txt -c copy joined-out.mp4 && rm list.txt
это объединяет все *.mp4
файлы в текущей папке в joined-out.mp4
проверено на Mac.
полученный размер файла является точной суммой моих 60 проверенных файлов. Не должно быть никаких потерь. Как раз то, что мне было нужно
Поздний ответ, но это единственный вариант, который действительно сработал для меня:
(echo file '1.mp4' & echo file '2.mp4' & echo file '3.mp4' & echo file '4.mp4') | ffmpeg -protocol_whitelist file,pipe -f concat -safe 0 -i pipe: -vcodec copy -acodec copy "coNvid19.mp4"
Для файлов mp4 я обнаружил, что он работает лучше и быстрее, используя инструмент командной строки с открытым исходным кодом "mp4box". Тогда Вы можете использовать это так:
mp4box.exe -адд video1.mp4 -cat video2.mp4 destvideo.mp4
Загрузите его здесь для большинства платформ: https://gpac.wp.imt.fr/mp4box/
Для Windows PowerShell
создать список файлов
PS > ls file* | % { $n = $_.name; "file '\$n'" } | out-file mylist.txt
проверить созданный файл
PS > cat mylist.txt
file '/path/to/file1.mkv'
file '/path/to/file2.mkv'
file '/path/to/file3.mkv'
запустить ffmpeg (не забудьте прикрепить
./
для файла списка)
PS > ffmpeg -safe 0 -f concat -i ./mylist.txt -c copy file.mkv
ffmpeg version git-2020-05-15-b18fd2b Copyright (c) 2000-2020 the FFmpeg developers
...
Вот скрипт, который я сделал, чтобы объединить несколько MP4 в GoPro в MP4 в формате 720p. Надеюсь, это поможет.
#!/bin/sh
cmd="( "
for i; do
cmd="${cmd}ffmpeg -i $i -ab 256000 -vb 10000000 -mbd rd -trellis 2 -cmp 2 -subcmp 2 -g 100 -f mpeg -; "
done
cmd="${cmd} ) | ffmpeg -i - -vb 10000000 -ab 256000 -s 1280x720 -y out-`date +%F-%H%M.%S`.mp4"
echo "${cmd}"
eval ${cmd}
Записывать пути к видео mp4 в файлinput.txt
:
file '/path/to/video1.mp4'
file '/path/to/video2.mp4'
а затем запустите это:
ffmpeg -f concat -safe 0 -i ./input.txt -c:v copy output.mp4
Просто это!
Однострочный фрагмент, не требующий промежуточного файла:
ffmpeg -safe 0 -f concat -i <(найти "$ PWD" -name '*.mp4' -printf "file '% p' \ n" | sort) -c copy output.mp4
Здесь
<()
синтаксис фактически создает временный файл "в фоновом режиме", так сказать
Вот сценарий (работает для произвольного количества указанных файлов (не только для всех в рабочем каталоге), без дополнительных файлов, также работает для.mov; проверено на macOS):
#!/bin/bash
if [ $# -lt 1 ]; then
echo "Usage: `basename $0` input_1.mp4 input_2.mp4 ... output.mp4"
exit 0
fi
ARGS=("$@") # determine all arguments
output=${ARGS[${#ARGS[@]}-1]} # get the last argument (output file)
unset ARGS[${#ARGS[@]}-1] # drop it from the array
(for f in "${ARGS[@]}"; do echo "file '$f'"; done) | ffmpeg -protocol_whitelist file,pipe -f concat -safe 0 -i pipe: -vcodec copy -acodec copy $output
Для тех, кому нужно объединить несколько видео MP4, закодированных с помощью H.264, я предлагаю Python-скрипт mp4concat.py , который автоматизирует параграф протокола Concat/использования промежуточных файлов из документации ffmpeg .
Загрузите скрипт либо по ссылке выше, либо с помощью
wget https://gist.githubusercontent.com/mwouts/115956d3fc7ece55cbce483a7a5148bd/raw/4bc5e03952f1b981dac3f97f4daf82c907774b17/mp4concat.py
а затем использовать его как
python3 mp4concat.py input1.mp4 ... inputN.mp4 --output output.mp4
Слияние всех файлов mp4 из текущего каталога
Мне лично не нравится создавать внешний файл, который я должен затем удалить, поэтому мое решение было следующим, которое включает в себя нумерацию файлов (например, file_1_name, file_2_name, file_10_name, file_20_name, file_100_name
...)
#!/bin/bash
filesList=""
for file in $(ls -1v *.mp4);do #lists even numbered file
filesList="${filesList}${file}|"
done
filesList=${filesList%?} # removes trailing pipe
ffmpeg -i "concat:$filesList" -c copy $(date +%Y%m%d_%H%M%S)_merged.mp4
Если вы хотите объединить файлы без необходимости создавать раздражающий отдельный текстовый файл только для включения имен файлов, вы можете сделать это:
echo -e "file 'in1.mp4'\nfile 'in2.mp4'" | ffmpeg -f concat -safe 0 -i /dev/stdin -c copy out.mp4
На самом деле каждый раз, когда команда требует передачи файла, вы можете вместо этого вывести строку и передать ее своей команде и просто использовать/dev/stdin
как имя файла.
Вот мой метод соединения каталога, заполненного файлами MP4, с использованием подстановки команд и фильтра видео concat (это перекодирует) - подумал, что кому-то еще пригодится использование этой строки, особенно если у вас много файлов (я просто присоединился 17 файлов одним махом)
ffmpeg $(for f in *.mp4 ; do echo -n "-i $f "; done) -filter_complex \
"$(i=0 ; for f in *.mp4 ; do echo -n "[$i:v] [$i:a] " ; i=$((i+1)) ; done \
&& echo "concat=n=$i:v=1:a=1 [v] [a]")" -map "[v]" -map "[a]" output.mp4
Примечание: эта команда объединяет ваши файлы в том порядке, в котором они названы (т.е. в том же порядке, в котором они представлены, если вы запустите ls *.mp4
) - в моём случае у каждого из них был номер трека, поэтому он работал отлично.
После различных попыток ниже скрипт работал на Windows 10 PowerShell.
$files=Get-ChildItem -path e:\ -Filter *.mp4
$files| ForEach-Object {"file '$($_.FullName)'"}| Out-File -FilePath e:\temp.txt -Encoding ASCII
if (-not (test-path "e:\ffmpeg\bin\ffmpeg.exe")) {throw "e:\ffmpeg\bin\ffmpeg.exe needed"}
E:\ffmpeg\bin\ffmpeg.exe -safe 0 -f concat -i "e:\temp.txt" -c copy -bsf:v hevc_mp4toannexb -an e:\joined.mp4
# Conversion Cleanup
Remove-Item e:\temp.txt
Здесь первые две строки создают текстовый файл temp.txt, который имеет следующий контент
file 'e:\first.mp4'
file 'e:\second.mp4'
3-я, 4-я строки проверяют, доступен ли ffmpeg в path, и создают "join.mp4"
Основные отличия от других ответов приведены ниже
usage of -bsf:v hevc_mp4toannexb -an
для моего файла mp4 выше, возможно, вам придется использовать другие альтернативы, как показано ниже, в зависимости от вашей кодировки видео.
h264_mp4toannexb
Все такие возможные фильтры битового потока можно найти по адресу https://ffmpeg.org/ffmpeg-bitstream-filters.html
ffmpeg \
-i input_1.mp4 \
-i input_2.mp4 \
-filter_complex '[0:v]pad=iw*2:ih[int];[int][1:v]overlay=W/2:0[vid]' \
-map [vid] \
-c:v libx264 \
-crf 23 \
-preset veryfast \
output.mp4
Объедините видеофайлы с разными CRF и переменной частотой кадров.
Для этого специализированного варианта я создал отдельный вопрос и ответ (уже решил сам):
Как кодировать сцены с разными CRF в видео H.264 MP4 с переменной частотой кадров?
Если вы предпочитаете метод № 2 из ответа rogerdpack, но не хотите использовать канал (например, вы просто хотите использовать
execv
в C) или не хотите лишних файлов, затем объедините
concat
демультиплексор с
data
а также
file
протоколы. Если вы используете что-то другое, кроме bash, просто посмотрите на последнюю строку.
#!/bin/bash
# @author Arzet Ro, 2021 <arzeth0@gmail.com>
# @license CC-0 (Public Domain)
function usage ()
{
echo "\
ffconcat's usage:
ffconcat (anyFfmpegInputOptions) -i /tmp/a.mp4 -i ... -i ... /tmp/out.mp4 (anyFfmpegOutputOptions)
ffconcat -vn /tmp/a.mp4 /tmp/b.opus /tmp/out.mp4 -y
ffconcat -http -i https://a/a.mp4 -i ftp://127.0.0.1/b.mp4 -i /tmp/c.opus /tmp/out.mkv
ffconcat has -http
ffmpeg has no -http"
}
# Configuration [begin]
forceRequireIArgumentForInputFiles=0
# Configuration [end]
in_array ()
{
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
requireIArgumentForInputFiles=0
if in_array "-i" "$@"
then
requireIArgumentForInputFiles=1
elif [[ "$forceRequireIArgumentForInputFiles" == "1" ]]
then
>&2 echo "forceRequireIArgumentForInputFiles=1, so you need -i"
usage
exit 1
fi
NL=$'\n'
inputOptions=()
outputOptions=()
inputFilesRawArray=()
outputFile=""
declare -a valuelessFfmpegArgs=("-http" "-hide_banner" "-dn" "-n" "-y" "-vn" "-an" "-autorotate" "-noautorotate" "-autoscale" "-noautoscale" "-stats" "-nostats" "-stdin" "-nostdin" "-ilme" "-vstats" "-psnr" "-qphist" "-hwaccels" "-sn" "-fix_sub_duration" "-ignore_unknown" "-copy_unknown" "-benchmark" "-benchmark_all" "-dump" "-hex" "-re" "-copyts" "-start_at_zero" "-shortest" "-accurate_seek" "-noaccurate_seek" "-seek_timestamp")
# ^ the list is not exhaustive
# used when requireIArgumentForInputFiles=0
areFfmpegArgsAllowed=1
isHttpMode=0
if in_array "-http" "$@"
then
isHttpMode=1
fi
secondArgumentIsWantedByThisFirstArgument=""
# if requireIArgumentForInputFiles=1
# then secondArgumentIsWantedByThisFirstArgument must be either "" or "-i"
isCurrentArgumentI=0
localRawFilesArray=()
outputFile=""
for arg in "$@"
do
if [[ "$secondArgumentIsWantedByThisFirstArgument" == "" && "$arg" == "-http" ]]
then
continue
fi
if [[ "$arg" == "--" ]]
then
areFfmpegArgsAllowed=0
continue
fi
if [[
(
"$areFfmpegArgsAllowed" == "1"
||
"$secondArgumentIsWantedByThisFirstArgument" != ""
)
&& !(
"$requireIArgumentForInputFiles" == "1"
&&
"$secondArgumentIsWantedByThisFirstArgument" == "-i"
)
&&
(
"$secondArgumentIsWantedByThisFirstArgument" != ""
||
(
"$requireIArgumentForInputFiles" == "0"
&&
"$arg" = -*
)
||
(
"$requireIArgumentForInputFiles" == "1"
)
)
]]
then
if [[ !(
"$requireIArgumentForInputFiles" == "1"
&&
"$arg" == "-i"
) ]]
then
if (( ${#inputFilesRawArray[@]} == 0 ))
then
inputOptions+=("$arg")
else
outputOptions+=("$arg")
fi
fi
elif [[
"$requireIArgumentForInputFiles" == "0"
||
"$secondArgumentIsWantedByThisFirstArgument" == "-i"
]]
then
if echo -n "$arg" | egrep '^(https?|ftp)://'
then
inputFilesRawArray+=("$arg")
localRawFilesArray+=("$arg")
else
tmp=`echo -n "$arg" | sed 's@^file:@@'`
localRawFilesArray+=("$tmp")
if [[ "$secondArgumentIsWantedByThisFirstArgument" == "-i" ]]
then
tmp=`ls -1d --quoting-style=escape -- "$tmp" | tr -d "\n" 2>/dev/null`
if [[ $tmp == "" ]]
then
>&2 echo "Input file `$tmp` not found"
exit 1
fi
else
tmp=`echo -n "$tmp" | sed -E 's@(\s|\\\\)@\\\\\1@g'`
# ^ FIXME: does it work for all filenames?
# coreutils-9.0/lib/quotearg.h:138
fi
inputFilesRawArray+=("file:$tmp")
fi
elif [[
"$requireIArgumentForInputFiles" == "1"
&&
"$secondArgumentIsWantedByThisFirstArgument" != "-i"
]]
then
if echo -n "$arg" | egrep '^(https?|ftp)://'
then
outputFile="$arg"
else
outputFile=`echo -n "$arg" | sed 's@^file:@@'`
outputFile="file:$outputFile"
fi
else
usage
exit 1
fi
if [[
"$secondArgumentIsWantedByThisFirstArgument" != ""
||
"$areFfmpegArgsAllowed" == "0"
]]
then
secondArgumentIsWantedByThisFirstArgument=""
else
if [[ "$requireIArgumentForInputFiles" == "1" && "$arg" == "-i" ]]
then
secondArgumentIsWantedByThisFirstArgument="$arg"
elif [[ "$requireIArgumentForInputFiles" == "0" && "$arg" = -* ]]
then
if ! in_array "$arg" ${valuelessFfmpegArgs[@]}
then
secondArgumentIsWantedByThisFirstArgument="$arg"
fi
fi
fi
done
if [[
"$requireIArgumentForInputFiles" == "0"
&&
"$outputFile" == ""
]]
then
outputFile="${localRawFilesArray[((${#localRawFilesArray[@]}-1))]}"
fi
actualOutputFile=outputFile
if [[ "file:" = $outputFile* ]]
then
actualOutputFile=`echo -n "$actualOutputFile" | sed 's@^file:@@'`
actualOutputFile=`readlink -nf -- "$actualOutputFile"`
actualOutputFile="file:${actualOutputFile}"
fi
if [[ "$requireIArgumentForInputFiles" == "0" ]]
then
unset 'inputFilesRawArray[((${#inputFilesRawArray[@]}-1))]'
unset 'localRawFilesArray[((${#localRawFilesArray[@]}-1))]'
outputOptions+=("$outputFile")
fi
#>&2 echo Input: ${inputFilesRawArray[@]}
#if [[ "$requireIArgumentForInputFiles" == "0" ]]
#then
# >&2 echo Output: $outputFile
#fi
if (( ${#inputFilesRawArray[@]} < 2 ))
then
>&2 echo "Error: Minimum 2 input files required, ${#inputFilesRawArray[@]} given."
>&2 echo Input: ${inputFilesRawArray[@]}
if [[ "$requireIArgumentForInputFiles" == "0" ]]
then
>&2 echo Output: $outputFile
fi
usage
#outputFile=/dev/null
exit 1
fi
if [[
"$requireIArgumentForInputFiles" == "0"
&&
"$outputFile" == ""
]]
then
>&2 echo "Error: No output file specified."
usage
exit 1
fi
ffmpegInputList=""
firstFileDone=0
inputFilesRawArrayLength=${#inputFilesRawArray[@]}
for (( i = 0; i < inputFilesRawArrayLength; i++ ))
do
lf="${localRawFilesArray[$i]}"
f="${inputFilesRawArray[$i]}"
if [[ "file:" = ${inputFilesRawArray[$i]}* ]]
then
actualF=`readlink -nf -- "$lf"`
if [[ "$actualF" == "$actualOutputFile" ]]
then
>&@ echo "Error: The same file `$actualF` is used both as an input file and an output file"
exit 1
fi
fi
if [[ "$firstFileDone" == "1" ]]
then
ffmpegInputList+="$NL"
fi
ffmpegInputList+="file $f"
firstFileDone=1
done
protocol_whitelist_appendage=""
if [[ "$isHttpMode" == "1" ]]
then
protocol_whitelist_appendage=",ftp,http,https"
fi
# Also print the final line:
set -x
ffmpeg \
-safe 0 \
-f concat \
-protocol_whitelist data,file"$protocol_whitelist_appendage" \
"${inputOptions[@]}" \
-i "data:text/plain;charset=UTF-8,${ffmpegInputList}" \
-c copy \
"${outputOptions[@]}"
# $ffmpegInputList is
# file file:./test.mp4\nfile file:/home/aaa.mp4\nfile http://a/b.aac.
# All whitespace in ffmpegInputList is escaped with \
Процентное кодирование (JavaScript
encodeURI
/
encodeURIComponent
) (
%20
и т. д.) не требуется, в отличие от HTML.
Протокол Concat, описанный здесь; https://trac.ffmpeg.org/wiki/Concatenate
При реализации с использованием именованных каналов, чтобы избежать промежуточных файлов
Очень быстрый (читай: мгновенный), без пропущенных кадров и работает хорошо.
Не забудьте удалить файлы именованных каналов и не забудьте проверить, является ли видео H264 и AAC, что вы можете сделать, просто ffmpeg -i filename.mp4
(проверьте на h264 и aac упоминает)
Принятый ответ в виде многоразового скрипта PowerShell
Param(
[string]$WildcardFilePath,
[string]$OutFilePath
)
try
{
$tempFile = [System.IO.Path]::GetTempFileName()
Get-ChildItem -path $wildcardFilePath | foreach { "file '$_'" } | Out-File -FilePath $tempFile -Encoding ascii
ffmpeg.exe -safe 0 -f concat -i $tempFile -c copy $outFilePath
}
finally
{
Remove-Item $tempFile
}