我正在尝试使用ffmpeg连接两个mp4文件。我需要这是一个自动过程,因此我选择了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文件合并在一起并使其正常工作。


当前回答

根据此处的文档: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

这种方法适用于所有平台。

我需要将其封装在跨平台脚本中的能力,因此我使用了流利的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!')
)

其他回答

当使用选项3在Mac上连接多个MP4时,我发现管道操作员不适合我。

以下一行程序适用于bash(Mac、Linux),不需要中间文件:

ffmpeg-f concat-safe 0-i<(对于./*.mp4中的f;do echo“文件'$PWD/$f'”;已完成)-c copy output.mp4

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

这对我(在窗户上)有用

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")

Python版本2

摘自我在博客上的帖子,这是我在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()

这里有一种快速(不到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转义)。

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

享受

如果您想连接文件,而不必创建一个烦人的单独文本文件,只需包含文件名,您可以这样做:

echo -e "file 'in1.mp4'\nfile 'in2.mp4'" | ffmpeg -f concat -safe 0 -i /dev/stdin -c copy out.mp4

事实上,每次命令需要传入文件时,您都可以回显一个字符串并将其通过管道发送到命令,只需使用/dev/stdin作为文件名。