如何使用 FFmpeg 连接两个 MP4 文件



我正在尝试使用ffmpeg连接两个mp4文件。 我需要这是一个自动过程,因此我选择 ffmpeg。 我将这两个文件转换为.ts文件,然后将它们连接起来,然后尝试对连接的.ts文件进行编码。 这些文件是h264aac编码的,我希望保持质量相同或尽可能接近原始质量。

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 文件连接在一起并让它工作。

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. 康卡特解复用器

如果要避免重新编码,并且格式不支持文件级串联(一般用户使用的大多数文件不支持文件级串联(,请使用此方法。

$ cat mylist.txt
file '/path/to/file1'
file '/path/to/file2'
file '/path/to/file3'
    
$ ffmpeg -f concat -safe 0 -i mylist.txt -c copy output.mp4

对于视窗

(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. 康卡特协议

将此方法用于支持文件级串联的格式(MPEG-1,MPEG-2 PS,DV(。请勿与 MP4 一起使用。

ffmpeg -i "concat:input1|input2" -codec copy output.mkv

此方法不适用于许多格式,包括 MP4,因为这些格式的性质以及此方法执行的简单物理连接。 这相当于只是原始连接文件。

<小时 />

如果不确定使用哪种方法,请尝试 concat 解复用器。

另请参阅

  • FFmpeg 常见问题解答:如何加入视频文件?
  • FFmpeg Wiki:如何连接(加入、合并(媒体文件

用于 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 批处理文件中尝试此操作(必须包含选项 -safe 0(:

:: Create File List
for %%i in (*.mp4) do echo file '%%i'>> mylist.txt
:: Concatenate Files
ffmpeg -f concat -safe 0 -i mylist.txt -c copy output.mp4

这适用于 Windows 7 上的批处理文件。不要尝试在命令行上使用它,因为它只能在批处理文件中工作!

以下将三个.mp3文件连接成一个.m4a

ffmpeg -i input1.mp3 -i input2.mp3 -i input3.mp3 -filter_complex "concat=n=3:v=0:a=1" -vn -y input.m4a

期权的含义

-filter_complex "concat=n=3:v=0:a=1

  • concat:连接过滤器连接流
  • n:输入段计数(= 同步的音频-视频流或纯音频或纯视频流(
  • v:输出视频流计数
  • a:输出音频流计数
  • -vn:禁用视频(-an会禁用音频(
  • -y:在没有提示的情况下覆盖输出文件

请参阅man ffmpegffmpeg -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从管道中读取

(注意 - 我的文件中没有空格或奇怪的东西 - 如果你想用"硬"文件做这个想法,你需要适当的 shell-escaping(。

我发现当使用选项 3 在接受的答案中连接 Mac 上的多个 MP4 时,管道操作员对我不起作用。

以下单行代码在 bash(Mac、Linux(中工作,不需要中间文件:

ffmpeg -f concat -safe 0 -i <(for f in ./*.mp4; do echo "file '$PWD/$f'"; done) -c copy output.mp4

在这里,<(( 语法实际上"在后台"创建了一个临时文件,可以这么说

我最终使用 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

这里 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
}

注意:在此线程中尝试了一些解决方案,但没有一个让我满意

有关 ffmpeg 中各种串联方式的详细文档可以在这里找到。

您可以使用"Concat 过滤器">进行快速连接。

它执行重新编码。当输入具有不同的视频/音频格式时,此选项是最佳选择。

对于连接 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

这适用于相同以及多种输入文件类型。

根据 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 个测试文件的精确总和。应该没有任何损失。正是我需要的

来自此处的文档: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!')
)

这对我有用(在Windows上(

ffmpeg -i "concat:input1|input2" -codec copy output

举个例子...

ffmpeg -i "concat:01.mp4|02.mp4" -codec copy output.mp4

使用一些 python 代码来使用文件夹中尽可能多的 mp4 来完成此操作(从 python.org 安装 python,复制并粘贴并将此代码保存到名为 mp4.py 的文件中,然后从文件夹中打开的 cmd 运行它与 python mp4.py 和文件夹中的所有 mp4 将被连接(

import glob
import os
stringa = ""
for f in glob.glob("*.mp4"):
    stringa += f + "|"
os.system("ffmpeg -i "concat:" + stringa + "" -codec copy output.mp4")

版本 2 与 Python

取自我博客上的帖子,这是我在python中的工作方式:

import os
import glob
def concatenate():
    stringa = "ffmpeg -i "concat:"
    elenco_video = glob.glob("*.mp4")
    elenco_file_temp = []
    for f in elenco_video:
        file = "temp" + str(elenco_video.index(f) + 1) + ".ts"
        os.system("ffmpeg -i " + f + " -c copy -bsf:v h264_mp4toannexb -f mpegts " + file)
        elenco_file_temp.append(file)
    print(elenco_file_temp)
    for f in elenco_file_temp:
        stringa += f
        if elenco_file_temp.index(f) != len(elenco_file_temp)-1:
            stringa += "|"
        else:
            stringa += "" -c copy  -bsf:a aac_adtstoasc output.mp4"
    print(stringa)
    os.system(stringa)
concatenate()

回答晚了,但这是真正对我有用的唯一选项:

(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 "1234.mp4"

主要答案对我不起作用,因为它给了我在下面"故障排除"部分中描述的错误,所以这是我自己的答案。

如何使用ffmpeg连接或组合许多mp4视频文件

快速摘要

创建一个包含要合并的所有文件路径列表的inputs.txt文件(请参阅下面的示例(。然后,将上述所有视频合并为一个,如下所示:

# 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

结合 Wyze 安全摄像头镜头的详细信息和完整示例

在 Ubuntu 20.04 上测试,将我的一堆 Wyze 安全摄像头视频合二为一。(我直接将相机的micro SD卡插入计算机(。

  1. 创建一个名为 inputs.txt 的文件,其中包含要连接的所有文件的列表。前任:

    file 'path/to/file1.mp4'
    file 'path/to/file2.mp4'
    file 'path/to/file3.mp4'
    

    就我而言,我将 2022 年 11 月 13 日 18:31(下午 6:31(到 2022 年 11 月 13 日 19:39(晚上 7:39(的 69 分钟视频合并在一起。在 Wyze 安全摄像头路径中,这意味着从 record/20221113/18/31.mp4'record/20221113/19/39.mp4 ,包括。所以,这是我的完整inputs.txt文件。请注意,您可以使用Sublime Text或MS VSCode的多光标编辑模式一次编辑多行和位置,快速轻松地编辑或创建此类文件。在这种情况下,我使用了Sublime Text。

    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 MB。以下是上述两个命令的结果:

  1. output.mp4 花了 11 秒来制作,大小为 380.3 MB。AAC 编码的音频显然要小一点。
  2. output.mkv 花了 8 秒的时间制作,大小为 410.7MB

故障排除/可能的错误

  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不支持 MP4 容器中的 PCM(pcm_alaw、pcm_s16le 等(。 请参阅此处:容器和此处当前不支持的编解码器。因此,请改为运行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容器。

对于.mp4文件,我发现使用开源命令行工具更好、更快:mp4box .然后你可以这样使用它:

mp4box.exe -add video1.mp4 -cat video2.mp4 destvideo.mp4

在此处下载适用于大多数平台:https://gpac.wp.imt.fr/mp4box/

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

这是我制作的脚本,用于将几个GoPro mp4连接成720p mp4。希望对您有所帮助。

#!/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}
<</div> div class="one_answers">

这是一个脚本(适用于任意数量的指定文件(不仅仅是工作目录中的所有文件(,没有其他文件,也适用于.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

对于那些需要连接一些用H.264编码的MP4视频的人,我提出了一个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 视频路径写入文件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

就这个!

经过下面的各种尝试,脚本在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:ffmpegbinffmpeg.exe")) {throw "e:ffmpegbinffmpeg.exe needed"}
    E:ffmpegbinffmpeg.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 在路径上是否可用并创建"连接.mp4">

与其他答案的主要区别如下

usage  of -bsf:v hevc_mp4toannexb -an

对于上面的MP4文件,您可能需要使用其他替代方案,如下所示,具体取决于您的视频编码。

h264_mp4toannexb

所有这些可能的比特流滤波器都可以在 https://ffmpeg.org/ffmpeg-bitstream-filters.html

这是我使用命令替换和 concat 视频过滤器(这将重新编码(加入充满 MP4 文件的目录的方法 - 认为其他人会从这个单行中获得一些用处,特别是如果你有很多文件(我刚刚一举加入了 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,则它们的显示顺序相同 - 就我而言,它们每个都有一个曲目编号,所以效果很好。

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

这里描述的 concat 协议;https://trac.ffmpeg.org/wiki/Concatenate#protocol

使用命名管道实现时,避免中间文件

非常快(读取:即时(,没有丢帧,并且运行良好。

请记住删除命名管道文件,并记住检查视频是否为 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
}

如果你更喜欢 rogerdpack 答案中的方法 #2,但你不想使用 pipe(例如,你只想在 C 中使用 execv(或者不想创建额外的文件(list.txt(,那么只需将concat解复用器与 datafile 协议相结合,即 FFmpeg 允许您几乎像在 HTML 中一样内联输入文件:

<img src="data:image/png;base64,..." alt="" />
ffmpeg -i 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB4AAAAQ4AQAAAADAqPzuAAABEklEQVR4Ae3BAQ0AAADCIPunfg8HDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4FT45QABPFL5RwAAAABJRU5ErkJggg==' /tmp/blackbg.mp4

下面是我的程序(放在/usr/bin/ffconcat中(,它自动内联"包含文件路径列表的文件"。此外,与所有其他答案不同,您可以使用任何 FFmpeg 选项。

如果你使用bash语言(C,Node.js(以外的语言,那么只需查看usage ()和最后一行。

#!/bin/bash
# ffconcat v0.3
# @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/h264@720p@25fps+opus.mp4 -i ftp://127.0.0.1/h264@720p@30fps+opus.mp4 -i /tmp/c.opus /tmp/out.mkv
    ffconcat -http -i https://a/vp9@1080p@30fps+opus.mp4 -i ftp://127.0.0.1/vp9@720p@30fps+opus.mp4 -i /tmp/c.opus /tmp/out.mp4
    WARNING: ffconcat uses `concat` demuxer; when
    using both this demuxer AND -y, FFmpeg doesn't check if
    an input file and output file
    are the same file, so your 100 GB input file
    could immediately become 10 KB.
    ffconcat checks that only when neither -i
    nor new FFmpeg release's boolean args (see valuelessFfmpegArgs in the code)
    are specified.
    ffmpeg has no -http.
    ffconcat has -http because ffmpeg automatically
    sets allowed protocols depending on -f and -i.
    But when -f concat, ffmpeg doesn't know what to do with -i.
    ffmpeg and mpv support VP9+Opus in .mp4
    Only one video codec is possible in an output file.
    You can't have both AAC and Opus in one .mp4 (not sure about other containers).
    If you combine VP9 videos, then make sure they have the same FPS.
    If you combine H264 videos of different resolutions,
    then you get A/V desync
    and also either
    1) the start of video streams after the first video stream are cut
    2) or video player freezes for 5 seconds when switching between video streams.
    Also it seems if DAR (display aspect ratio) differs (at least in H.264)
    then incorrect (5x longer) duration is estimated
    and mpv displays the second video with 1 FPS.
    You can see the info about an input file
    with
    mediainfo file.mp4
    or
    ffprobe -hide_banner -protocol_whitelist file,rtp,udp -show_streams file.mp4"
}
# Configuration [begin]
forceRequireIArgumentForInputFiles=0
# Configuration [end]


in_array ()
{
    local e match="$1"
    shift
    for e; do [[ "$e" == "$match" ]] && return 0; done
    return 1
}
if [[ "$#" == 0 ]]
then
    usage
    exit
fi
requireIArgumentForInputFiles=0
if in_array "--help" "$@"
then
    usage
    exit
elif in_array "-help" "$@"
then
    usage
    exit
elif 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"     "write_id3v2" "write_apetag" "write_mpeg2" "ignore_loop" "skip_rate_check" "no_resync_search" "export_xmp")
#^ used when requireIArgumentForInputFiles=0
# TODO: fill all the args
# grep -C 3 AV_OPT_TYPE_BOOL libavformat/ libavcodec/
# grep -C 3 OPT_BOOL fftools/
# Unfortunately, unlike MPV, FFmpeg neither
# requires nor supports `=`, i.e. `--y --i=file.mp4'
# instead of `-y -i file.mp4`.
# Which means it's unclear whether an argument
# is a value of an argument or an input/output file.
areFfmpegArgsAllowed=1
isHttpMode=0
if in_array "-http" "$@"
then
    isHttpMode=1
fi

# if an argument is not a boolean argument, then what key needs a value
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
                if ! ls -1d -- "$tmp" >/dev/null 2>/dev/null
                then
                    >&2 echo "Input file '$tmp' not found"
                    exit 1
                fi
            fi
            tmp=`echo -n "$tmp" | sed -E 's@(s|\\)@\\1@g' | sed "s@'@\\'@g"`
            # ^ FIXME: does it work for all filenames?
            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 [[ "$requireIArgumentForInputFiles" == "0" || "file:" =~ ^"$outputFile"* ]]
then
    actualOutputFile=`echo -n "$actualOutputFile" | sed 's@^file:@@'`
    actualOutputFile=`readlink -nf -- "$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 [[ "${inputFilesRawArray[$i]}" =~ ^file: ]]
    then
        actualF=`readlink -nf -- "$lf"`
        if [[ "$actualF" == "$actualOutputFile" ]]
        then
            >&2 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.mp4nfile file:/home/aaa.mp4nfile http://a/b.aac
# All whitespace and ' in ffmpegInputList are escaped with ``.

百分比编码(JavaScript的encodeURI/encodeURIComponent((%20等(在-i中不需要,不像HTML。

连接具有不同 CRF 和可变帧速率的视频文件

对于这个专门的变体,我创建了一个单独的问答(我已经解决了(:

如何将具有不同CRF的场景编码为具有可变帧速率的H.264 MP4视频?

ffmpeg -i input1.ts -i input2.ts -i input3.ts -filter_complex "concat=n=3:v=1:a=0" -vn -y output.ts 

享受!!

最新更新