Как объединить два файла 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 камеры прямо к компьютеру).

  1. Создайте файл с именем , содержащий список всех файлов, которые вы хотите объединить. Бывший:

            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'
    
  2. Объедините все вышеперечисленные видео в одно:

            # 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 МБ . Вот результаты двух приведенных выше команд:

  1. output.mp4на создание ушло 11 секунд , а размер — 380,3 МБ . Звук в кодировке AAC, по-видимому, немного меньше.
  2. output.mkvна создание ушло 8 секунд , а размер — 410,7 МБ .

Устранение неполадок/возможные ошибки

  1. Если вы запустите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.

    Видеть:

    1. https://nono.ma/says/concat-unsafe-file-name-operation-not-permitted
    2. ffmpeg concat: «Небезопасное имя файла»
  2. Если вы запустите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
}
Другие вопросы по тегам