使用ffmpeg合并(连接)文件

原文:

https://www.nmm-hd.org/newbbs/viewtopic.php?t=1533

https://superuser.com/questions/277642/how-to-merge-audio-and-video-file-in-ffmpeg

https://trac.ffmpeg.org/wiki/Concatenate

关键词:concatenate;append;合并;连接
使用ffmpeg连接文件分两类,连接编码完全相同的文件;连接编码不同的文件

连接编码完全相同的文件
有两种方式,使用concat “分离器(demuxer)”和concat “协议(protocol)。demuxer比较自由,编码相同、但是多媒体文件容器不同也能连接。因此demuxer能处理各种容器,而protocol只能处理区区几种容器。老版本的ffmpeg只能用protocol,最近demuxer在ffmpeg中出现。

concat demuxer是FFmpeg 1.1添加进来的,文档见此
使用方法
创建一个 mylist.txt 文件,每行写一个想要连接的文件的路径,格式如下:

代码: 全选

#该行为注释
file '/path/to/file1'
file '/path/to/file2'
file '/path/to/file3'

可以用绝对路径,也可以用相对路径。然后使用stream copy或重编码:

代码: 全选

ffmpeg -f concat -safe 0 -i mylist.txt -c copy output

 

demuxer在“流”层面工作,与之不同的 是protocol在文件层面工作,因此只有特定格式的文件能连接(像mpg或mpeg transport stream文件,也可能有其他的),类似于UNIX类系统里的cat和windows系统里的copy。

代码: 全选

ffmpeg -i "concat:input1.mpg|input2.mpg|input3.mpg" -c copy output.mpg

如果不是mpg格式的文件呢?可以先用ffmpeg转成mpeg transport stream,再连接。举个例子,h264视频和aac音频的mp4:

代码: 全选

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

这种方法会产生不少临时文件,如果你会用named pipe,原帖有方法可以一行解决。
所有mpeg编码的格式(H.264, MPEG4/divx/xvid, MPEG2; MP2, MP3, AAC)都可以转成mpeg transport stream,不过有时需要加一些额外的命令(具体的-bsf命令)。

连接不同编码的多媒体文件
concat滤镜
最近新版本的ffmpeg里有concat滤镜。更多信息参考concat滤镜文档。
使用方法用例子说明

代码: 全选

ffmpeg -i input1.mp4 -i input2.webm \
-filter_complex '[0:0] [0:1] [1:0] [1:1] concat=n=2:v=1:a=1 [v] [a]' \
-map '[v]' -map '[a]' <encoding options> output.mkv

-filter_complex这行的 ‘[0:0] [0:1] [1:0] [1:1] 是告诉ffmpeg将哪些码流送到concat滤镜里。这里,代表输入文件0(例子中的input1.mp4)的0号流和1号流、输入文件1(栗子中的input2.webm)的0号流和1号流。

concat=n=2:v=1:a=1 [v] [a]’ 就是调用concat滤镜。n=2告诉滤镜有两个输入文件;v=1告诉滤镜有1个视频流,a=1告诉滤镜有1个音频流。 [v]和[a]定义输出流的名称,ffmpeg的其他部分就知道concat的输出了。
需要注意的是,整个滤镜需要用单引号包围。

代码: 全选

-map '[v]' -map '[a]'

告诉ffmpeg用concat滤镜的输出流,而不是用原来文件的流。
注意:此滤镜和重新封装(流复制stream copying)不兼容,不能用-c copy。另外,不知道这种方式支不支持软字幕。
这个例子还告诉我们,输入文件格式不同concat也是支持的。ffmpeg能够解码的任何格式都可以操作,但是分辨率和一些其他的属性需要匹配。

Merging video and audio, with audio re-encoding

See this example, taken from this blog entry but updated for newer syntax. It should be something to the effect of:

ffmpeg -i video.mp4 -i audio.wav \
-c:v copy -c:a aac -strict experimental output.mp4

Here, we assume that the video file does not contain any audio stream yet, and that you want to have the same output format (here, MP4) as the input format.

The above command transcodes the audio, since MP4s cannot carry PCM audio streams. You can use any other desired audio codec if you want. See the AAC Encoding Guide for more info.

If your audio or video stream is longer, you can add the -shortest option so that ffmpeg will stop encoding once one file ends.

Copying the audio without re-encoding

If your output container can handle (almost) any codec – like MKV – then you can simply copy both audio and video streams:

ffmpeg -i video.mp4 -i audio.wav -c copy output.mkv

Replacing audio stream

If your input video already contains audio, and you want to replace it, you need to tell ffmpeg which audio stream to take:

ffmpeg -i video.mp4 -i audio.wav \
-c:v copy -c:a aac -strict experimental \
-map 0:v:0 -map 1:a:0 output.mp4

The map option makes ffmpeg only use the first video stream from the first input and the first audio stream from the second input for the output file.

 

Concatenating media files

If you have media files with exactly the same codec and codec parameters you can concatenate them as described in “Concatenation of files with same codecs“. If you have media with different codecs you can concatenate them as described in “Concatenation of files with different codecs” below.

Concatenation of files with same codecs

There are two methods within ffmpeg that can be used to concatenate files of the same type: the concat ”demuxer” and the concat ”protocol”. The demuxer is more flexible – it requires the same codecs, but different container formats can be used; and it can be used with any container formats, while the protocol only works with a select few containers. However, the concat protocol is available in older versions of ffmpeg, where the demuxer isn’t. The demuxer also requires that the inputs have a consistent bitrate setting, the concat protocol is more flexible in this regard.

Concat demuxer

The concat demuxer was added to FFmpeg 1.1. You can read about it in the documentation.

Instructions

Create a file mylist.txt with all the files you want to have concatenated in the following form (lines starting with a # are ignored):

# this is a comment
file '/path/to/file1'
file '/path/to/file2'
file '/path/to/file3'

Note that these can be either relative or absolute paths. Then you can stream copy or re-encode your files:

ffmpeg -f concat -safe 0 -i mylist.txt -c copy output

The -safe 0 above is not required if the paths are relative.

It is possible to generate this list file with a bash for loop, or using printf. Either of the following would generate a list file containing every *.wav in the working directory:

# with a bash for loop
for f in ./*.wav; do echo "file '$f'" >> mylist.txt; done
# or with printf
printf "file '%s'\n" ./*.wav > mylist.txt

On Windows Command-line:

(for %i in (*.wav) do @echo file '%i') > mylist.txt

If your shell supports process substitution (like Bash and Zsh), you can avoid explicitly creating a list file and do the whole thing in a single line. This would be impossible with the concat protocol (see below). Make sure to generate absolute paths here, since ffmpeg will resolve paths relative to the list file your shell may create in a directory such as “/proc/self/fd/”.

ffmpeg -f concat -safe 0 -i <(for f in ./*.wav; do echo "file '$PWD/$f'"; done) -c copy output.wav
ffmpeg -f concat -safe 0 -i <(printf "file '$PWD/%s'\n" ./*.wav) -c copy output.wav
ffmpeg -f concat -safe 0 -i <(find . -name '*.wav' -printf "file '$PWD/%p'\n") -c copy output.wav

You can also loop a video. This example will loop input.mkv 10 times:

for i in {1..10}; do printf "file '%s'\n" input.mkv >> mylist.txt; done
ffmpeg -f concat -i mylist.txt -c copy output.mkv

Concatenation becomes troublesome, if next clip for concatenation does not exist at the moment, because decoding won’t start until the whole list is read. However, it is possible to refer another list at the end of the current list:

#!/bin/bash

fn_concat_init() {
    echo "fn_concat_init"
    concat_pls=`mktemp -u -p . concat.XXXXXXXXXX.txt`
    concat_pls="${concat_pls#./}"
    echo "concat_pls=${concat_pls:?}"
    mkfifo "${concat_pls:?}"
    echo
}

fn_concat_feed() {
    echo "fn_concat_feed ${1:?}"
    {
        >&2 echo "removing ${concat_pls:?}"
        rm "${concat_pls:?}"
        concat_pls=
        >&2 fn_concat_init
        echo 'ffconcat version 1.0'
        echo "file '${1:?}'"
        echo "file '${concat_pls:?}'"
    } >"${concat_pls:?}"
    echo
}

fn_concat_end() {
    echo "fn_concat_end"
    {
        >&2 echo "removing ${concat_pls:?}"
        rm "${concat_pls:?}"
        # not writing header.
    } >"${concat_pls:?}"
    echo
}

fn_concat_init

echo "launching ffmpeg ... all.mkv"
timeout 60s ffmpeg -y -re -loglevel warning -i "${concat_pls:?}" -pix_fmt yuv422p all.mkv &

ffplaypid=$!


echo "generating some test data..."
i=0; for c in red yellow green blue; do
    ffmpeg -loglevel warning -y -f lavfi -i testsrc=s=720x576:r=12:d=4 -pix_fmt yuv422p -vf "drawbox=w=50:h=w:t=w:c=${c:?}" test$i.mkv
    fn_concat_feed test$i.mkv
    ((i++));
    echo
done
echo "done"

fn_concat_end

wait "${ffplaypid:?}"

echo "done encoding all.mkv"

Concat protocol

While the demuxer works at the stream level, the concat protocol works at the file level. Certain files (mpg and mpeg transport streams, possibly others) can be concatenated. This is analogous to using cat on UNIX-like systems or copy on Windows.

Instructions

ffmpeg -i "concat:input1.mpg|input2.mpg|input3.mpg" -c copy output.mpg

If you have MP4 files, these could be losslessly concatenated by first transcoding them to mpeg transport streams. With h.264 video and AAC audio, the following can be used:

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

If you’re using a system that supports named pipes, you can use those to avoid creating intermediate files – this sends stderr (which ffmpeg sends all the written data to) to /dev/null, to avoid cluttering up the command-line:

mkfifo temp1 temp2
ffmpeg -i input1.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts temp1 2> /dev/null & \
ffmpeg -i input2.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts temp2 2> /dev/null & \
ffmpeg -f mpegts -i "concat:temp1|temp2" -c copy -bsf:a aac_adtstoasc output.mp4

All MPEG codecs (H.264, MPEG4/divx/xvid, MPEG2; MP2, MP3, AAC) are supported in the mpegts container format, though the commands above would require some alteration (the -bsf bitstream filters will have to be changed).

Concatenation of files with different codecs

Concat filter

The concat filter is available in recent versions of ffmpeg. See the concat filter documentation for more info.

Instructions

This is easiest to explain using an example:

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]" <encoding options> output.mkv

On the -filter_complex line, the following:

[0:v:0] [0:a:0] [1:v:0] [1:a:0]

tells ffmpeg what streams to send to the concat filter; in this case, video stream 0 [0:v:0] and audio stream 0 [0:a:0] from input 0 (input1.mp4 in this example), and video stream 0 [1:v:0] and audio stream 0 [1:v:0] from input 1 (input2.webm).

concat=n=2:v=1:a=1 [v] [a]'

This is the concat filter itself. n=2 is telling the filter that there are two input files; v=1 is telling it that there will be one video stream; a=1 is telling it that there will be one audio stream. [v] and [a] are names for the output streams to allow the rest of the ffmpeg line to use the output of the concat filter.

Note that the single quotes around the whole filter section are required.

-map '[v]' -map '[a]'

This tells ffmpeg to use the results of the concat filter rather than the streams directly from the input files.

Note that filters are incompatible with stream copying; you can’t use -c copy with this method. Also, I’m not sure whether softsubs are supported.

As you can infer from this example, multiple types of input are supported, and anything readable by ffmpeg should work. The inputs have to be of the same frame size, and a handful of other attributes have to match.

Using an external script

With any vaguely-modern version of ffmpeg, the following script is made redundant by the advent the concat filter, which achieves the same result in a way that works across platforms. It is a clever workaround of ffmpeg’s then-limitations, but most people (i.e. anyone not stuck using an ancient version of ffmpeg for whatever reason) should probably use one of the methods listed above.

The following script can be used to concatenate multiple input media files (containing audio/video streams) into one output file (just like as if all the inputs were played in a playlist, one after another). It is based on this FAQ item: How can I join video files, which also contains other useful information.

If you find any bugs, feel free to correct the script, add yourself to the list of contributors and change the version string to reflect your change(s) or email the author with your patch, whatever you find more convenient.

Instructions

Save the script in a file named mmcat (or some other name), make it executable (chmod +x mmcat) and run it, using the syntax:

./mmcat <input1> <input2> <input3> ... <output>

If you get an error like this:

#/tmp/mcs_v_all: Operation not permitted

that could mean that you don’t have correct permissions set on /tmp directory (or whatever you set in TMP variable) or that decoding of your input media has failed for some reason. In this case it would be the best to turn on the logging (as described in the script’s comments)

Script

#!/bin/bash

################################################################################
#
# Script name: MultiMedia Concat Script (mmcat)
# Author: burek ([email protected])
# License: GNU/GPL, see http://www.gnu.org/copyleft/gpl.html
# Date: 2012-07-14
#
# This script concatenates (joins, merges) several audio/video inputs into one
# final output (just like as if all the inputs were played in a playlist, one
# after another).
#
# All input files must have at least one audio and at least one video stream.
# If not, you can easily add audio silence, using FFmpeg. Just search the
# internet for "ffmpeg add silence".
#
# The script makes use of FFmpeg tool (www.ffmpeg.org) and is free for use under
# the GPL license. The inspiration for this script came from this FAQ item:
# http://ffmpeg.org/faq.html#How-can-I-join-video-files_003f
#
# If you find any bugs, please send me an e-mail so I can fix it.
#
################################################################################
#
# General syntax: mmcat <input1> <input2> <input3> ... <output>
#
# For example: mmcat file1.flv file2.flv output.flv
# would create "output.flv" out of "file1.flv" and "file2.flv".
#
################################################################################

# change this to what you need !!!
EXTRA_OPTIONS='-vcodec libx264 -crf 23 -preset medium -acodec aac -strict experimental -ac 2 -ar 44100 -ab 128k'

################################################################################
#
# NO NEED TO TOUCH ANYTHING AFTER THIS LINE!
#
################################################################################

# the version of the script
VERSION=1.3

# location of temp folder
TMP=/tmp

################################################################################

echo "MultiMedia Concat Script v$VERSION (mmcat) - A script to concatenate multiple multimedia files."
echo "Based on FFmpeg - www.ffmpeg.org"
echo "Don't forget to edit this script and change EXTRA_OPTIONS"
echo ""

################################################################################
# syntax check (has to have at least 3 params: infile1, infile2, outfile
################################################################################
if [ -z $3 ]; then
	echo "Syntax: $0 <input1> <input2> <input3> ... <output>"
	exit 1
fi

################################################################################
# get all the command line parameters, except for the last one, which is output
################################################################################
# $first  - first parameter
# $last   - last parameter (output file)
# $inputs - all the inputs, except the first input, because 1st input is
#           handled separately
################################################################################
first=${@:1:1}
last=${@:$#:1}
len=$(($#-2))
inputs=${@:2:$len}

# remove all previous tmp fifos (if exist)
rm -f $TMP/mcs_*

################################################################################
# decode first input differently, because the video header does not have to be
# kept for each video input, only the header from the first video is needed
################################################################################
mkfifo $TMP/mcs_a1 $TMP/mcs_v1

ffmpeg -y -i $first -vn -f u16le -acodec pcm_s16le -ac 2 -ar 44100 $TMP/mcs_a1 2>/dev/null </dev/null &
ffmpeg -y -i $first -an -f yuv4mpegpipe -vcodec rawvideo $TMP/mcs_v1 2>/dev/null </dev/null &

# if you need to log the output of decoding processes (usually not necessary)
# then replace the "2>/dev/null" in 2 lines above with your log file names, like this:
#ffmpeg -y -i $first -vn -f u16le -acodec pcm_s16le -ac 2 -ar 44100 $TMP/mcs_a1 2>$TMP/log.a.1 </dev/null &
#ffmpeg -y -i $first -an -f yuv4mpegpipe -vcodec rawvideo $TMP/mcs_v1 2>$TMP/log.v.1 </dev/null &

################################################################################
# decode all the other inputs, remove first line of video (header) with tail
# $all_a and $all_v are lists of all a/v fifos, to be used by "cat" later on
################################################################################
all_a=$TMP/mcs_a1
all_v=$TMP/mcs_v1
i=2
for f in $inputs
do
	mkfifo $TMP/mcs_a$i $TMP/mcs_v$i

	ffmpeg -y -i $f -vn -f u16le -acodec pcm_s16le -ac 2 -ar 44100 $TMP/mcs_a$i 2>/dev/null </dev/null &
	{ ffmpeg -y -i $f -an -f yuv4mpegpipe -vcodec rawvideo - 2>/dev/null </dev/null | tail -n +2 > $TMP/mcs_v$i ; } &

	# if you need to log the output of decoding processes (usually not necessary)
	# then replace the "2>/dev/null" in 2 lines above with your log file names, like this:
	#ffmpeg -y -i $f -vn -f u16le -acodec pcm_s16le -ac 2 -ar 44100 $TMP/mcs_a$i 2>$TMP/log.a.$i </dev/null &
	#{ ffmpeg -y -i $f -an -f yuv4mpegpipe -vcodec rawvideo - 2>$TMP/log.v.$i </dev/null | tail -n +2 > $TMP/mcs_v$i ; } &

	all_a="$all_a $TMP/mcs_a$i"
	all_v="$all_v $TMP/mcs_v$i"
	let i++
done

################################################################################
# concatenate all raw audio/video inputs into one audio/video
################################################################################
mkfifo $TMP/mcs_a_all
mkfifo $TMP/mcs_v_all
cat $all_a > $TMP/mcs_a_all &
cat $all_v > $TMP/mcs_v_all &

################################################################################
# finally, encode the raw concatenated audio/video into something useful
################################################################################
ffmpeg -f u16le -acodec pcm_s16le -ac 2 -ar 44100 -i $TMP/mcs_a_all \
       -f yuv4mpegpipe -vcodec rawvideo -i $TMP/mcs_v_all \
	$EXTRA_OPTIONS \
	$last

################################################################################
# remove all fifos
################################################################################
rm -f $TMP/mcs_*

The script above can be modified to use the ‘-f avi’ insted of ‘-f yuv4mpegpipe’. Benefits:

  • unlike yuv4mpegpipe, just one pipe for both video and audio
  • unlike yuv4mpegpipe, matroska and flv, no need to skip header in second file using tail, because avi demuxer will skip it automatically
  • unlike mpegts, avi supports rawvideo and pcm

Pipe-friendly formats

Pipe friendly formats