Concat multiple videos with different resolution

<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.

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

Create a test file without audio stream

ffmpeg -i nhl.mp4 -c copy -an nhl_nosound.mp4