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

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

ffmpeg -i part1.mp4 -vcodec copy -vbsf h264_mp4toannexb -acodec copy part1.tsffmpeg -i part2.mp4 -vcodec copy -vbsf h264_mp4toannexb -acodec copy part2.tscat part1.ts part2.ts > parts.tsffmpeg -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/sav_interleaved_write_frame(): Error while opening file

这发生在编码的一半左右,这让我认为你不能将两个. ts文件合并在一起并使其工作。

826262 次浏览

我最终使用mpg作为中间格式,它起作用了(注意这是一个危险的例子,-qScale 0将重新编码视频…)

ffmpeg -i 1.mp4 -qscale 0 1.mpgffmpeg -i 2.mp4 -qscale 0 2.mpgcat 1.mpg 2.mpg | ffmpeg -f mpeg -i - -qscale 0 -vcodec mpeg4 output.mp4

这是我制作的一个脚本,用于将几个GoPro mp4连接到720p mp4中。希望它能有所帮助。

#!/bin/shcmd="( "for i; docmd="${cmd}ffmpeg -i $i -ab 256000 -vb 10000000 -mbd rd -trellis 2 -cmp 2 -subcmp 2 -g 100 -f mpeg -; "donecmd="${cmd} ) | ffmpeg -i - -vb 10000000 -ab 256000 -s 1280x720 -y out-`date +%F-%H%M.%S`.mp4"echo "${cmd}"eval ${cmd}

FFmpeg有三种连接方法:

1.Concat视频滤波器

如果您的输入没有相同的参数(宽度、高度等),或者格式/编解码器不相同,或者您想执行任何过滤,请使用此方法。

请注意,此方法对所有输入执行重新编码。如果你想避免重新编码,你可以只对不匹配的输入重新编码,以便它们共享相同的编解码器和其他参数,然后使用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.concatdemuxer

当您想避免重新编码并且您的格式不支持文件级连接(普通用户使用的大多数文件不支持文件级连接)时,请使用此方法。

$ cat mylist.txtfile '/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.txtffmpeg -safe 0 -f concat -i list.txt -c copy output.mp4

3.concat协议

将此方法与支持文件级连接的格式一起使用(MPEG-1、MPEG-2 PS、DV)。做没有与MP4一起使用。

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

此方法不适用于许多格式,包括MP4,因为这些格式的性质和此方法执行的简单连接。


如果对使用哪种方法有疑问,请尝试Concat分路器。

另见

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

下面将三个.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以打印所有选项(包括所有格式和编解码器特定选项)。

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

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

对于MP4文件:

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

ffmpeg -i myfile1.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts temp1.tsffmpeg -i myfile2.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts temp2.ts// now joinffmpeg -i "concat:temp1.ts|temp2.ts" -c copy -bsf:a aac_adtstoasc output.mp4

注意!:输出将像第一个文件(而不是第二个文件)

对于mp4文件

对于. mp4文件(我从DailyMotion.com获得:一个50分钟的电视节目,只能下载三个部分,作为三个. mp4视频文件),以下是Windows7的有效解决方案,不是是否涉及重新编码文件。

我重命名了这些文件(file1.mp4file2.mp4file3.mp4),以便这些部分按照正确的顺序观看完整的电视节目。

然后我创建了一个简单的批处理文件(concat.bat),内容如下:

:: Create File Listecho file file1.mp4 >  mylist.txtecho file file2.mp4 >> mylist.txtecho file file3.mp4 >> mylist.txt
:: Concatenate Filesffmpeg -f concat -i mylist.txt -c copy output.mp4

批处理文件ffmpeg.exe必须与要加入的. mp4文件放在同一个文件夹中。然后运行批处理文件。运行通常不到十秒钟。
.

增编(2018/10/21)-

如果您正在寻找的是一种用于指定当前文件夹中所有mp4文件而无需大量重新输入的方法,请在Windows批处理文件中尝试此操作(必须包括选项安全0):

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

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

基于rogerdpack和ed999的响应,我创建了我的. sh版本

#!/bin/bash
[ -e list.txt ] && rm list.txtfor f in *.mp4doecho "file $f" >> list.txtdone
ffmpeg -f concat -i list.txt -c copy joined-out.mp4 && rm list.txt

它将当前文件夹中的所有*.mp4文件连接到joined-out.mp4

在mac上测试。

结果文件大小是我60个测试文件的确切总和。不应该有任何损失。正是我需要的

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

合并当前目录中的所有mp4文件

我个人不喜欢创建事后必须删除的外部文件,所以我的解决方案如下,其中包括文件编号列表(如file_1_name, file_2_name, file_10_name, file_20_name, file_100_name,…)

#!/bin/bashfilesList=""for file in $(ls -1v *.mp4);do #lists even numbered filefilesList="${filesList}${file}|"donefilesList=${filesList%?} # removes trailing pipeffmpeg -i "concat:$filesList" -c copy $(date +%Y%m%d_%H%M%S)_merged.mp4

这对我有用(在Windows上)

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

一个例子…

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

python

使用一些python代码来处理文件夹中尽可能多的mp4(从python.org安装python,复制粘贴并将此代码保存到名为mp4.py的文件中,并从使用python在文件夹中打开的cmd运行mp4.py文件夹中的所有mp4都将连接)

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

Python版本2

取自我的发布在我的博客,这是我在python中的操作方式:

import osimport 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 += fif 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()

从这里的留档: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.tsffmpeg -i input2.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate2.tsffmpeg -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!'))

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

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

在大多数平台上下载:https://gpac.wp.imt.fr/mp4box/

我发现管道操作员在接受的答案中使用选项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

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

经过各种尝试,下面的脚本在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 CleanupRemove-Item e:\temp.txt

这里的前两行创建一个文本文件temp.txt它包含以下内容

file 'e:\first.mp4'file 'e:\second.mp4'

第3、4行检查路径上是否有ffmpeg可用,并创建“joined.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,它们的呈现顺序相同)连接您的文件-在我的情况下,它们每个都有一个轨道号,所以它工作得很好。

这里描述的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 asciiffmpeg.exe -safe 0 -f concat -i $tempFile -c copy $outFilePath}finally{Remove-Item $tempFile}

这里有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 arrayif [ $# = 0 ]; then inputfiles=($(ls -1v *.mp4)); fiif [ $# = 2 ]; then inputfiles=($1 $2); fiif [ $# -ge 3 ]; thenoutputfile=${@: -1} # Get the last argumentinputfiles=(${@:1:$# - 1}) # Get all arguments besides last one as arrayfi  
# -y: automatically overwrite output file if exists# -loglevel quiet: disable ffmpeg logsffmpeg -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}

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

这是一个脚本(适用于任意数量的指定文件(不仅仅是工作目录中的所有文件),没有其他文件,也适用于. mov;在macOS上测试):

#!/bin/bash
if [ $# -lt 1 ]; thenecho "Usage: `basename $0` input_1.mp4 input_2.mp4 ... output.mp4"exit 0fi
ARGS=("$@") # determine all argumentsoutput=${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

很晚的回答,但这是唯一对我有效的选择:

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

我有不同编码器的剪辑libx264H.264

我将所有剪辑转换为libx264,并使用了来自投票最多的答案的分机方法。

ffmpeg -i input.flv -vcodec libx264 output.mp4

如果您更喜欢方法#2从rogerdpack的答案,但不想使用管道(例如,您只想在C中使用execv)或不想创建额外的文件(list.txt),那么只需将concat分离器与datafile协议相结合,即FFmpeg允许您内联输入文件,几乎与超文本标记语言一样:

<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 -yffconcat -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.mkvffconcat -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.mp4WARNING: ffconcat uses `concat` demuxer; whenusing both this demuxer AND -y, FFmpeg doesn't check ifan input file and output fileare the same file, so your 100 GB input filecould immediately become 10 KB.ffconcat checks that only when neither -inor new FFmpeg release's boolean args (see valuelessFfmpegArgs in the code)are specified.
ffmpeg has no -http.ffconcat has -http because ffmpeg automaticallysets 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 .mp4Only 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 desyncand also either1) the start of video streams after the first video stream are cut2) 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 estimatedand mpv displays the second video with 1 FPS.You can see the info about an input filewithmediainfo file.mp4orffprobe -hide_banner -protocol_whitelist file,rtp,udp -show_streams file.mp4"}
# Configuration [begin]forceRequireIArgumentForInputFiles=0# Configuration [end]



in_array (){local e match="$1"shiftfor e; do [[ "$e" == "$match" ]] && return 0; donereturn 1}
if [[ "$#" == 0 ]]thenusageexitfi
requireIArgumentForInputFiles=0if in_array "--help" "$@"thenusageexitelif in_array "-help" "$@"thenusageexitelif in_array "-i" "$@"thenrequireIArgumentForInputFiles=1elif [[ "$forceRequireIArgumentForInputFiles" == "1" ]]then>&2 echo "forceRequireIArgumentForInputFiles=1, so you need -i"usageexit 1fi



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=1isHttpMode=0
if in_array "-http" "$@"thenisHttpMode=1fi

# if an argument is not a boolean argument, then what key needs a valuesecondArgumentIsWantedByThisFirstArgument=""# if requireIArgumentForInputFiles=1# then secondArgumentIsWantedByThisFirstArgument must be either "" or "-i"isCurrentArgumentI=0localRawFilesArray=()outputFile=""for arg in "$@"doif [["$secondArgumentIsWantedByThisFirstArgument" == ""&&"$arg" == "-http"]]thencontinuefiif [[ "$arg" == "--" ]]thenareFfmpegArgsAllowed=0continuefiif [[("$areFfmpegArgsAllowed" == "1"||"$secondArgumentIsWantedByThisFirstArgument" != "")&& !("$requireIArgumentForInputFiles" == "1"&&"$secondArgumentIsWantedByThisFirstArgument" == "-i")&&("$secondArgumentIsWantedByThisFirstArgument" != ""||("$requireIArgumentForInputFiles" == "0"&&"$arg" = -*)||("$requireIArgumentForInputFiles" == "1"))]]thenif [[ !("$requireIArgumentForInputFiles" == "1"&&"$arg" == "-i") ]]thenif (( ${#inputFilesRawArray[@]} == 0 ))theninputOptions+=("$arg")elseoutputOptions+=("$arg")fifielif [["$requireIArgumentForInputFiles" == "0"||"$secondArgumentIsWantedByThisFirstArgument" == "-i"]]thenif echo -n "$arg" | egrep '^(https?|ftp)://'theninputFilesRawArray+=("$arg")localRawFilesArray+=("$arg")elsetmp=`echo -n "$arg" | sed 's@^file:@@'`localRawFilesArray+=("$tmp")if [[ "$secondArgumentIsWantedByThisFirstArgument" == "-i" ]]thenif ! ls -1d -- "$tmp" >/dev/null 2>/dev/nullthen>&2 echo "Input file '$tmp' not found"exit 1fifitmp=`echo -n "$tmp" | sed -E 's@(\s|\\\\)@\\\\\1@g' | sed "s@'@\\\\\'@g"`# ^ FIXME: does it work for all filenames?inputFilesRawArray+=("file:$tmp")fielif [["$requireIArgumentForInputFiles" == "1"&&"$secondArgumentIsWantedByThisFirstArgument" != "-i"]]thenif echo -n "$arg" | egrep '^(https?|ftp)://'thenoutputFile="$arg"elseoutputFile=`echo -n "$arg" | sed 's@^file:@@'`outputFile="file:$outputFile"fielseusageexit 1fiif [["$secondArgumentIsWantedByThisFirstArgument" != ""||"$areFfmpegArgsAllowed" == "0"]]thensecondArgumentIsWantedByThisFirstArgument=""elseif [[ "$requireIArgumentForInputFiles" == "1" && "$arg" == "-i" ]]thensecondArgumentIsWantedByThisFirstArgument="$arg"elif [[ "$requireIArgumentForInputFiles" == "0" && "$arg" = -* ]]thenif ! in_array "$arg" ${valuelessFfmpegArgs[@]}thensecondArgumentIsWantedByThisFirstArgument="$arg"fififidoneif [["$requireIArgumentForInputFiles" == "0"&&"$outputFile" == ""]]thenoutputFile="${localRawFilesArray[((${#localRawFilesArray[@]}-1))]}"fiactualOutputFile="$outputFile"if [[ "$requireIArgumentForInputFiles" == "0" || "file:" =~ ^"$outputFile"* ]]thenactualOutputFile=`echo -n "$actualOutputFile" | sed 's@^file:@@'`actualOutputFile=`readlink -nf -- "$actualOutputFile"`fi
if [[ "$requireIArgumentForInputFiles" == "0" ]]thenunset '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: $outputFilefiusage#outputFile=/dev/nullexit 1fiif [["$requireIArgumentForInputFiles" == "0"&&"$outputFile" == ""]]then>&2 echo "Error: No output file specified."usageexit 1fi

ffmpegInputList=""firstFileDone=0inputFilesRawArrayLength=${#inputFilesRawArray[@]}
for (( i = 0; i < inputFilesRawArrayLength; i++ ))dolf="${localRawFilesArray[$i]}"f="${inputFilesRawArray[$i]}"if [[ "${inputFilesRawArray[$i]}" =~ ^file: ]]thenactualF=`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 1fifiif [[ "$firstFileDone" == "1" ]]thenffmpegInputList+="$NL"fiffmpegInputList+="file $f"firstFileDone=1done
protocol_whitelist_appendage=""if [[ "$isHttpMode" == "1" ]]thenprotocol_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 and ' in ffmpegInputList are escaped with `\`.

百分比编码(JavaScript的encodeURI/encodeURIComponent)(%20等)在-i中不需要,与超文本标记语言不同。

对于Windows Powershell

创建文件列表

PS > ls file* | % { $n = $_.name; "file '\$n'" } | out-file mylist.txt

检查创建的文件

PS > cat mylist.txtfile '/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.mkvffmpeg version git-2020-05-15-b18fd2b Copyright (c) 2000-2020 the FFmpeg developers...

对于那些需要连接一些用H.264编码的MP4视频的人,我提出了一个Python脚本mp4concat.py,它可以自动执行ffmpeg留档中的Concat协议/使用中间文件段落。

从上面的链接或使用

wget https://gist.githubusercontent.com/mwouts/115956d3fc7ece55cbce483a7a5148bd/raw/4bc5e03952f1b981dac3f97f4daf82c907774b17/mp4concat.py

然后将其用作

python3 mp4concat.py input1.mp4 ... inputN.mp4 --output output.mp4
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作为文件名。

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

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

快速摘要

创建一个inputs.txt文件,其中包含要组合的所有文件路径列表(参见下面的示例)。然后,将所有上述视频组合成一个,如下所示:

# Option 1 (preferred): into a single mp4 video with AAC audio encoding# and original video encodingtime 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 encodingtime ffmpeg -f concat -safe 0 -i inputs.txt -c copy output.mkv

组合Wyze安全摄像头素材的详细信息和完整示例

在Ubuntu 20.04上测试,将我的一堆Wyze安全摄像头视频组合成一个。(我将相机的microSD卡直接插入我的计算机)。

  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,包括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 encodingtime 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 encodingtime 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 accat:"不安全的文件名"
  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 containerCould 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容器。