使用ffmpeg和xfade滤镜合并多个视频文件

6

我需要将多个视频文件(包括音频)合并成一个视频。我注意到最近发布了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

上面的代码生成了一个带音频的视频。第一个和第二个片段与音频对齐,但是从第二个转换开始,声音就不对齐了。

@llogan 这太庞大了。https://pastebin.com/SGnqB7Lt - tjk
5个回答

26

你的偏移量不正确。尝试使用:

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的偏移值:

输入 输入时长 + 上一个xfade偏移量 - xfade持续时间 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

这些是简化的示例持续时间,与原始问题中显示的持续时间不同。


你说得对,我今天也通过一些测试找到了解决方法。我还发现在执行这些操作之前通过修剪/填充音频来获得更好的同步方式。这是一个问题,因为大多数文件的音频和视频持续时间不相等。所以现在我正在做的是(这是另一组视频+您的其他建议):https://pastebin.com/QGcVZsk3 - tjk
使用我的方法,我得到了“超过1000个帧重复”,而在输出的中间视频和音频之间不同步。有什么想法吗? - Richard
@Trương Quốc Khánh,你能否在ffmpeg参数中添加xfade? - Spartan 117
@Richard,你解决了不同步的问题吗?我现在也遇到了同样的问题。 - user1734282
@user1734282 你能试试这个吗? ffmpeg -i video0.mp4 -i video1.mp4 -i video2.mp4 -filter_complex "[0:v][1:v]xfade=transition=fade:duration=0.500:offset=41.567[v01]; [v01][2:v]xfade=transition=fade:duration=0.500:offset=55.534,format=yuv420p[video]; [0:a][1:a]acrossfade=d=0.500:c1=tri:c2=tri[a01]; [a01][2:a]acrossfade=d=0.500:c1=tri:c2=tri[audio]" -map [video] -map [audio] -movflags +faststart output.mp4 - Richard

5
自动化处理过程将有助于解决计算偏移量时的错误。我创建了一个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]

请提供 segments.txt 文件的详细信息。 - Mayank Kumar Chaudhari
我修改了代码并在片段中硬编码了3个文件名。它对音频非常有效,但是最后一个视频没有被添加。相反,直到最后一个视频的音乐结束,才会显示中间视频的最后一帧。 - Mayank Kumar Chaudhari

3
上面的 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

0

我写了一个类似但更简单的脚本

#!/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 \
    "$1"

由此产生的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"

1
你的回答可以通过更详细地解释代码的作用以及如何帮助提问者来改进。 - Tyler2P

-1

这个回答中的脚本有一个错误,被这个回答指出,但作为一个新的def呈现。

如果有人只想要以前回答的更正原始脚本,请将# 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)






能否提供该操作的最终 ffmpeg 命令? - user1432181

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接