20 KiB
Compare-Videos.ps1
Combines 2 to 4 video files into a single comparison video using ffmpeg.
Left side: Original video, Right side: Outpainted video using LTX-2.3 and Wan2GP.
See Create-PillarBox.ps1 script for an example with 4 videos.
Synopsis
Creates side-by-side or grid comparison videos with automatic scaling and padding to preserve aspect ratios. By default, creates 1920x1080 (1K HD) output. Use -In4K for 3840x2160 (4K UHD) output. The script detects the orientation of the first video (S1) and applies appropriate scaling for either landscape or portrait mode. Each source video is scaled to fit its cell without stretching, with black letterbox/pillarbox padding applied as needed.
When exactly two sources already tile the output frame perfectly, they are passed through untouched — see Exact Fit.
Parameters
-S1(required): Path to source video 1-S2(required): Path to source video 2-S3(optional): Path to source video 3-S4(optional): Path to source video 4-Output(required): Path to the output video file-NoAudio: Exclude audio from output (default: copies audio from S1)-In4K: Generate comparison video in 4K UHD (3840x2160) resolution instead of 1K HD (1920x1080)-Clip: Removes black padding from the output. Ignored when the two sources are an exact fit for the output frame, since there is no padding to remove. For landscape, only applies when exactly 2 source videos are provided; ignored otherwise. For portrait, applies when 2–4 source videos are provided (all sources must be portrait); for 2–3 videos removes the outer side (left/right) padding, for 4 videos removes the outer top/bottom padding. With one landscape source and one portrait source (2-video only), switches to a mixed-orientation layout: the landscape video keeps its native pixel size (or 2× in-In4K) and the portrait video is centered in the remaining width with black pillar boxes. See Clip Mode for details.-Sideways: Arranges all videos in a single horizontal row. For landscape mode, replaces the default 2x2 grid layout (used for 3 and 4 videos) with a single row. For portrait mode, the layout is the same as the default (already side-by-side). Can be combined with-Clipto remove black padding. See Sideways Mode for details.
Exact Fit
With exactly 2 source videos, when both are already as tall as the output frame and their widths add up to the output width, placing them side by side is the target frame. In this case the script applies no scaling and no padding at all — the two streams go straight into hstack, so neither source is resampled and no quality is lost.
The condition is checked against the selected output resolution:
| Required height (both sources) | Required width (S1 + S2) | |
|---|---|---|
| Default (1K HD) | 1080 |
1920 |
With -In4K |
2160 |
3840 |
The widths do not have to be equal — any split that sums to the output width qualifies.
Examples
| S1 | S2 | Output | Exact fit? |
|---|---|---|---|
1280x1080 |
640x1080 |
1920x1080 |
Yes — 1280 + 640 = 1920, both 1080 tall |
960x1080 |
960x1080 |
1920x1080 |
Yes — equal halves |
1440x1080 |
480x1080 |
1920x1080 |
Yes — uneven split still sums to 1920 |
2560x2160 + -In4K |
1280x2160 |
3840x2160 |
Yes — 2560 + 1280 = 3840, both 2160 tall |
2560x2160 |
1280x2160 |
1920x1080 |
No — heights are not 1080 (add -In4K) |
1280x720 |
640x1080 |
1920x1080 |
No — S1 is not 1080 tall |
1280x1080 |
680x1080 |
1920x1080 |
No — widths sum to 1960, not 1920 |
Resulting filter graph (note the absence of scale and pad):
[0:v][1:v]hstack=inputs=2[outv]
The console reports it as:
Exact fit: 1280x1080 + 640x1080 already tile 1920x1080; sources used unscaled.
Interaction with other switches
-Clipis ignored: an exact-fit output contains no padding to remove. A notice is printed.-Sidewaysis ignored: two sources side by side are already a single row. A notice is printed.- Applies to 2 sources only. With 3 or 4 videos the normal grid/row layouts are used.
- Orientation detection still runs, but is irrelevant — the exact-fit check takes precedence over both the landscape and portrait layouts. (A pair such as two
960x1080sources is detected as "portrait", yet is now passed through instead of being squeezed into 608-wide cells.)
Orientation Detection
The script automatically detects the orientation of S1 by comparing its width and height:
- Landscape mode: Width > Height (e.g., 1920x1080)
- Portrait mode: Height > Width (e.g., 1080x1920)
All videos are then scaled according to the detected orientation.
Layout Rules
Each source video is scaled to fit its cell without stretching. Aspect ratio is always preserved using force_original_aspect_ratio=decrease combined with black padding to fill the cell exactly.
Landscape Mode (width > height)
1K HD (1920x1080) - Default
2 videos: Output is 1920x1080. Each video occupies a 960x1080 cell side-by-side.
[S1][S2]
3 videos: Output is 1920x1080. Top row has two 960x540 cells (S1 left, S2 right). Bottom row has S3 in a 960x540 cell, horizontally centered with 480px black padding on each side.
[S1][S2]
[ S3 ]
4 videos: Output is 1920x1080. Four 960x540 cells in a 2x2 grid.
[S1][S2]
[S3][S4]
4K UHD (3840x2160) - With -In4K
2 videos: Output is 3840x2160. Each video occupies a 1920x2160 cell side-by-side.
[S1][S2]
3 videos: Output is 3840x2160. Top row has two 1920x1080 cells (S1 left, S2 right). Bottom row has S3 in a 1920x1080 cell, horizontally centered with 960px black padding on each side.
[S1][S2]
[ S3 ]
4 videos: Output is 3840x2160. Four 1920x1080 cells in a 2x2 grid.
[S1][S2]
[S3][S4]
Portrait Mode (height > width)
1K HD (1920x1080) - Default
All videos are displayed horizontally side-by-side, scaled and centered with black padding.
2 videos: Each video is scaled to 608x1080. Combined width is 1216x1080, centered horizontally in 1920x1080 output with 352px padding on each side. With -Clip: 1216x1080 (no side padding).
[ ][S1][S2][ ]
3 videos: Each video is scaled to 608x1080. Combined width is 1824x1080, centered horizontally in 1920x1080 output with 48px padding on each side. With -Clip: 1824x1080 (all sources must be portrait; otherwise falls back to padded layout).
[ ][S1][S2][S3][ ]
4 videos: Each video is scaled to 480x854. Combined width is 1920x854, centered vertically in 1920x1080 output with 113px padding on top and bottom. With -Clip: 1920x854 (all sources must be portrait; otherwise falls back to padded layout).
[S1][S2][S3][S4]
4K UHD (3840x2160) - With -In4K
2 videos: Each video is scaled to 1216x2160. Combined width is 2432x2160, centered horizontally in 3840x2160 output with 704px padding on each side. With -Clip: 2432x2160 (no side padding).
[ ][S1][S2][ ]
3 videos: Each video is scaled to 1216x2160. Combined width is 3648x2160, centered horizontally in 3840x2160 output with 96px padding on each side. With -Clip: 3648x2160 (all sources must be portrait; otherwise falls back to padded layout).
[ ][S1][S2][S3][ ]
4 videos: Each video is scaled to 960x1708. Combined width is 3840x1708, centered vertically in 3840x2160 output with 226px padding on top and bottom. With -Clip: 3840x1708 (all sources must be portrait; otherwise falls back to padded layout).
[S1][S2][S3][S4]
Sideways Mode
With -Sideways, all videos are placed in a single horizontal row regardless of video count. For landscape sources this is the key difference from the default layout, which uses a 2x2 grid for 3 and 4 videos.
Can be combined with -Clip to remove black padding: for landscape, the output height is reduced to the natural content height of the sources; for portrait, the outer centering pad is omitted and the output matches the raw hstack dimensions.
Landscape Mode — Sideways
1K HD (1920x1080)
2 videos: Each cell is 960x1080. Video content scales to 960x540, padded vertically. Total: 1920x1080. With -Clip: total 1920x540.
[S1][S2]
3 videos: Each cell is 640x1080. Video content scales to 640x360, padded vertically. Total: 1920x1080. With -Clip: total 1920x360.
[S1][S2][S3]
4 videos: Each cell is 480x1080. Video content scales to 480x270, padded vertically. Total: 1920x1080. With -Clip: total 1920x270.
[S1][S2][S3][S4]
4K UHD (3840x2160) — With -In4K
2 videos: Each cell is 1920x2160. Video content scales to 1920x1080, padded vertically. Total: 3840x2160. With -Clip: total 3840x1080.
3 videos: Each cell is 1280x2160. Video content scales to 1280x720, padded vertically. Total: 3840x2160. With -Clip: total 3840x720.
4 videos: Each cell is 960x2160. Video content scales to 960x540, padded vertically. Total: 3840x2160. With -Clip: total 3840x540.
Portrait Mode — Sideways
Portrait videos are already displayed side-by-side by default. The -Sideways switch produces the same layout as the standard portrait mode. With -Clip, the outer black padding is omitted and the output matches the raw hstack dimensions.
1K HD (1920x1080)
2 videos: Each cell 608x1080. Output 1920x1080 (352px padding per side). With -Clip: 1216x1080.
3 videos: Each cell 608x1080. Output 1920x1080 (48px padding per side). With -Clip: 1824x1080.
4 videos: Each cell 480x854. Output 1920x1080 (~113px padding top/bottom). With -Clip: 1920x854.
4K UHD (3840x2160) — With -In4K
2 videos: Each cell 1216x2160. Output 3840x2160 (704px padding per side). With -Clip: 2432x2160.
3 videos: Each cell 1216x2160. Output 3840x2160 (96px padding per side). With -Clip: 3648x2160.
4 videos: Each cell 960x1706. Output 3840x2160 (~227px padding top/bottom). With -Clip: 3840x1706.
Clip Mode
With only 2 source videos, the default behavior pads the output to the full target resolution (1920x1080 or 3840x2160). When the sources are shorter than 16:9 (e.g. two 1280x720 videos placed side-by-side at 1K HD), this leaves significant black bars on the top and bottom. The -Clip switch removes those bars. It works in two modes depending on the source orientations.
Same-orientation mode (both landscape or both portrait)
The final output height is reduced to the actual height each source naturally scales to inside its half-width cell, removing the top/bottom letterbox entirely.
How it works
- Both
S1andS2are probed for their originalwidth x height. - For each source, the script computes the height it would scale to when fitted by width into its cell (
outputWidth / 2for landscape,outputWidth * 0.6333 / 2for portrait), preserving aspect ratio (force_original_aspect_ratio=decrease). - The larger of the two scaled heights is used as the new output height (rounded up to an even number for
libx264/yuv420p), capped at the original output height. - All subsequent scaling/padding uses that reduced output height, so neither source is cropped and there is no top/bottom letterbox.
Examples
| Sources | Default output | With -Clip |
With -Clip -In4K |
|---|---|---|---|
Two 1280x720 (16:9) |
1920x1080 |
1920x540 |
3840x1080 |
Two 1920x1080 (16:9) |
1920x1080 |
1920x1080 (already full height) |
3840x2160 (already full height) |
One 1280x720 + one 1280x960 |
1920x1080 |
1920x720 (max scaled height) |
3840x1440 |
Mixed-orientation mode (one landscape + one portrait)
When one source is landscape and the other is portrait, -Clip switches to a layout designed to display the landscape video at its native pixel size while pillar-boxing the portrait into the remaining horizontal space.
How it works
- Both
S1andS2are probed; the script detects that one is landscape (width > height) and the other is portrait. - A
scaleFactoris derived from the target resolution:1.0at 1K HD,2.0at 4K UHD (outputWidth / 1920). - The landscape source is rendered at
landscapeNativeWidth * scaleFactor×landscapeNativeHeight * scaleFactor. At 1K HD this is its native pixel size; at 4K UHD it's exactly 2× upscaled, matching the-In4Kdoubling used elsewhere. - The output height is set to the landscape's scaled height.
- The portrait source is scaled to fit the remaining width (
outputWidth - landscapeScaledWidth) × output height withforce_original_aspect_ratio=decrease, then padded with black to fill that cell exactly (pillar-box bars). S1is placed on the left andS2on the right (the script preserves the same S1/S2 ordering as the same-orientation layout, regardless of which one is landscape).- If the landscape source is already as wide as the target resolution (e.g. a 1080p source at
-In4Kwould scale to 3840 wide, leaving no room for the portrait), the script falls back to the same-orientation logic and emits a console warning.
Examples
| Sources (S1, S2) | -Clip output |
-Clip -In4K output |
|---|---|---|
1280x720 landscape + 720x1280 portrait |
1920x720 (landscape 1280x720 on its side, portrait cell 640x720) |
3840x1440 (landscape 2560x1440, portrait cell 1280x1440) |
720x1280 portrait + 1280x720 landscape |
1920x720 (portrait cell 640x720 on left as S1, landscape 1280x720 on right as S2) |
3840x1440 (same with -In4K doubling) |
854x480 landscape + 480x854 portrait |
1920x480 (landscape 854x480, portrait cell 1066x480) |
3840x960 |
1920x1080 landscape + 1080x1920 portrait |
falls back to same-orientation behaviour (landscape already at target width) | falls back to same-orientation behaviour |
Portrait clip mode
When all source videos are portrait, -Clip removes the outer black padding rather than adjusting height:
- 2 or 3 videos: The outer side (left/right) padding is omitted. Output width equals the raw hstack width (2× or 3× the cell width).
- 4 videos: The outer top/bottom padding is omitted. Output height equals the natural 9:16 cell height.
For 3 and 4 videos, S2 (and S3/S4 as needed) are probed to verify all sources are portrait. If any source is not portrait, the full padded layout is used and a warning is emitted.
| Sources | Default output | With -Clip |
With -Clip -In4K |
|---|---|---|---|
| 2 portrait videos | 1920x1080 | 1216x1080 | 2432x2160 |
| 3 portrait videos (all portrait) | 1920x1080 | 1824x1080 | 3648x2160 |
| 4 portrait videos (all portrait) | 1920x1080 | 1920x854 | 3840x1708 |
Restrictions
- For landscape, only applies when exactly 2 source videos are provided. With 3 or 4 landscape videos, the switch is ignored and a warning is emitted.
- For portrait, applies when 2–4 source videos are provided. For 3 and 4 videos, all sources must be portrait; if any is not, the padded layout is used automatically.
- Mixed-orientation mode (one landscape + one portrait) requires the landscape source's scaled width to be strictly less than the target width and its scaled height to fit within the target height; otherwise the script falls back to the same-orientation behaviour automatically.
- The audio handling, codec, and CRF are unchanged.
Examples
Exact fit — two videos that already tile 1920x1080 (no scaling, no padding):
.\Compare-Videos.ps1 -S1 wide_1280x1080.mp4 -S2 narrow_640x1080.mp4 -Output exact.mp4
Exact fit at 4K (2560x2160 + 1280x2160 → 3840x2160):
.\Compare-Videos.ps1 -S1 wide_2560x2160.mp4 -S2 narrow_1280x2160.mp4 -Output exact_4k.mp4 -In4K
Two portrait videos clipped to remove side padding (1216x1080):
.\Compare-Videos.ps1 -S1 portrait1.mp4 -S2 portrait2.mp4 -Output portrait_clip.mp4 -Clip
Same pair at 4K (2432x2160):
.\Compare-Videos.ps1 -S1 portrait1.mp4 -S2 portrait2.mp4 -Output portrait_clip_4k.mp4 -Clip -In4K
Three portrait videos clipped (1824x1080 — all sources must be portrait):
.\Compare-Videos.ps1 -S1 p1.mp4 -S2 p2.mp4 -S3 p3.mp4 -Output portrait3_clip.mp4 -Clip
Four portrait videos clipped to remove top/bottom padding (1920x854 — all sources must be portrait):
.\Compare-Videos.ps1 -S1 p1.mp4 -S2 p2.mp4 -S3 p3.mp4 -S4 p4.mp4 -Output portrait4_clip.mp4 -Clip
Compare two videos:
.\Compare-Videos.ps1 -S1 original.mp4 -S2 enhanced.mp4 -Output comparison.mp4
Compare three videos:
.\Compare-Videos.ps1 -S1 version1.mp4 -S2 version2.mp4 -S3 version3.mp4 -Output comparison.mp4
Compare four videos in grid:
.\Compare-Videos.ps1 -S1 a.mp4 -S2 b.mp4 -S3 c.mp4 -S4 d.mp4 -Output grid.mp4
Without audio:
.\Compare-Videos.ps1 -S1 video1.mp4 -S2 video2.mp4 -Output comparison.mp4 -NoAudio
Using absolute paths:
.\Compare-Videos.ps1 -S1 "C:\Videos\original.mp4" -S2 "D:\Processed\enhanced.mp4" -Output "E:\Output\comparison.mp4"
Generate 4K UHD comparison:
.\Compare-Videos.ps1 -S1 original.mp4 -S2 enhanced.mp4 -Output comparison_4k.mp4 -In4K
Two 720p videos clipped to a true 16:9 strip (1920x540):
.\Compare-Videos.ps1 -S1 a_720p.mp4 -S2 b_720p.mp4 -Output side-by-side.mp4 -Clip
Same pair at 4K (3840x1080):
.\Compare-Videos.ps1 -S1 a_720p.mp4 -S2 b_720p.mp4 -Output side-by-side_4k.mp4 -Clip -In4K
Mixed orientation: 720p landscape + 720x1280 portrait (1920x720 output):
.\Compare-Videos.ps1 -S1 landscape_720p.mp4 -S2 portrait_720x1280.mp4 -Output mixed.mp4 -Clip
# Landscape occupies 1280x720 on the left, portrait fits in the right 640x720 cell with pillar boxes.
Same mixed pair at 4K (3840x1440 output):
.\Compare-Videos.ps1 -S1 landscape_720p.mp4 -S2 portrait_720x1280.mp4 -Output mixed_4k.mp4 -Clip -In4K
# Landscape is upscaled to 2560x1440, portrait cell is 1280x1440.
4K comparison with four videos:
.\Compare-Videos.ps1 -S1 a.mp4 -S2 b.mp4 -S3 c.mp4 -S4 d.mp4 -Output grid_4k.mp4 -In4K
Three landscape videos in a single row (640x1080 cells):
.\Compare-Videos.ps1 -S1 a.mp4 -S2 b.mp4 -S3 c.mp4 -Output sideways.mp4 -Sideways
Four landscape videos in a single row at 4K (960x2160 cells):
.\Compare-Videos.ps1 -S1 a.mp4 -S2 b.mp4 -S3 c.mp4 -S4 d.mp4 -Output sideways_4k.mp4 -Sideways -In4K
Three landscape videos in a single row, clipped to content height (1920x360 output):
.\Compare-Videos.ps1 -S1 a.mp4 -S2 b.mp4 -S3 c.mp4 -Output sideways_clip.mp4 -Sideways -Clip
Encoding Settings
- Codec: libx264
- Preset: fast
- CRF: 8 (high quality)
- Pixel Format: yuv420p
- Audio: Copied from S1 (unless
-NoAudiospecified)
Existing Files
- An existing
-Outputvideo is replaced without a prompt. ffmpeg runs with-y, so the old file is lost. Use-WhatIffirst, or write to a new name. - The folder that contains
-Outputmust already exist. The script stops with an error instead of creating it. - The source videos are only read and are never modified.
Notes
- Requires
ffmpegandffprobein system PATH - All source video files and output directory must exist before execution
- The script probes S1 to determine orientation (landscape vs. portrait)
- All videos are scaled according to the detected orientation of S1
- The full ffmpeg command is printed to console before execution
- Supports
-WhatIffor dry-run testing without creating output - Videos with different aspect ratios are automatically letterboxed/pillarboxed with black padding
- Source videos are never stretched or distorted
- With 2 sources that exactly tile the output frame, no
scale/padfilter is applied at all; see Exact Fit - Portrait mode videos are always displayed side-by-side horizontally, not in a grid
- Use
-Sidewaysto force all videos into a single horizontal row; this replaces the default 2×2 grid for landscape 3 and 4 videos -Sidewaysand-Clipcan be combined: for landscape, the cell height is reduced to the natural content height; for portrait, the outer centering padding is omitted