<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.
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
# <u>ERROR</u>: [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
# <u>ERROR</u>: 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
# <u>--> YAY! Working</u>
# rectcrop transition looks great for hockey video
ffmpeg -i nhl.mp4 -c copy -an nhl_nosound.mp4