<aside> 💡 METHOD 1: Concat filter
</aside>
# Using scale filter
======================
ffmpeg -i nosound-draw.mp4 -i nhl.mp4 -i del-nosound-raw.mp4 -i shl-draw.mp4 -filter_complex \\
"[0:v]scale=1920:1080[0v];
[1:v]scale=1920:1080[1v];
[2:v]scale=1920:1080[2v];
[3:v]scale=1920:1080[3v];
[0v][0:a][1v][1:a][2v][2:a][3v][3:a]concat=n=4:v=1:a=1:unsafe=1[v][a]" \\
-map "[v]" -map "[a]" -c:v libx264 \\
-movflags +faststart out.mp4 -y
# time: 284.45s user 1.57s system 524% cpu 54.541 total
### Using scale2ref filter
======================
ffmpeg -i nosound-draw.mp4 -i nhl.mp4 -i del-nosound-raw.mp4 -i shl-draw.mp4 -filter_complex \\
"[1][0]scale2ref[1scaled][main]; \\
[2][main]scale2ref[2scaled][main]; \\
[3][main]scale2ref[3scaled][main]; \\
[main][0:a][1scaled][1:a][2scaled][2:a][3scaled][3:a]concat=n=4:v=1:a=1:unsafe=1[v][a]" \\
-map "[v]" -map "[a]" -c:v libx264 \\
-movflags +faststart out.mp4 -y
# output fps = 50
# benchmark: 283.31s user 1.36s system 533% cpu 53.343 total
# with more options (
ffmpeg -i nosound-draw.mp4 -i nhl.mp4 -i del-nosound-raw.mp4 -i shl-draw.mp4 -filter_complex \\
"[1][0]scale2ref[1scaled][main]; \\
[2][main]scale2ref[2scaled][main]; \\
[3][main]scale2ref[3scaled][main]; \\
[main][0:a][1scaled][1:a][2scaled][2:a][3scaled][3:a]concat=n=4:v=1:a=1:unsafe=1[v][a]" \\
-map "[v]" -map "[a]" -c:v libx264 \\
-movflags +faststart -r 25 -g 50 -crf 18 -me_method umh \\
-pix_fmt yuv420p -trellis 0 -bf 8 \\
-acodec aac -ar 44100 -ab 128k \\
-f mp4 out.mp4 -y
# output fps = 25
# benchmark: 271.94s user 2.15s system 538% cpu 50.940 total
# better perhaps because fps is lower
<aside> 💡 METHOD 2: xfade filter
</aside>
This stackoverfow thread provides an excellent explanation of how the xfade filter works and how to calculate the offset value, which is often overlooked by the documentation.
https://stackoverflow.com/questions/63553906/merging-multiple-video-files-with-ffmpeg-and-xfade-filter
How to get xfade offset
values:
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 |
These are simplified example durations that are different than the durations shown in the original question.
ffprobe
.using scale2ref
to scale video to a reference video
# nosound-draw.mp4 00:23:00 50fps / 1920x1080
# nhl.mp4 00:00:20.02 60fps / 1280x720
# del-nosound-raw.mp4 00:00:05.02 25fps / 1280x720
# shl-draw.mp4 00:00:23.02 50fps / 1280x720
# offset1 = 23 + 0 - 1 = 22
# offset2 = 20.02 + 22 - 1 = 41.02
# offset3 = 5.02 + 41.02 - 1 = 45.04
# With fade transition
ffmpeg -i nosound-draw.mp4 -i nhl.mp4 -i del-nosound-raw.mp4 -i shl-draw.mp4 -filter_complex \\
"[1][0]scale2ref[1scaled][main]; \\
[2][main]scale2ref[2scaled][main]; \\
[3][main]scale2ref[3scaled][main]; \\
[main][1scaled]xfade=transition=fade:duration=1:offset=22[vfade1]; \\
[vfade1][2scaled]xfade=transition=fade:duration=1:offset=41.02[vfade2]; \\
[vfade2][3scaled]xfade=transition=fade:duration=1:offset=45.04,format=yuv420p; \\
[0:a][1:a]acrossfade=d=1[afade1]; \\
[afade1][2:a]acrossfade=d=1[afade2]; \\
[afade2][3:a]acrossfade=d=1" \\
-movflags +faststart out_fade.mp4
# ERROR: [Parsed_xfade_3 @ 0x109a12c50] First input link main timebase (1/12800) do not match the corresponding second input link xfade timebase (1/90000)
# [Parsed_xfade_3 @ 0x109a12c50] Failed to configure output pad on Parsed_xfade_3
# Error reinitializing filters!
# Failed to inject frame into filter network: Invalid argument
# Error while processing the decoded data for stream #3:0
# --> Try adding a `settb` filter
ffmpeg -i nosound-draw.mp4 -i nhl.mp4 -i del-nosound-raw.mp4 -i shl-draw.mp4 -filter_complex \\
"[1][0]scale2ref[1scaled][main]; \\
[2][main]scale2ref[2scaled][main]; \\
[3][main]scale2ref[3scaled][main]; \\
[main]settb=AVTB,setpts=PTS-STARTPTS[0v];
[1scaled]settb=AVTB,setpts=PTS-STARTPTS[1v];
[2scaled]settb=AVTB,setpts=PTS-STARTPTS[2v];
[3scaled]settb=AVTB,setpts=PTS-STARTPTS[3v];
[0v][1v]xfade=transition=fade:duration=1:offset=22[vfade1]; \\
[vfade1][2v]xfade=transition=fade:duration=1:offset=41.02[vfade2]; \\
[vfade2][3v]xfade=transition=fade:duration=1:offset=45.04,format=yuv420p; \\
[0:a][1:a]acrossfade=d=1[afade1]; \\
[afade1][2:a]acrossfade=d=1[afade2]; \\
[afade2][3:a]acrossfade=d=1" \\
-movflags +faststart out_fade.mp4 -y
# ERROR: First input link main frame rate (50/1) do not match the corresponding
# second input link xfade frame rate (60/1)
# --> Adding FPS filter
# <https://donglumail.medium.com/3-methods-you-need-to-know-for-ffmpeg-transition-animation-7d2ea8f7ced7>
ffmpeg -i nosound-draw.mp4 -i nhl.mp4 -i del-nosound-raw.mp4 -i shl-draw.mp4 -filter_complex \\
"[1][0]scale2ref[1scaled][main]; \\
[2][main]scale2ref[2scaled][main]; \\
[3][main]scale2ref[3scaled][main]; \\
[main]settb=AVTB,setpts=PTS-STARTPTS,fps=30/1[0v];
[1scaled]settb=AVTB,setpts=PTS-STARTPTS,fps=30/1[1v];
[2scaled]settb=AVTB,setpts=PTS-STARTPTS,fps=30/1[2v];
[3scaled]settb=AVTB,setpts=PTS-STARTPTS,fps=30/1[3v];
[0v][1v]xfade=transition=rectcrop:duration=1:offset=22[vfade1]; \\
[vfade1][2v]xfade=transition=fade:duration=1:offset=41.02[vfade2]; \\
[vfade2][3v]xfade=transition=radial:duration=1:offset=45.04,format=yuv420p; \\
[0:a][1:a]acrossfade=d=1[afade1]; \\
[afade1][2:a]acrossfade=d=1[afade2]; \\
[afade2][3:a]acrossfade=d=1" \\
-movflags +faststart out_fade.mp4 -y
# --> YAY! Working
# rectcrop transition looks great for hockey video
ffmpeg -i nhl.mp4 -c copy -an nhl_nosound.mp4