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

964

我正在尝试使用 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文件连接在一起使其正常工作。


8
这是一个我个人认为的合法编程问题,但如果需要的话,可以将其移至SuperUser。 - Ed999
这是一个有效的命令行编程问题,用于创建特定的自动化过程并避免特定的错误。目前,StackOverflow有26,149个标记为[ffmpeg]的问题;SuperUser有6,811个标记为[ffmpeg]的问题;VideoProduction有2,117个标记为[ffmpeg]的问题。这个问题可以说在StackOverflow、SuperUser或Video Production都是合适的。如果这个问题被移动,那么其他的SO [ffmpeg]问题也应该被重新审查吗?值得注意的是,这个问题在SO上已经接受(并且得到了很高的投票),有很多得票数很高的答案。 - undefined
根据这个定义,这个问题可能是不合适的。我投票赞成保持关闭状态。(而且它在这里已经超过十年了,可以说是反对重新开放,因为这里的适用主题规则已经改变。我们根据今天的规则来评估是否重新开放或保持关闭状态。) - undefined
@Chris,“programmatic use of the FFmpeg libraries, API, or tools”这个表述的范围存在歧义。例如,考虑调试命令行表达式(即使用工具)以便包含在程序化自动化中。这个情况在2013年6月18日的[ffmpeg]标签中得到了不同的处理方式。“ffmpeg命令行工具应该在Super User上提问”。基于标签的“命令行工具”限制声明后来被移除,截至我上次检查,使用工具仍然是可以的。然而,话虽如此,... 1/2 - undefined
@Chris然而,话虽如此,如果"这里可以问什么话题?"对_"命令行工具表达式"有明确的清晰度会很有帮助。作为一名软件开发人员,我经常在编译代码和解释脚本中以编程方式使用"命令行工具表达式"_. 明确一点,我对适当的移动是没问题的!更多的是指出这个问题是SO长期以来存在的模糊边界的典型例子。... 这影响了许多SO的问题和标签。2/2 - undefined
显示剩余4条评论
31个回答

1672
FFmpeg有三种连接方法:
1. concat video filter 如果您的输入没有相同的参数(宽度、高度等),或者不是相同的格式/编解码器,或者您想要执行任何过滤操作,请使用此方法。
请注意,此方法会对所有输入进行重新编码。如果您想避免重新编码,您可以只重新编码不匹配的输入,使它们共享相同的编解码器和其他参数,然后使用concat demuxer来避免重新编码所有内容。
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

适用于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

或者

(for %i in (*.mp4) do @echo file '%i') > 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 demuxer。
另请参阅:
- FFmpeg常见问题解答:如何合并视频文件? - FFmpeg维基:如何拼接(合并)媒体文件?

5
Windows 命令提示符的转义字符是 ^;我也遇到了引号的问题,所以命令变成了:ffmpeg -i concat:input1^|input2 -codec copy output。 - user423430
6
串联分离器适用于视频,但我失去了音频。 - Loïc
66
要生成一行命令,请使用:ffmpeg -safe 0 -f concat -i <(find . -type f -name '*' -printf "file '$PWD/%p'\n" | sort) -c copy output.mkv(mkv 支持更多编解码器,但你也可以尝试使用 mp4)。-safe 0 参数是为了应对最近的 ffmpeg 版本关于“不安全文件名”的警告,而 -type f 参数是为了只列出文件。我添加了 | sort 参数来按字母顺序排序文件;因为 find 命令根据文件系统上保存的顺序读取它们。这个命令也适用于带有空格的文件名。 - erik
1
如果我使用以下命令: -f concat -safe 0 -i "file.txt" -c copy -y "OutPut.mp4" 它可以连接视频,但是当一些视频带有音频而另一些没有音频时,它无法正常工作。请问我该如何解决这个问题?谢谢。 - Ahmad
6
如果你(和我一样)在concat demuxer方法中遇到“未知关键字”的错误,那么说明你没有按照list.txt的格式进行编写。每一行都必须以“file”开头。 - idbrii
显示剩余33条评论

226

MP4文件操作方法

对于.mp4文件(这些文件是我从DailyMotion.com获得的,一个50分钟电视剧集,只能下载三个部分,即三个.mp4视频文件),以下方法适用于Windows 7,而且不需要重新编码文件。

我将文件重命名(为file1.mp4file2.mp4file3.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上可以在批处理文件中使用。不要尝试在命令行中使用,因为它只能在批处理文件中工作!


3
这是一个非常好的解决方案,因为它能够避免您为“-i”参数键入冗长的文件列表。在Linux上,我这样做:使用命令ls -1 P1041*.mp4 | sed s/"P"/" file P"/g > mylist.txt ,这比编写批处理脚本更酷。无论如何,“-c copy”执行非常快速(这一点我之前并不知道),这是这个答案真正神奇的地方。 - Würgspaß
1
你上次的代码不起作用。我必须使用这个代替:(for %i in (*.mp4) do @echo file '%i') > mylist.txt - Xam
3
在我的 Mac 上使用 ffmpeg v3.2.2 运行您提供的拼接代码后,我的 mp4 文件在输出文件中完全没有音频。请帮我解决这个问题。 - kakyo
@kakyo 请尝试使用_http://ffmpeg.zeranoe.com/builds/_上的最新版本构建的ffmpeg,但很遗憾我无法解决Mac上的问题,我的代码仅适用于Windows。我自己只在Windows 7上运行它,但它应该可以在Windows Vista / 7/8/10上正常工作。 - Ed999
2
应该在顶部添加“del mylist.txt”。 - Nime Cloud
显示剩余18条评论

100

将三个.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 来查看所有选项(包括所有格式和编解码器特定的选项)。


4
如果我使用-f concat -safe 0 -i "file.txt" -c copy -y "OutPut.mp4",它会合并视频,但当某些视频带有音频而另一些没有音频时,它无法工作。请问如何解决这个问题?谢谢。 - Ahmad
2
工作得很好,只需要将 "v=0" 更改为 "v=1" 就解决了其余问题! - Ray
1
你让它听起来像是 va 是布尔值;一个“是”或“否”,表示输出是否应该包含视频/音频。但事实并非如此。它们实际上是整数。FFmpeg 的 concat-filter 文档中写道:“设置输出视频/音频流的数量,这也是每个片段中视频/音频流的数量”。即使在示例中,你也可以看到例如 a 被设置为 2 - Reino
输入链接in0:v0的参数(大小为428x640,SAR为0:1)与相应的输出链接in0:v0的参数(大小为428x640,SAR为1576:1575)不匹配。 - theonlygusti
对我来说有效,但还需要像@Beyar一样将v = 0更改为v = 1。 - Shiping
我运行了ffmpeg -i input1.mp3 -i input2.mp3 -filter_complex "concat=n=2:v=0:a=1" -vn -y output,但输出只包含了input1(input2被忽略了)。这是怎么回事? - Apostolos

70

对于MP4文件:

如果它们不完全相同(具有100%相同的编解码器、分辨率和类型),那么您首先必须将它们转码为中间流:

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

注意!:输出的结果将会像第一个文件一样(而不是第二个文件)。


2
正如我在下面的答案中所示,转码文件是不必要的。FFmpeg可以将多个mp4文件连接成单个mp4文件 - 无需任何中间步骤,也无需重新编码。唯一需要重新编码的情况是,如果原始文件不是mp4文件,但意图是创建一个mp4文件作为输出文件。 - Ed999
8
你好,当视频没有音轨时,该命令运行良好。然而,当 myfile1.mp4 没有音频轨道,而 myfile2.mp4 有音频轨道时,生成的 output.mp4 结果没有音频。期望的 output.mp4 应该包含音频。我该如何解决这个问题? - AnswerZhao
2
@T.Todua...这太糟糕了。file1|file2生成的视频与file2|file1不同。顺序很重要。 - AnswerZhao
@T.Todua,我想知道您是否知道如何在指定输出比特率的情况下连接两个MP4视频的ffmpeg问题的答案?https://video.stackexchange.com/q/24569/5657 谢谢。 - Ryan
3
我尝试了你的指令来合并两个mp4文件,它有效。但是,第二个文件的音频在新生成的拼接文件中出现了卡顿和“啊-噢-咿”的声音。你有解决方法吗? - superlinux
显示剩余7条评论

67

以下是一种快速(少于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 转义)。


4
使用适当的转义处理包含空格的文件:ls Movie_Part_1.mp4 Movie_Part_2.mp4 | perl -ne '$_ =~ s/\n$//; print "file '"'"'$_'"'"'\n"' | ffmpeg -f concat -i - -c copy Movie_Joined.mp4 该命令可将两个名为 "Movie_Part_1.mp4" 和 "Movie_Part_2.mp4" 的视频文件拼接成一个名为 "Movie_Joined.mp4" 的文件。其中,通过在文件名前后添加单引号和反斜杠来处理文件名中的空格字符。 - Thomas Bachem
4
如果您的文件命名正确并且顺序正确,您也可以只运行以下命令:ls * | perl -ne 'print "file $_"' | ffmpeg -f concat -i - -c copy Movie_Joined.mp4。在我的OS X系统上完美地运行,正是我想要的结果。 - Nate Beaty
4
在Linux上,我使用了这个一行命令:ffmpeg -safe 0 -f concat -i <(find . -type f -name '*' -printf "file '$PWD/%p'\n" | sort) -c copy output.mkv(mkv支持比mp4更多的编解码器,但您也可以尝试使用mp4)。-safe 0用于处理最近版本的ffmpeg会出现 Unsafe file name 的警告问题。-type f是为了只列出文件。我还添加了|sort来按字母表顺序排列文件; 因为find按照文件系统中保存的顺序读取它们。这对带有空格的文件也适用。 - erik
22
为了让ffmpeg正常工作,我不得不添加以下参数:-protocol_whitelist file,tcp,http,pipe - Bensge
1
我不得不为OSX添加文件和管道的协议白名单。以下是对我有效的方法:ls file1.mp4 file2.mp4 | perl -ne 'print "file $_"' | ffmpeg -f concat -safe 0 -protocol_whitelist "file,pipe" -i - -c copy output.mp4 - Donn Felker
显示剩余4条评论

56

在接受的答案中,当我使用选项3连接多个MP4文件时,管道运算符对我不起作用。

以下一行命令适用于bash(Mac、Linux),并且不需要中间文件:

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

这里,<()语法实际上创建了一个“在后台”中的临时文件。


43

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

9
截至本评论,-sameq 已被移除。请改用 -qscale 0 - Xavier Ho
9
“-sameq”并不意味着“相同的质量”,正如@XavierHo所述,它已经从ffmpeg中删除。 - llogan
5
似乎很遗憾必须通过中间格式转码视频才能连接两个文件。这样会有世代损失。最好不要深入到容器格式之外,而是像@rogerdpack上面做的那样,在ffmpeg中使用“concat”命令。 - Randall Cook
1
该方法会降低mp4视频的质量。 - bozzmob
1
当尝试连接具有相似分辨率/宽高比的视频时,效果很好。但是,如果您有不同的分辨率,则此方法将尝试使它们都具有相同的分辨率,这是不正确的。 - Gru
显示剩余3条评论

24

这里提供两种仅使用ffmpeg,且不需要中间文件、perl、python或javascript的纯bash解决方案

使用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

接受零或两个以上参数的函数

#######################################
# 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
}
注意:我在这个帖子中尝试了一些解决方案,但都没有满足我的要求。

19

有关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

合并三个文件:

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

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


@开发者:有关选项的说明-> 连接过滤器维基 - SJ00
5
当连接三个文件时,应使用concat=n=3:v=1:a=1而不是n=2。 - Cynapsis
1
我没有得到无损连接,我的 ouput.mp4 视频比特率明显低于输入视频的比特率。 - jasan
@jasan,“concat filter”执行输出的重新编码。对于无损输出,请选择“concat protocol”或“concat demuxer”,但这两个选项都需要您明确输入格式。 - SJ00
我们可以移除 "[0:v:0] [0:a:0] [1:v:0] [1:a:0] [2:v:0] [2:a:0]" 吗?移除后,它也能正常工作。 - Stony
显示剩余5条评论

15

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

如果你想知道如何将多个.mov.MOV文件合并成一个文件,请参考我在这里的其他答案:Super User: 合并MOV视频文件。我还有一个find命令,可以快速将所有感兴趣的文件收集到files.txtinputs.txt中,而不必手动操作。

如何使用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安全摄像头视频合并为一个视频。(我将摄像头的微型SD卡直接插入了我的电脑)。

创建一个名为`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。
将所有以上视频合并为一个视频: ``` # 选项1(首选):合并为一个具有AAC音频编码和原始视频编码的单个mp4视频 time ffmpeg -f concat -safe 0 -i inputs.txt -c:v copy -c:a aac output.mp4
# 选项2:合并为一个具有原始音频和视频编码的单个.mkv视频 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.7 MB

故障排除/可能的错误

如果你运行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: "Unsafe file name"
如果你运行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等)。" 请参考:codec not currently supported in containerhere。所以,改为运行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容器。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接