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

This commit is contained in:
guru committed 2026-10-14 10:41:21 -04:00
1 parent 70b4b83be0
commit ae7880af95
1 file changed
+71 -42
+71 -42
View File
@@ -8,6 +8,12 @@
Each source video is scaled to fit its cell without stretching, with black letterbox/pillarbox Each source video is scaled to fit its cell without stretching, with black letterbox/pillarbox
padding applied as needed to preserve aspect ratios. 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: The script detects the orientation of S1 (landscape or portrait) and applies appropriate scaling:
Landscape mode (width > height) - 1K HD: Landscape mode (width > height) - 1K HD:
@@ -78,6 +84,9 @@
Generate comparison video in 4K UHD (3840x2160) resolution instead of 1K HD (1920x1080). Generate comparison video in 4K UHD (3840x2160) resolution instead of 1K HD (1920x1080).
.PARAMETER Clip .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 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 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 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. 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 .EXAMPLE
.\Compare-Videos.ps1 -S1 a.mp4 -S2 b.mp4 -S3 c.mp4 -Output sideways.mp4 -Sideways .\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 $S1 -Name "S1"
Test-VideoExists -Path $S2 -Name "S2" Test-VideoExists -Path $S2 -Name "S2"
@@ -226,16 +255,9 @@ elseif ($S3) { $videoCount = 3 }
# --- Detect orientation of S1 --- # --- Detect orientation of S1 ---
Write-Host "Detecting orientation of S1..." -ForegroundColor Cyan 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 $dim1 = Get-VideoDimensions -Path $S1 -Name "S1"
$width = $dim1.Width
if ($LASTEXITCODE -ne 0) { $height = $dim1.Height
Write-Error "Failed to probe video dimensions for S1: $probeOutput"
exit 1
}
$dimensions = $probeOutput -split ','
$width = [int]$dimensions[0]
$height = [int]$dimensions[1]
$isPortrait = $height -gt $width $isPortrait = $height -gt $width
@@ -256,6 +278,34 @@ if ($isPortrait) {
Write-Host "Landscape mode detected (${width}x${height}) - Output: $resolutionLabel" -ForegroundColor Green 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) { if ($Clip -and -not $Sideways) {
$clipAllowed = ($isPortrait -and $videoCount -le 4) -or (-not $isPortrait -and $videoCount -eq 2) $clipAllowed = ($isPortrait -and $videoCount -le 4) -or (-not $isPortrait -and $videoCount -eq 2)
if (-not $clipAllowed) { if (-not $clipAllowed) {
@@ -268,16 +318,6 @@ if ($Clip -and -not $Sideways) {
$clipMixedLandscape = $false $clipMixedLandscape = $false
$clipMixedFilterComplex = $null $clipMixedFilterComplex = $null
if ($Clip -and -not $Sideways) { 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 $s1IsLandscape = $width -gt $height
$s2IsLandscape = $w2 -gt $h2 $s2IsLandscape = $w2 -gt $h2
$isMixedOrientation = ($s1IsLandscape -xor $s2IsLandscape) $isMixedOrientation = ($s1IsLandscape -xor $s2IsLandscape)
@@ -364,7 +404,11 @@ if ($Clip -and -not $Sideways) {
$filterComplex = "" $filterComplex = ""
$inputArgs = @("-i", $S1, "-i", $S2) $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 Write-Host "Sideways mode: arranging $videoCount videos in a single row." -ForegroundColor Cyan
if ($isPortrait) { if ($isPortrait) {
@@ -457,13 +501,8 @@ if ($Sideways) {
$inputArgs += @("-i", $S3) $inputArgs += @("-i", $S3)
$applyPortraitClip = $false $applyPortraitClip = $false
if ($Clip) { if ($Clip) {
$probeOutput3 = & ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 $S3 2>&1 $dim3 = Get-VideoDimensions -Path $S3 -Name "S3"
if ($LASTEXITCODE -ne 0) { $w3 = $dim3.Width; $h3 = $dim3.Height
Write-Error "Failed to probe video dimensions for S3: $probeOutput3"
exit 1
}
$dim3 = $probeOutput3 -split ','
$w3 = [int]$dim3[0]; $h3 = [int]$dim3[1]
if (($h2 -gt $w2) -and ($h3 -gt $w3)) { if (($h2 -gt $w2) -and ($h3 -gt $w3)) {
$applyPortraitClip = $true $applyPortraitClip = $true
} else { } else {
@@ -490,20 +529,10 @@ if ($Sideways) {
$inputArgs += @("-i", $S3, "-i", $S4) $inputArgs += @("-i", $S3, "-i", $S4)
$applyPortraitClip4 = $false $applyPortraitClip4 = $false
if ($Clip) { if ($Clip) {
$probeOutput3 = & ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 $S3 2>&1 $dim3 = Get-VideoDimensions -Path $S3 -Name "S3"
if ($LASTEXITCODE -ne 0) { $w3 = $dim3.Width; $h3 = $dim3.Height
Write-Error "Failed to probe video dimensions for S3: $probeOutput3" $dim4 = Get-VideoDimensions -Path $S4 -Name "S4"
exit 1 $w4 = $dim4.Width; $h4 = $dim4.Height
}
$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]
if (($h2 -gt $w2) -and ($h3 -gt $w3) -and ($h4 -gt $w4)) { if (($h2 -gt $w2) -and ($h3 -gt $w3) -and ($h4 -gt $w4)) {
$applyPortraitClip4 = $true $applyPortraitClip4 = $true
} else { } else {