我有一个 ffmpeg 命令来连接 300 多个不同格式的视频。 concat 复杂过滤器的正确语法是什么?
I have a ffmpeg command to concatenate 300+ videos of different formats. What is the proper syntax for the concat complex filter?
我打算拼接大量不同格式和分辨率的视频文件,其中一些没有声音,并在每个文件之间添加一个大约 0.5 秒的短暂黑屏“停顿”。
我写了一个python脚本来生成这样的命令。
我使用 ffmpeg.exe -t 0.5 -f lavfi -i color=c=black:s=640x480 -c:v libx264 -tune stillimage -pix_fmt yuv420p blank500ms.mp4
创建了一个 0.5 秒的视频文件。
然后我用 -f lavfi -i anullsrc -c:v copy -c:a aac -shortest
添加了一个无声音频
我现在遇到了为没有流的流添加空白音轨的问题,但我不想生成新文件,我想将它添加到我的复杂过滤器中。
这是我的复杂脚本和生成命令。
命令(有行 returns,因为我用 python 子进程模块发送它)
ffmpeg.exe
-i
input0.mp4
-i
input1.mp4
-i
input2.mp4
-i
input3.mp4
-i
input4.mp4
-i
input5.mp4
-i
input6.mp4
-i
input7.mp4
-i
input8.mp4
-i
input9.mp4
-i
input10.mp4
-f
lavfi
-i
anullsrc
-filter_complex_script
C:/filter_complex_script.txt
-map
"[final_video]"
-map
"[final_audio]"
output.mp4
complex_filter_script:
[0]fps=24[fps0];
[fps0]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled0];
[1]fps=24[fps1];
[fps1]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled1];
[2]fps=24[fps2];
[fps2]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled2];
[3]fps=24[fps3];
[fps3]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled3];
[4]fps=24[fps4];
[fps4]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled4];
[5]fps=24[fps5];
[fps5]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled5];
[6]fps=24[fps6];
[fps6]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled6];
[7]fps=24[fps7];
[fps7]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled7];
[8]fps=24[fps8];
[fps8]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled8];
[9]fps=24[fps9];
[fps9]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled9];
[10]fps=24[fps10];
[fps10]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled10];
[10]split=10[blank0][blank1][blank2][blank3][blank4][blank5][blank6][blank7][blank8][blank9];
[rescaled0:v][0:a][blank0][rescaled1:v][1:a][blank1][rescaled2:v][2:a][blank2][rescaled3:v][3:a][blank3][rescaled4:v][4:a][blank4][rescaled5:v][5:a][blank5][rescaled6:v][11:a][blank6][rescaled7:v][11:a][blank7][rescaled8:v][11:a][blank8][rescaled9:v][11:a][blank9]concat=n=22:v=1:a=1[final_video][final_audio]
如您所见,一些视频使用 [11:a]
,因为它是无声音频流。
input10.mp4,映射到 [10],然后拆分(或“克隆”)成 blanked0 到 9,是一个短停顿分隔符。
ffmpeg 告诉我错误
[Parsed_split_55 @ 000001591c33b280] Media type mismatch between the 'Parsed_split_55' filter output pad 1 (video) and the 'Parsed_concat_56' filter input pad 5 (audio)
[AVFilterGraph @ 000001591bf1e6c0] Cannot create the link split:1 -> concat:5
Error initializing complex filters.
Invalid argument
关于使用 [X:Y:Z] 语法,以及顺序在 concat 参数列表中的重要性,我有点迷茫。
我愿意接受任何其他建议来解决我的问题。我宁愿在没有中间文件的情况下在单个命令中执行此操作。
编辑:
有关详细信息,我已经编写了一个大型 concat+xstack 过滤器,它适用于 8GB 内存。
在这种情况下,有很多输入,但这些输入很小,大部分在1到10MB之间,所以它可能不会产生内存不足的问题,虽然我不确定.
虽然理论上可行,但我不建议使用如此多的输入文件调用 FFmpeg。这将增加 运行 时间的内存占用,并可能降低速度(如果不抛出 out-of-memory 错误)。相反,我的建议是分两步解决这个问题:
- 第 1 步:对每个视频文件进行转码,以便每个文件都按照您喜欢的方式正确编码。循环执行此操作并保存为中间文件。
- 第2步:Copy-concat所有中间文件形成最终输出
这里的重要部分是所有临时文件都具有完全相同的流配置。视频:编解码器、帧率 (fps
)、pix_fmt (pfmt
)、大小 (w
、h
) 和时基和音频:编解码器、sample_fmt (sfmt
)、采样率 (fs
)、通道布局 ('layout') 和时基。 (我在下面花括号内的命令草图中使用了这些“变量”。)
步骤 1 命令草图:
下面我假设输入文件中的视频和音频配置除了大小之外是相同的,您已经在代码中解决了这一点。如果没有,您可能需要额外的过滤器。
- 如果视频文件同时包含音频和视频:
ffmpeg -i input.mp4 \
-f lavfi -i color=c=black:s={w}x{h}:d=0.5:r={fps},format={pfmt} \
-f lavfi -i aevalsrc=0:n=1:c={layout}:s={fs},aformat={sfmt} \
-filter_complex [0:v]scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:-1:-1,setsar=1[v]; \
[v][0:a][1:v][2:a]concat=n=2:v=1:a=1[vout][aout] \
-map [vout] -map [aout] -enc_time_base 0 output.mp4
- 如果视频文件只有视频流:
ffmpeg -i input.mp4 \
-f lavfi -i color=c=black:s={w}x{h}:d=0.5:r={fps},format={pfmt} \
-f lavfi -i aevalsrc=0:n=1:c={layout}:s={fs},aformat={sfmt} \
-filter_complex [0:v]scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:-1:-1,setsar=1[v]; \
[v][2:a][1:v][2:a]concat=n=2:v=1:a=1[vout][aout] \
-map [vout] -map [aout] -enc_time_base 0 output.mp4
请注意,1 和 2 之间的唯一区别是 concat
过滤器的第二个输入。如果音频丢失,只需使用 aevalsrc
作为丢失的流。
- 最后一个输入视频没有 0.5 秒的填充:
有音频
ffmpeg -i input.mp4 \
-vf scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:-1:-1,setsar=1 \
-enc_time_base 0 output.mp4
没有音频:
ffmpeg -i input.mp4 \
-f lavfi -i aevalsrc=0:n=1:c={layout}:s={fs},aformat={sfmt} \
-filter_complex [0:v]scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:-1:-1,setsar=1[v]; \
[v][2:a]concat=n=1:v=1:a=1[vout][aout] \
-map [vout] -map [aout] -enc_time_base 0 output.mp4
- 使用
ffprobe
判断文件是否有音频流(也可以使用ffmpeg,但我更喜欢这种方式):
ffprobe -of default=nk=1:nw=1 -select_streams a -show_entries stream input.mp4
在python中,您可以运行此命令与subprocess.run
与stdout=sp.PIPE
并检查获得的stdout
字节的长度(>0与音频,=0 无音频)。
- 在 运行 编译 per-input ffmpeg 的同时,还编写了
ffconcat
文本文件。
The concat
demuxer 以文本文件作为输入,格式如下:
ffconcat version 1.0
file output1.mp4
file output2.mp4
...
其中 output#.mp4
是您在循环中生成的文件的名称。在第 1 步循环中构建此文件并将其保存在与中间视频文件相同的目录中(称之为 ffconcat.txt
)。
步骤 2 命令草图
至此大部分工作已经完成,您应该可以通过以下方式获取最终视频:
ffmpeg -i ffconcat.txt -c copy final.mp4
警告:我没有测试这些代码,所以如果您遇到任何您无法理解的拼写错误,请发表评论,我很乐意 correct/clarify。
One-n-done草图
上面写的可以扩展到single-run(或部分组合)方法。假设有100个文件,那么你可以这样做:
ffmpeg -i input0.mp4 -i input1.mp4 ... -i input99.mp4 \
-f lavfi -i color=c=black:s={w}x{h}:d=0.5:r={fps},format={pfmt} \
-f lavfi -i aevalsrc=0:n=1:c={layout}:s={fs},aformat={sfmt} \
-filter_complex \
[0:v]scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:-1:-1,setsar=1[v0]; \
[1:v]scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:-1:-1,setsar=1[v1]; \
...
[99:v]scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:-1:-1,setsar=1[v99]; \
[v0][0:a][100:v][101:a][v2][101:a][100:v][101:a]...[100:v][101:a][v99][99:a]concat=n=199:v=1:a=1[vout][aout] \
-map [vout] -map [aout] output.mp4
在这里,我假设第一个和最后一个有音频,第二个没有音频。输入 #100 = color
过滤器,输入 #101 = aevalsrc
过滤器。要连接的 video-audio 流对总数为 199(100 个视频和 99 个 0.5 秒暂停。这里的关键是您可以根据需要多次重复使用过滤器输出。
我打算拼接大量不同格式和分辨率的视频文件,其中一些没有声音,并在每个文件之间添加一个大约 0.5 秒的短暂黑屏“停顿”。
我写了一个python脚本来生成这样的命令。
我使用 ffmpeg.exe -t 0.5 -f lavfi -i color=c=black:s=640x480 -c:v libx264 -tune stillimage -pix_fmt yuv420p blank500ms.mp4
创建了一个 0.5 秒的视频文件。
然后我用 -f lavfi -i anullsrc -c:v copy -c:a aac -shortest
我现在遇到了为没有流的流添加空白音轨的问题,但我不想生成新文件,我想将它添加到我的复杂过滤器中。
这是我的复杂脚本和生成命令。
命令(有行 returns,因为我用 python 子进程模块发送它)
ffmpeg.exe
-i
input0.mp4
-i
input1.mp4
-i
input2.mp4
-i
input3.mp4
-i
input4.mp4
-i
input5.mp4
-i
input6.mp4
-i
input7.mp4
-i
input8.mp4
-i
input9.mp4
-i
input10.mp4
-f
lavfi
-i
anullsrc
-filter_complex_script
C:/filter_complex_script.txt
-map
"[final_video]"
-map
"[final_audio]"
output.mp4
complex_filter_script:
[0]fps=24[fps0];
[fps0]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled0];
[1]fps=24[fps1];
[fps1]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled1];
[2]fps=24[fps2];
[fps2]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled2];
[3]fps=24[fps3];
[fps3]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled3];
[4]fps=24[fps4];
[fps4]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled4];
[5]fps=24[fps5];
[fps5]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled5];
[6]fps=24[fps6];
[fps6]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled6];
[7]fps=24[fps7];
[fps7]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled7];
[8]fps=24[fps8];
[fps8]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled8];
[9]fps=24[fps9];
[fps9]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled9];
[10]fps=24[fps10];
[fps10]scale=480:270:force_original_aspect_ratio=decrease,pad=480:270:(ow-iw)/2:(oh-ih)/2,setsar=1,setpts=PTS-STARTPTS[rescaled10];
[10]split=10[blank0][blank1][blank2][blank3][blank4][blank5][blank6][blank7][blank8][blank9];
[rescaled0:v][0:a][blank0][rescaled1:v][1:a][blank1][rescaled2:v][2:a][blank2][rescaled3:v][3:a][blank3][rescaled4:v][4:a][blank4][rescaled5:v][5:a][blank5][rescaled6:v][11:a][blank6][rescaled7:v][11:a][blank7][rescaled8:v][11:a][blank8][rescaled9:v][11:a][blank9]concat=n=22:v=1:a=1[final_video][final_audio]
如您所见,一些视频使用 [11:a]
,因为它是无声音频流。
input10.mp4,映射到 [10],然后拆分(或“克隆”)成 blanked0 到 9,是一个短停顿分隔符。
ffmpeg 告诉我错误
[Parsed_split_55 @ 000001591c33b280] Media type mismatch between the 'Parsed_split_55' filter output pad 1 (video) and the 'Parsed_concat_56' filter input pad 5 (audio)
[AVFilterGraph @ 000001591bf1e6c0] Cannot create the link split:1 -> concat:5
Error initializing complex filters.
Invalid argument
关于使用 [X:Y:Z] 语法,以及顺序在 concat 参数列表中的重要性,我有点迷茫。
我愿意接受任何其他建议来解决我的问题。我宁愿在没有中间文件的情况下在单个命令中执行此操作。
编辑:
有关详细信息,我已经编写了一个大型 concat+xstack 过滤器,它适用于 8GB 内存。
在这种情况下,有很多输入,但这些输入很小,大部分在1到10MB之间,所以它可能不会产生内存不足的问题,虽然我不确定.
虽然理论上可行,但我不建议使用如此多的输入文件调用 FFmpeg。这将增加 运行 时间的内存占用,并可能降低速度(如果不抛出 out-of-memory 错误)。相反,我的建议是分两步解决这个问题:
- 第 1 步:对每个视频文件进行转码,以便每个文件都按照您喜欢的方式正确编码。循环执行此操作并保存为中间文件。
- 第2步:Copy-concat所有中间文件形成最终输出
这里的重要部分是所有临时文件都具有完全相同的流配置。视频:编解码器、帧率 (fps
)、pix_fmt (pfmt
)、大小 (w
、h
) 和时基和音频:编解码器、sample_fmt (sfmt
)、采样率 (fs
)、通道布局 ('layout') 和时基。 (我在下面花括号内的命令草图中使用了这些“变量”。)
步骤 1 命令草图:
下面我假设输入文件中的视频和音频配置除了大小之外是相同的,您已经在代码中解决了这一点。如果没有,您可能需要额外的过滤器。
- 如果视频文件同时包含音频和视频:
ffmpeg -i input.mp4 \
-f lavfi -i color=c=black:s={w}x{h}:d=0.5:r={fps},format={pfmt} \
-f lavfi -i aevalsrc=0:n=1:c={layout}:s={fs},aformat={sfmt} \
-filter_complex [0:v]scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:-1:-1,setsar=1[v]; \
[v][0:a][1:v][2:a]concat=n=2:v=1:a=1[vout][aout] \
-map [vout] -map [aout] -enc_time_base 0 output.mp4
- 如果视频文件只有视频流:
ffmpeg -i input.mp4 \
-f lavfi -i color=c=black:s={w}x{h}:d=0.5:r={fps},format={pfmt} \
-f lavfi -i aevalsrc=0:n=1:c={layout}:s={fs},aformat={sfmt} \
-filter_complex [0:v]scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:-1:-1,setsar=1[v]; \
[v][2:a][1:v][2:a]concat=n=2:v=1:a=1[vout][aout] \
-map [vout] -map [aout] -enc_time_base 0 output.mp4
请注意,1 和 2 之间的唯一区别是 concat
过滤器的第二个输入。如果音频丢失,只需使用 aevalsrc
作为丢失的流。
- 最后一个输入视频没有 0.5 秒的填充:
有音频
ffmpeg -i input.mp4 \
-vf scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:-1:-1,setsar=1 \
-enc_time_base 0 output.mp4
没有音频:
ffmpeg -i input.mp4 \
-f lavfi -i aevalsrc=0:n=1:c={layout}:s={fs},aformat={sfmt} \
-filter_complex [0:v]scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:-1:-1,setsar=1[v]; \
[v][2:a]concat=n=1:v=1:a=1[vout][aout] \
-map [vout] -map [aout] -enc_time_base 0 output.mp4
- 使用
ffprobe
判断文件是否有音频流(也可以使用ffmpeg,但我更喜欢这种方式):
ffprobe -of default=nk=1:nw=1 -select_streams a -show_entries stream input.mp4
在python中,您可以运行此命令与subprocess.run
与stdout=sp.PIPE
并检查获得的stdout
字节的长度(>0与音频,=0 无音频)。
- 在 运行 编译 per-input ffmpeg 的同时,还编写了
ffconcat
文本文件。
The concat
demuxer 以文本文件作为输入,格式如下:
ffconcat version 1.0
file output1.mp4
file output2.mp4
...
其中 output#.mp4
是您在循环中生成的文件的名称。在第 1 步循环中构建此文件并将其保存在与中间视频文件相同的目录中(称之为 ffconcat.txt
)。
步骤 2 命令草图
至此大部分工作已经完成,您应该可以通过以下方式获取最终视频:
ffmpeg -i ffconcat.txt -c copy final.mp4
警告:我没有测试这些代码,所以如果您遇到任何您无法理解的拼写错误,请发表评论,我很乐意 correct/clarify。
One-n-done草图
上面写的可以扩展到single-run(或部分组合)方法。假设有100个文件,那么你可以这样做:
ffmpeg -i input0.mp4 -i input1.mp4 ... -i input99.mp4 \
-f lavfi -i color=c=black:s={w}x{h}:d=0.5:r={fps},format={pfmt} \
-f lavfi -i aevalsrc=0:n=1:c={layout}:s={fs},aformat={sfmt} \
-filter_complex \
[0:v]scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:-1:-1,setsar=1[v0]; \
[1:v]scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:-1:-1,setsar=1[v1]; \
...
[99:v]scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:-1:-1,setsar=1[v99]; \
[v0][0:a][100:v][101:a][v2][101:a][100:v][101:a]...[100:v][101:a][v99][99:a]concat=n=199:v=1:a=1[vout][aout] \
-map [vout] -map [aout] output.mp4
在这里,我假设第一个和最后一个有音频,第二个没有音频。输入 #100 = color
过滤器,输入 #101 = aevalsrc
过滤器。要连接的 video-audio 流对总数为 199(100 个视频和 99 个 0.5 秒暂停。这里的关键是您可以根据需要多次重复使用过滤器输出。