From ae7880af95bb3fb68ffc343ddb0fe6136f843a1a Mon Sep 17 00:00:00 2001 From: Claude Toupin Date: Wed, 14 Oct 2026 10:41:21 -0400 Subject: [PATCH] Add exact-fit mode to Compare-Videos.ps1 for two sources that already tile the output frame perfectly (e.g. 1280x1080 + 640x1080 -> 1920x1080), stacking them side by side with no scaling or padding when both heights match output height and widths sum to output width, refactor dimension probing into Get-VideoDimensions function to eliminate duplicate ffprobe calls, and update documentation to explain exact-fit behavior and that -Clip and -Sideways have no effect in this mode --- Compare-Videos.ps1 | 113 ++++++++++++++++++++++++++++----------------- 1 file changed, 71 insertions(+), 42 deletions(-) diff --git a/Compare-Videos.ps1 b/Compare-Videos.ps1 index 6ca3c7b..f5c3d2a 100644 --- a/Compare-Videos.ps1 +++ b/Compare-Videos.ps1 @@ -8,6 +8,12 @@ Each source video is scaled to fit its cell without stretching, with black letterbox/pillarbox padding applied as needed to preserve aspect ratios. + Exact fit (2 videos only): when S1 and S2 are both exactly as tall as the output + (1080, or 2160 with -In4K) and their widths add up to the output width (1920, or 3840 + with -In4K), the two videos already tile the target frame perfectly. In that case they + are stacked side by side untouched - no scaling, no padding, no quality loss. + Example: 1280x1080 + 640x1080 -> 1920x1080. -Clip and -Sideways have no effect here. + The script detects the orientation of S1 (landscape or portrait) and applies appropriate scaling: Landscape mode (width > height) - 1K HD: @@ -78,6 +84,9 @@ Generate comparison video in 4K UHD (3840x2160) resolution instead of 1K HD (1920x1080). .PARAMETER Clip + Ignored when the two sources are an exact fit for the output frame (see DESCRIPTION), + since such an output has no padding to remove. + Reduces the output height to match the actual scaled height of the sources, removing the top/bottom black letterbox padding. For landscape, only applies when exactly 2 source videos are provided; ignored otherwise. For portrait, applies when 2-4 source @@ -135,6 +144,12 @@ Compare two 720p (1280x720) videos with no top/bottom letterboxing: output is 1920x540. +.EXAMPLE + .\Compare-Videos.ps1 -S1 wide_1280x1080.mp4 -S2 narrow_640x1080.mp4 -Output exact.mp4 + + Exact fit: both sources are 1080 tall and 1280 + 640 = 1920, so they are stacked + side by side with no scaling or padding at all. + .EXAMPLE .\Compare-Videos.ps1 -S1 a.mp4 -S2 b.mp4 -S3 c.mp4 -Output sideways.mp4 -Sideways @@ -202,6 +217,20 @@ function Test-VideoExists { } } +function Get-VideoDimensions { + param([string]$Path, [string]$Name) + + $probeOutput = & ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 $Path 2>&1 + + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to probe video dimensions for ${Name}: $probeOutput" + exit 1 + } + + $dimensions = $probeOutput -split ',' + return [pscustomobject]@{ Width = [int]$dimensions[0]; Height = [int]$dimensions[1] } +} + Test-VideoExists -Path $S1 -Name "S1" Test-VideoExists -Path $S2 -Name "S2" @@ -226,16 +255,9 @@ elseif ($S3) { $videoCount = 3 } # --- Detect orientation of S1 --- Write-Host "Detecting orientation of S1..." -ForegroundColor Cyan -$probeOutput = & ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 $S1 2>&1 - -if ($LASTEXITCODE -ne 0) { - Write-Error "Failed to probe video dimensions for S1: $probeOutput" - exit 1 -} - -$dimensions = $probeOutput -split ',' -$width = [int]$dimensions[0] -$height = [int]$dimensions[1] +$dim1 = Get-VideoDimensions -Path $S1 -Name "S1" +$width = $dim1.Width +$height = $dim1.Height $isPortrait = $height -gt $width @@ -256,6 +278,34 @@ if ($isPortrait) { Write-Host "Landscape mode detected (${width}x${height}) - Output: $resolutionLabel" -ForegroundColor Green } +# S2 dimensions are needed for the exact-fit check below and for -Clip. +$w2 = 0 +$h2 = 0 +if ($videoCount -eq 2 -or $Clip) { + $dim2 = Get-VideoDimensions -Path $S2 -Name "S2" + $w2 = $dim2.Width + $h2 = $dim2.Height +} + +# --- Exact fit: two sources that already tile the output perfectly --- +# When both sources are exactly as tall as the output and their widths add up to the +# output width (e.g. 1280x1080 + 640x1080 at 1K HD), stacking them side by side already +# produces the target frame. Scaling or padding would only degrade them, so the sources +# are passed straight to hstack untouched. -Clip and -Sideways have nothing to do in this +# case (there is no padding to remove and the layout is already a single row). +$exactFit = ($videoCount -eq 2 -and $height -eq $outputHeight -and $h2 -eq $outputHeight -and ($width + $w2) -eq $outputWidth) + +if ($exactFit) { + Write-Host ("Exact fit: {0}x{1} + {2}x{3} already tile {4}x{5}; sources used unscaled." -f $width, $height, $w2, $h2, $outputWidth, $outputHeight) -ForegroundColor Green + if ($Clip) { + Write-Host "Exact fit: -Clip has no effect (output contains no padding)." -ForegroundColor Yellow + $Clip = $false + } + if ($Sideways) { + Write-Host "Exact fit: -Sideways has no effect (sources are already a single row)." -ForegroundColor Yellow + } +} + if ($Clip -and -not $Sideways) { $clipAllowed = ($isPortrait -and $videoCount -le 4) -or (-not $isPortrait -and $videoCount -eq 2) if (-not $clipAllowed) { @@ -268,16 +318,6 @@ if ($Clip -and -not $Sideways) { $clipMixedLandscape = $false $clipMixedFilterComplex = $null if ($Clip -and -not $Sideways) { - Write-Host "Probing S2 dimensions for -Clip..." -ForegroundColor Cyan - $probeOutput2 = & ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 $S2 2>&1 - if ($LASTEXITCODE -ne 0) { - Write-Error "Failed to probe video dimensions for S2: $probeOutput2" - exit 1 - } - $dim2 = $probeOutput2 -split ',' - $w2 = [int]$dim2[0] - $h2 = [int]$dim2[1] - $s1IsLandscape = $width -gt $height $s2IsLandscape = $w2 -gt $h2 $isMixedOrientation = ($s1IsLandscape -xor $s2IsLandscape) @@ -364,7 +404,11 @@ if ($Clip -and -not $Sideways) { $filterComplex = "" $inputArgs = @("-i", $S1, "-i", $S2) -if ($Sideways) { +if ($exactFit) { + # Both sources already have the exact output height and their widths sum to the output + # width: hstack them as-is, with no scale and no pad filters in the graph. + $filterComplex = "[0:v][1:v]hstack=inputs=2[outv]" +} elseif ($Sideways) { Write-Host "Sideways mode: arranging $videoCount videos in a single row." -ForegroundColor Cyan if ($isPortrait) { @@ -457,13 +501,8 @@ if ($Sideways) { $inputArgs += @("-i", $S3) $applyPortraitClip = $false if ($Clip) { - $probeOutput3 = & ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 $S3 2>&1 - if ($LASTEXITCODE -ne 0) { - Write-Error "Failed to probe video dimensions for S3: $probeOutput3" - exit 1 - } - $dim3 = $probeOutput3 -split ',' - $w3 = [int]$dim3[0]; $h3 = [int]$dim3[1] + $dim3 = Get-VideoDimensions -Path $S3 -Name "S3" + $w3 = $dim3.Width; $h3 = $dim3.Height if (($h2 -gt $w2) -and ($h3 -gt $w3)) { $applyPortraitClip = $true } else { @@ -490,20 +529,10 @@ if ($Sideways) { $inputArgs += @("-i", $S3, "-i", $S4) $applyPortraitClip4 = $false if ($Clip) { - $probeOutput3 = & ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 $S3 2>&1 - if ($LASTEXITCODE -ne 0) { - Write-Error "Failed to probe video dimensions for S3: $probeOutput3" - exit 1 - } - $dim3 = $probeOutput3 -split ',' - $w3 = [int]$dim3[0]; $h3 = [int]$dim3[1] - $probeOutput4 = & ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 $S4 2>&1 - if ($LASTEXITCODE -ne 0) { - Write-Error "Failed to probe video dimensions for S4: $probeOutput4" - exit 1 - } - $dim4 = $probeOutput4 -split ',' - $w4 = [int]$dim4[0]; $h4 = [int]$dim4[1] + $dim3 = Get-VideoDimensions -Path $S3 -Name "S3" + $w3 = $dim3.Width; $h3 = $dim3.Height + $dim4 = Get-VideoDimensions -Path $S4 -Name "S4" + $w4 = $dim4.Width; $h4 = $dim4.Height if (($h2 -gt $w2) -and ($h3 -gt $w3) -and ($h4 -gt $w4)) { $applyPortraitClip4 = $true } else {