From a687bb5348013af0ab16ae2ba2adce05e044d004 Mon Sep 17 00:00:00 2001 From: Claude Toupin Date: Wed, 3 Jun 2026 15:26:45 -0400 Subject: [PATCH] Add mixed-orientation layout for -Clip mode with landscape video at native size and portrait video scaled to match height --- Compare-Videos.ps1 | 108 ++++++++++++++++++++++++++++++++++++--------- Get-VideoInfo.ps1 | 5 ++- 2 files changed, 91 insertions(+), 22 deletions(-) diff --git a/Compare-Videos.ps1 b/Compare-Videos.ps1 index 102398c..1be56a4 100644 --- a/Compare-Videos.ps1 +++ b/Compare-Videos.ps1 @@ -58,8 +58,19 @@ the top/bottom black letterbox padding. Only applies when exactly 2 source videos are provided; ignored otherwise. - Example: two 1280x720 (16:9) sources rendered at 1K HD become 1920x540 instead of - 1920x1080. The same pair rendered with -In4K becomes 3840x1080 instead of 3840x2160. + Behavior depends on the orientations of the two sources: + + - Both same orientation (both landscape or both portrait): the output height is set + to the larger of the two sources' scaled heights inside their half-width cells. + Example: two 1280x720 sources -> 1920x540 (1K) or 3840x1080 (4K). + + - Mixed orientations (one landscape, one portrait), and the landscape source is + smaller than the target resolution: the landscape video occupies its native pixel + width at 1K HD (or 2x in 4K), and the portrait video is scaled to match the + landscape's height and centered in the remaining width with black pillar boxes. + Example: 1280x720 landscape + 720x1280 portrait at 1K HD -> 1920x720 (landscape + occupies 1280x720 on its S1/S2 side, portrait fits in the remaining 640x720). + At 4K the same pair becomes 3840x1440 (landscape 2560x1440, portrait cell 1280x1440). .EXAMPLE .\Compare-Videos.ps1 -S1 original.mp4 -S2 enhanced.mp4 -Output comparison.mp4 @@ -186,6 +197,8 @@ if ($isPortrait) { } # --- Clip mode: shrink output height to the actual scaled height of the sources --- +$clipMixedLandscape = $false +$clipMixedFilterComplex = $null if ($Clip) { 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 @@ -197,34 +210,87 @@ if ($Clip) { $w2 = [int]$dim2[0] $h2 = [int]$dim2[1] - # Per-source cell width matches the 2-video branches below. - if ($isPortrait) { - $clipCellWidth = [int]($outputWidth * 0.6333 / 2) - } else { - $clipCellWidth = [int]($outputWidth / 2) + $s1IsLandscape = $width -gt $height + $s2IsLandscape = $w2 -gt $h2 + $isMixedOrientation = ($s1IsLandscape -xor $s2IsLandscape) + + if ($isMixedOrientation) { + # One source is landscape, the other is portrait. Try the mixed-orientation + # layout: the landscape video keeps its native pixel size at 1K HD (or 2x in 4K), + # and the portrait video is scaled to match the landscape's height and centered + # in the remaining width with black pillar-box bars. + $scaleFactor = $outputWidth / 1920.0 # 1.0 at 1K HD, 2.0 at 4K UHD + + if ($s1IsLandscape) { + $landscapeW = $width; $landscapeH = $height + } else { + $landscapeW = $w2; $landscapeH = $h2 + } + + $landscapeOutW = [int][Math]::Floor($landscapeW * $scaleFactor) + $landscapeOutH = [int][Math]::Floor($landscapeH * $scaleFactor) + if ($landscapeOutW % 2) { $landscapeOutW++ } + if ($landscapeOutH % 2) { $landscapeOutH++ } + + if ($landscapeOutW -lt $outputWidth -and $landscapeOutH -le $outputHeight) { + $portraitCellW = $outputWidth - $landscapeOutW + $outputHeight = $landscapeOutH + + Write-Host ("Clip mode (mixed orientation): landscape {0}x{1} + portrait {2}x{1} -> {3}x{1}" -f $landscapeOutW, $landscapeOutH, $portraitCellW, $outputWidth) -ForegroundColor Yellow + + # Preserve S1=left, S2=right ordering. Each input is scaled into its cell + # with aspect-ratio preserved, then padded with black to exactly fill the cell. + if ($s1IsLandscape) { + $leftW = $landscapeOutW; $rightW = $portraitCellW + } else { + $leftW = $portraitCellW; $rightW = $landscapeOutW + } + $clipMixedFilterComplex = "[0:v]scale=${leftW}:${landscapeOutH}:force_original_aspect_ratio=decrease,pad=${leftW}:${landscapeOutH}:(ow-iw)/2:(oh-ih)/2:black[v0];" + + "[1:v]scale=${rightW}:${landscapeOutH}:force_original_aspect_ratio=decrease,pad=${rightW}:${landscapeOutH}:(ow-iw)/2:(oh-ih)/2:black[v1];" + + "[v0][v1]hstack=inputs=2[outv]" + $clipMixedLandscape = $true + } else { + Write-Host "Clip mode: mixed orientations detected, but the landscape source already fills the target width; using standard clip layout." -ForegroundColor Yellow + } } - # Height each source would naturally scale to inside its cell when fitted by width - # (force_original_aspect_ratio=decrease). Take the larger of the two so neither - # source loses content; cap at the original output height. - $h1Scaled = [int][Math]::Floor($clipCellWidth * $height / $width) - $h2Scaled = [int][Math]::Floor($clipCellWidth * $h2 / $w2) - $clippedHeight = [Math]::Max($h1Scaled, $h2Scaled) - if ($clippedHeight % 2) { $clippedHeight++ } # libx264/yuv420p requires even dimensions - if ($clippedHeight -gt $outputHeight) { $clippedHeight = $outputHeight } + if (-not $clipMixedLandscape) { + # Standard -Clip behaviour: both sources share an orientation (or mixed fell back). + # Compute the largest natural scaled height across both sources and reduce the + # output height to that value. - if ($clippedHeight -lt $outputHeight) { - Write-Host ("Clip mode: output height reduced from {0} to {1} ({2}x{1})" -f $outputHeight, $clippedHeight, $outputWidth) -ForegroundColor Yellow - $outputHeight = $clippedHeight - } else { - Write-Host "Clip mode: sources already fill the full output height; no clipping applied." -ForegroundColor Yellow + # Per-source cell width matches the 2-video branches below. + if ($isPortrait) { + $clipCellWidth = [int]($outputWidth * 0.6333 / 2) + } else { + $clipCellWidth = [int]($outputWidth / 2) + } + + # Height each source would naturally scale to inside its cell when fitted by width + # (force_original_aspect_ratio=decrease). Take the larger of the two so neither + # source loses content; cap at the original output height. + $h1Scaled = [int][Math]::Floor($clipCellWidth * $height / $width) + $h2Scaled = [int][Math]::Floor($clipCellWidth * $h2 / $w2) + $clippedHeight = [Math]::Max($h1Scaled, $h2Scaled) + if ($clippedHeight % 2) { $clippedHeight++ } # libx264/yuv420p requires even dimensions + if ($clippedHeight -gt $outputHeight) { $clippedHeight = $outputHeight } + + if ($clippedHeight -lt $outputHeight) { + Write-Host ("Clip mode: output height reduced from {0} to {1} ({2}x{1})" -f $outputHeight, $clippedHeight, $outputWidth) -ForegroundColor Yellow + $outputHeight = $clippedHeight + } else { + Write-Host "Clip mode: sources already fill the full output height; no clipping applied." -ForegroundColor Yellow + } } } $filterComplex = "" $inputArgs = @("-i", $S1, "-i", $S2) -if ($isPortrait) { +if ($clipMixedLandscape) { + # Mixed-orientation -Clip layout was prepared above; use it as-is. + $filterComplex = $clipMixedFilterComplex +} elseif ($isPortrait) { # Portrait mode scaling - calculate cell dimensions based on output resolution $cellWidth2 = [int]($outputWidth * 0.6333 / 2) # ~608 for 1K, ~1216 for 4K $cellWidth3 = [int]($outputWidth * 0.6333 / 2) # ~608 for 1K, ~1216 for 4K diff --git a/Get-VideoInfo.ps1 b/Get-VideoInfo.ps1 index 53da915..2c12b12 100644 --- a/Get-VideoInfo.ps1 +++ b/Get-VideoInfo.ps1 @@ -161,7 +161,10 @@ try { $valueDisplay = ('{0} frames ({1}s, {2} fps)' -f $frames, $durationSeconds, $fps) } elseif ($fieldValue -is [array]) { - $valueDisplay = ($fieldValue | ConvertTo-Json -Compress -Depth 10) + # Pretty-print array values as JSON, prefixed with a newline so the + # multi-line output starts cleanly under the field label. + $jsonArray = $fieldValue | ConvertTo-Json -Depth 10 + $valueDisplay = [Environment]::NewLine + $jsonArray } else { $valueDisplay = $fieldValue