使用 ffmpeg 和 xfade 过滤器合并多个视频文件
Merging multiple video files with ffmpeg and xfade filter
我需要将多个视频文件(包含音频)合并为一个视频。我注意到 xfade 最近发布并使用了它,但我 运行 遇到了音频同步问题。
所有视频的视频和音频格式/分辨率/声望和比特率/等都相同。
这是我用来合并 5 个不同持续时间的视频和 0.5 交叉淡入淡出过渡的方法:
ffmpeg \
-i v0.mp4 \
-i v1.mp4 \
-i v2.mp4 \
-i v3.mp4 \
-i v4.mp4 \
-filter_complex \
"[0][1]xfade=transition=fade:duration=0.5:offset=3.5[V01]; \
[V01][2]xfade=transition=fade:duration=0.5:offset=32.75[V02]; \
[V02][3]xfade=transition=fade:duration=0.5:offset=67.75[V03]; \
[V03][4]xfade=transition=fade:duration=0.5:offset=98.75[video]; \
[0:a][1:a]acrossfade=d=0.5:c1=tri:c2=tri[A01]; \
[A01][2:a]acrossfade=d=0.5:c1=tri:c2=tri[A02]; \
[A02][3:a]acrossfade=d=0.5:c1=tri:c2=tri[A03]; \
[A03][4:a]acrossfade=d=0.5:c1=tri:c2=tri[audio]" \
-vsync 0 -map "[video]" -map "[audio]" out.mp4
上面的代码生成了一个带音频的视频。第一和第二段与音频对齐,但从第二个过渡开始,声音未对齐。
您的偏移量不正确。尝试:
ffmpeg -i v0.mp4 -i v1.mp4 -i v2.mp4 -i v3.mp4 -i v4.mp4 -filter_complex \
"[0][1:v]xfade=transition=fade:duration=1:offset=3[vfade1]; \
[vfade1][2:v]xfade=transition=fade:duration=1:offset=10[vfade2]; \
[vfade2][3:v]xfade=transition=fade:duration=1:offset=21[vfade3]; \
[vfade3][4:v]xfade=transition=fade:duration=1:offset=25,format=yuv420p; \
[0:a][1:a]acrossfade=d=1[afade1]; \
[afade1][2:a]acrossfade=d=1[afade2]; \
[afade2][3:a]acrossfade=d=1[afade3]; \
[afade3][4:a]acrossfade=d=1" \
-movflags +faststart out.mp4
如何获取 xfade offset
值:
input
input duration
+
previous xfade offset
-
xfade duration
offset
=
v0.mp4
4
+
0
-
1
3
v1.mp4
8
+
3
-
1
10
v2.mp4
12
+
10
-
1
21
v3.mp4
5
+
21
-
1
25
这些是简化的示例持续时间,与原始问题中显示的持续时间不同。
- 有关详细信息,请参阅 xfade and acrossfade 过滤器文档。
- 有关过渡效果和更多示例的图库,请参阅 FFmpeg Wiki: xfade。
- 你可以get input durations with
ffprobe
.
自动化该过程将有助于处理计算偏移量时的错误。我创建了一个 Python 脚本,用于计算并为任何大小的输入视频列表构建图表:
https://gist.github.com/royshil/369e175960718b5a03e40f279b131788
它将检查视频文件的长度(ffprobe
)以找出正确的偏移量。
问题的症结在于构建过滤图和计算偏移量:
# Prepare the filter graph
video_fades = ""
audio_fades = ""
last_fade_output = "0:v"
last_audio_output = "0:a"
video_length = 0
for i in range(len(segments) - 1):
# Video graph: chain the xfade operator together
video_length += file_lengths[i]
next_fade_output = "v%d%d" % (i, i + 1)
video_fades += "[%s][%d:v]xfade=duration=0.5:offset=%.3f[%s]; " % \
(last_fade_output, i + 1, video_length - 1, next_fade_output)
last_fade_output = next_fade_output
# Audio graph:
next_audio_output = "a%d%d" % (i, i + 1)
audio_fades += "[%s][%d:a]acrossfade=d=1[%s]%s " % \
(last_audio_output, i + 1, next_audio_output, ";" if (i+1) < len(segments)-1 else "")
last_audio_output = next_audio_output
它可能会产生一个过滤图,例如
[0:v][1:v]xfade=duration=0.5:offset=42.511[v01];
[v01][2:v]xfade=duration=0.5:offset=908.517[v12];
[v12][3:v]xfade=duration=0.5:offset=1098.523[v23];
[v23][4:v]xfade=duration=0.5:offset=1234.523[v34];
[v34][5:v]xfade=duration=0.5:offset=2375.523[v45];
[v45][6:v]xfade=duration=0.5:offset=2472.526[v56];
[v56][7:v]xfade=duration=0.5:offset=2659.693[v67];
[0:a][1:a]acrossfade=d=1[a01];
[a01][2:a]acrossfade=d=1[a12];
[a12][3:a]acrossfade=d=1[a23];
[a23][4:a]acrossfade=d=1[a34];
[a34][5:a]acrossfade=d=1[a45];
[a45][6:a]acrossfade=d=1[a56];
[a56][7:a]acrossfade=d=1[a67]
上面的Python脚本对我帮助很大,但是在计算偏移量时出错了。视频流应为 'video_length - fade_duration*(i+1)'.
如下代码:
def gen_filter(segments):
video_fades = ""
audio_fades = ""
settb = ""
last_fade_output = "0:v"
last_audio_output = "0:a"
fade_duration = 0.3
video_length = 0
file_lengths = [0]*len(segments)
for i in range(len(segments)):
settb += "[%d]settb=AVTB[%d:v];" % (i,i)
for i in range(len(segments)-1):
file_lengths[i] = float(ffmpeg.probe(segments[i])['format']['duration'])
video_length += file_lengths[i]
next_fade_output = "v%d%d" % (i, i + 1)
video_fades += "[%s][%d:v]xfade=transition=fade:duration=%f:offset=%f%s%s" % \
(last_fade_output, i + 1, fade_duration, video_length - fade_duration*(i+1), '['+next_fade_output+'];' if (i) < len(segments)-2 else "","" if (i) < len(segments)-2 else ",format=yuv420p[video];")
last_fade_output = next_fade_output
next_audio_output = "a%d%d" % (i, i + 1)
audio_fades += "[%s][%d:a]acrossfade=d=%f%s" % \
(last_audio_output, i + 1, fade_duration*2, '['+next_audio_output+'];' if (i) < len(segments)-2 else "[audio]")
last_audio_output = next_audio_output
return settb + video_fades + audio_fades
has a mistake, which was pointed out by 中的脚本,但显示为新定义。
如果有人只想要上一个答案的更正原始脚本,请将 # Prepare the filter graph
之后的块替换为:
# Prepare the filter graph
video_fades = ""
audio_fades = ""
last_fade_output = "0:v"
last_audio_output = "0:a"
video_length = 0
fade_duration = 0.5
for i in range(len(segments) - 1):
# Video graph: chain the xfade operator together
video_length += file_lengths[i]
next_fade_output = "v%d%d" % (i, i + 1)
video_fades += "[%s][%d:v]xfade=duration=0.5:offset=%.3f[%s]; " % \
(last_fade_output, i + 1, video_length - fade_duration*(i+1), next_fade_output)
last_fade_output = next_fade_output
# Audio graph:
next_audio_output = "a%d%d" % (i, i + 1)
audio_fades += "[%s][%d:a]acrossfade=d=1[%s]%s " % \
(last_audio_output, i + 1, next_audio_output, ";" if (i+1) < len(segments)-1 else "")
last_audio_output = next_audio_output
# Assemble the FFMPEG command arguments
ffmpeg_args = ['ffmpeg',
*itertools.chain(*files_input),
'-filter_complex', video_fades + audio_fades,
'-map', '[%s]' % last_fade_output,
'-map', '[%s]' % last_audio_output,
'-y',
args.output_filename]
# Run FFMPEG
subprocess.run(ffmpeg_args)
我写了一个similar but simpler script:
#!/bin/bash
# usage: ls -1 something*.mp4 | ffmpeg_xfade.sh output.mp4
fdur=0.5
ftrans=pixelize
f0n=0
f1n=1
alld=0
while read f; do
allvf="$allvf$vf"
allaf="$allaf$af"
inputs="$inputs -i $f "
d=$(ffprobe -v error -select_streams v:0 -show_entries stream=duration -of default=noprint_wrappers=1:nokey=1 "$f")
alld=$(bc -l <<< "$alld + $d")
offset=$(bc -l <<< "$alld - $fdur * $f1n")
vf="[vfade$f0n][$f1n:v]xfade=transition=$ftrans:duration=$fdur:offset=$offset[vfade$f1n];"
af="[afade$f0n][$f1n:a]acrossfade=d=$fdur[afade$f1n];"
(( f0n++ ))
(( f1n++ ))
done
f0n=$(( f0n - 1 ))
allvf="[0:v]copy[vfade0];$allvf[vfade$f0n]format=yuv420p"
allaf="[0:a]acopy[afade0];$allaf[afade$f0n]acopy"
#set -vx
ffmpeg -y -hide_banner $inputs \
-filter_complex "$allvf;$allaf" \
-c:v h264_nvenc -preset p7 -profile:v high -rc-lookahead 8 -spatial_aq 1 -pix_fmt yuv420p \
-c:a libopus \
""
由此产生的 ffmpeg 命令类似于:
ffmpeg -y -hide_banner -i f1.mp4 -i f2.mp4 -i f3.mp4 -i f4.mp4
-filter_complex "[0:v]copy[vfade0];[vfade0][1:v]xfade=transition=pixelize:duration=0.5:offset=-.5[vfade1];
[vfade1][2:v]xfade=transition=pixelize:duration=0.5:offset=10.0[vfade2];
[vfade2][3:v]xfade=transition=pixelize:duration=0.5:offset=20.0[vfade3];
[vfade3]format=yuv420p;
[0:a]acopy[afade0];[afade0][1:a]acrossfade=d=0.5[afade1];
[afade1][2:a]acrossfade=d=0.5[afade2];
[afade2][3:a]acrossfade=d=0.5[afade3];
[afade3]acopy" -c:v h264_nvenc -preset p7 -profile:v high -rc-lookahead 8 -spatial_aq 1 -pix_fmt yuv420p -c:a libopus "output.mp4"
我需要将多个视频文件(包含音频)合并为一个视频。我注意到 xfade 最近发布并使用了它,但我 运行 遇到了音频同步问题。
所有视频的视频和音频格式/分辨率/声望和比特率/等都相同。
这是我用来合并 5 个不同持续时间的视频和 0.5 交叉淡入淡出过渡的方法:
ffmpeg \
-i v0.mp4 \
-i v1.mp4 \
-i v2.mp4 \
-i v3.mp4 \
-i v4.mp4 \
-filter_complex \
"[0][1]xfade=transition=fade:duration=0.5:offset=3.5[V01]; \
[V01][2]xfade=transition=fade:duration=0.5:offset=32.75[V02]; \
[V02][3]xfade=transition=fade:duration=0.5:offset=67.75[V03]; \
[V03][4]xfade=transition=fade:duration=0.5:offset=98.75[video]; \
[0:a][1:a]acrossfade=d=0.5:c1=tri:c2=tri[A01]; \
[A01][2:a]acrossfade=d=0.5:c1=tri:c2=tri[A02]; \
[A02][3:a]acrossfade=d=0.5:c1=tri:c2=tri[A03]; \
[A03][4:a]acrossfade=d=0.5:c1=tri:c2=tri[audio]" \
-vsync 0 -map "[video]" -map "[audio]" out.mp4
上面的代码生成了一个带音频的视频。第一和第二段与音频对齐,但从第二个过渡开始,声音未对齐。
您的偏移量不正确。尝试:
ffmpeg -i v0.mp4 -i v1.mp4 -i v2.mp4 -i v3.mp4 -i v4.mp4 -filter_complex \
"[0][1:v]xfade=transition=fade:duration=1:offset=3[vfade1]; \
[vfade1][2:v]xfade=transition=fade:duration=1:offset=10[vfade2]; \
[vfade2][3:v]xfade=transition=fade:duration=1:offset=21[vfade3]; \
[vfade3][4:v]xfade=transition=fade:duration=1:offset=25,format=yuv420p; \
[0:a][1:a]acrossfade=d=1[afade1]; \
[afade1][2:a]acrossfade=d=1[afade2]; \
[afade2][3:a]acrossfade=d=1[afade3]; \
[afade3][4:a]acrossfade=d=1" \
-movflags +faststart out.mp4
如何获取 xfade offset
值:
input | input duration | + | previous xfade offset |
- | xfade duration |
offset = |
---|---|---|---|---|---|---|
v0.mp4 |
4 | + | 0 | - | 1 | 3 |
v1.mp4 |
8 | + | 3 | - | 1 | 10 |
v2.mp4 |
12 | + | 10 | - | 1 | 21 |
v3.mp4 |
5 | + | 21 | - | 1 | 25 |
这些是简化的示例持续时间,与原始问题中显示的持续时间不同。
- 有关详细信息,请参阅 xfade and acrossfade 过滤器文档。
- 有关过渡效果和更多示例的图库,请参阅 FFmpeg Wiki: xfade。
- 你可以get input durations with
ffprobe
.
自动化该过程将有助于处理计算偏移量时的错误。我创建了一个 Python 脚本,用于计算并为任何大小的输入视频列表构建图表:
https://gist.github.com/royshil/369e175960718b5a03e40f279b131788
它将检查视频文件的长度(ffprobe
)以找出正确的偏移量。
问题的症结在于构建过滤图和计算偏移量:
# Prepare the filter graph
video_fades = ""
audio_fades = ""
last_fade_output = "0:v"
last_audio_output = "0:a"
video_length = 0
for i in range(len(segments) - 1):
# Video graph: chain the xfade operator together
video_length += file_lengths[i]
next_fade_output = "v%d%d" % (i, i + 1)
video_fades += "[%s][%d:v]xfade=duration=0.5:offset=%.3f[%s]; " % \
(last_fade_output, i + 1, video_length - 1, next_fade_output)
last_fade_output = next_fade_output
# Audio graph:
next_audio_output = "a%d%d" % (i, i + 1)
audio_fades += "[%s][%d:a]acrossfade=d=1[%s]%s " % \
(last_audio_output, i + 1, next_audio_output, ";" if (i+1) < len(segments)-1 else "")
last_audio_output = next_audio_output
它可能会产生一个过滤图,例如
[0:v][1:v]xfade=duration=0.5:offset=42.511[v01];
[v01][2:v]xfade=duration=0.5:offset=908.517[v12];
[v12][3:v]xfade=duration=0.5:offset=1098.523[v23];
[v23][4:v]xfade=duration=0.5:offset=1234.523[v34];
[v34][5:v]xfade=duration=0.5:offset=2375.523[v45];
[v45][6:v]xfade=duration=0.5:offset=2472.526[v56];
[v56][7:v]xfade=duration=0.5:offset=2659.693[v67];
[0:a][1:a]acrossfade=d=1[a01];
[a01][2:a]acrossfade=d=1[a12];
[a12][3:a]acrossfade=d=1[a23];
[a23][4:a]acrossfade=d=1[a34];
[a34][5:a]acrossfade=d=1[a45];
[a45][6:a]acrossfade=d=1[a56];
[a56][7:a]acrossfade=d=1[a67]
上面的Python脚本对我帮助很大,但是在计算偏移量时出错了。视频流应为 'video_length - fade_duration*(i+1)'.
如下代码:
def gen_filter(segments):
video_fades = ""
audio_fades = ""
settb = ""
last_fade_output = "0:v"
last_audio_output = "0:a"
fade_duration = 0.3
video_length = 0
file_lengths = [0]*len(segments)
for i in range(len(segments)):
settb += "[%d]settb=AVTB[%d:v];" % (i,i)
for i in range(len(segments)-1):
file_lengths[i] = float(ffmpeg.probe(segments[i])['format']['duration'])
video_length += file_lengths[i]
next_fade_output = "v%d%d" % (i, i + 1)
video_fades += "[%s][%d:v]xfade=transition=fade:duration=%f:offset=%f%s%s" % \
(last_fade_output, i + 1, fade_duration, video_length - fade_duration*(i+1), '['+next_fade_output+'];' if (i) < len(segments)-2 else "","" if (i) < len(segments)-2 else ",format=yuv420p[video];")
last_fade_output = next_fade_output
next_audio_output = "a%d%d" % (i, i + 1)
audio_fades += "[%s][%d:a]acrossfade=d=%f%s" % \
(last_audio_output, i + 1, fade_duration*2, '['+next_audio_output+'];' if (i) < len(segments)-2 else "[audio]")
last_audio_output = next_audio_output
return settb + video_fades + audio_fades
如果有人只想要上一个答案的更正原始脚本,请将 # Prepare the filter graph
之后的块替换为:
# Prepare the filter graph
video_fades = ""
audio_fades = ""
last_fade_output = "0:v"
last_audio_output = "0:a"
video_length = 0
fade_duration = 0.5
for i in range(len(segments) - 1):
# Video graph: chain the xfade operator together
video_length += file_lengths[i]
next_fade_output = "v%d%d" % (i, i + 1)
video_fades += "[%s][%d:v]xfade=duration=0.5:offset=%.3f[%s]; " % \
(last_fade_output, i + 1, video_length - fade_duration*(i+1), next_fade_output)
last_fade_output = next_fade_output
# Audio graph:
next_audio_output = "a%d%d" % (i, i + 1)
audio_fades += "[%s][%d:a]acrossfade=d=1[%s]%s " % \
(last_audio_output, i + 1, next_audio_output, ";" if (i+1) < len(segments)-1 else "")
last_audio_output = next_audio_output
# Assemble the FFMPEG command arguments
ffmpeg_args = ['ffmpeg',
*itertools.chain(*files_input),
'-filter_complex', video_fades + audio_fades,
'-map', '[%s]' % last_fade_output,
'-map', '[%s]' % last_audio_output,
'-y',
args.output_filename]
# Run FFMPEG
subprocess.run(ffmpeg_args)
我写了一个similar but simpler script:
#!/bin/bash
# usage: ls -1 something*.mp4 | ffmpeg_xfade.sh output.mp4
fdur=0.5
ftrans=pixelize
f0n=0
f1n=1
alld=0
while read f; do
allvf="$allvf$vf"
allaf="$allaf$af"
inputs="$inputs -i $f "
d=$(ffprobe -v error -select_streams v:0 -show_entries stream=duration -of default=noprint_wrappers=1:nokey=1 "$f")
alld=$(bc -l <<< "$alld + $d")
offset=$(bc -l <<< "$alld - $fdur * $f1n")
vf="[vfade$f0n][$f1n:v]xfade=transition=$ftrans:duration=$fdur:offset=$offset[vfade$f1n];"
af="[afade$f0n][$f1n:a]acrossfade=d=$fdur[afade$f1n];"
(( f0n++ ))
(( f1n++ ))
done
f0n=$(( f0n - 1 ))
allvf="[0:v]copy[vfade0];$allvf[vfade$f0n]format=yuv420p"
allaf="[0:a]acopy[afade0];$allaf[afade$f0n]acopy"
#set -vx
ffmpeg -y -hide_banner $inputs \
-filter_complex "$allvf;$allaf" \
-c:v h264_nvenc -preset p7 -profile:v high -rc-lookahead 8 -spatial_aq 1 -pix_fmt yuv420p \
-c:a libopus \
""
由此产生的 ffmpeg 命令类似于:
ffmpeg -y -hide_banner -i f1.mp4 -i f2.mp4 -i f3.mp4 -i f4.mp4
-filter_complex "[0:v]copy[vfade0];[vfade0][1:v]xfade=transition=pixelize:duration=0.5:offset=-.5[vfade1];
[vfade1][2:v]xfade=transition=pixelize:duration=0.5:offset=10.0[vfade2];
[vfade2][3:v]xfade=transition=pixelize:duration=0.5:offset=20.0[vfade3];
[vfade3]format=yuv420p;
[0:a]acopy[afade0];[afade0][1:a]acrossfade=d=0.5[afade1];
[afade1][2:a]acrossfade=d=0.5[afade2];
[afade2][3:a]acrossfade=d=0.5[afade3];
[afade3]acopy" -c:v h264_nvenc -preset p7 -profile:v high -rc-lookahead 8 -spatial_aq 1 -pix_fmt yuv420p -c:a libopus "output.mp4"