Add -Size parameter to Crop-Clips.ps1 for custom output resolution with cover-scaling and center-cropping, supporting named presets (320p-4K) that transpose for portrait sources and custom "width:height" format, overriding the cropProfiles table to process videos of any resolution instead of skipping non-standard sizes, with even-number dimension rounding for yuv420p compatibility and skipping of videos already at requested size, plus fix output info reporting to skip when -WhatIf or declined -Confirm leaves

This commit is contained in:
guru committed 2026-10-22 08:30:52 -04:00
1 parent 9088022ef7
commit 45f1396769
1 file changed
+83 -2
+83 -2
View File
@@ -5,10 +5,20 @@
Source directory containing video files. Source directory containing video files.
.PARAMETER Target .PARAMETER Target
Target directory for cropped video files. Target directory for cropped video files.
.PARAMETER Size
Size of the output video. Uses the known non-standard generator resolutions table if not specified.
Possible values: 320p, 384p, 480p, 720p, 900p, 1080p, HD, 1440p, 2K, 2160p, 4K, or custom size in format "width:height".
When specified, the resolutions table is overridden and every video is scaled (preserving aspect
ratio) so it fully covers the requested size, then centre-cropped to it, so videos of any
resolution are processed instead of being skipped.
Named presets are landscape (e.g. 1080p = 1920x1080) and are transposed for portrait sources
(a 1080x1920 source with -Size 1080p stays 1080x1920); a custom "width:height" is used exactly as
given. Videos already at the requested size are skipped.
.DESCRIPTION .DESCRIPTION
Crops video clips to standard resolutions using ffmpeg. Handles the non-standard resolutions Crops video clips to standard resolutions using ffmpeg. Handles the non-standard resolutions
produced by LTX-2, Kling and Wan (see the $cropProfiles table), plus any square (1:1) source, produced by LTX-2, Kling and Wan (see the $cropProfiles table), plus any square (1:1) source,
which is scaled to 1080x1080. Videos with any other resolution are skipped. which is scaled to 1080x1080. Videos with any other resolution are skipped, unless -Size is
used, which overrides the table and converts every video to the requested size.
Supports -WhatIf and -Confirm for safe execution. Supports -WhatIf and -Confirm for safe execution.
#> #>
[CmdletBinding(SupportsShouldProcess)] [CmdletBinding(SupportsShouldProcess)]
@@ -17,7 +27,10 @@ param(
[string]$Source = '.', [string]$Source = '.',
[Parameter(Mandatory = $false)] [Parameter(Mandatory = $false)]
[string]$Target = '.' [string]$Target = '.',
[Parameter(Mandatory = $false)]
[string]$Size = $null
) )
Set-StrictMode -Version Latest Set-StrictMode -Version Latest
@@ -133,6 +146,39 @@ $cropProfiles = @(
@{ Width = 1284; Height = 716; Filter = 'scale=-2:720,crop=1280:720:(in_w-1280)/2:(in_h-720)/2' } @{ Width = 1284; Height = 716; Filter = 'scale=-2:720,crop=1280:720:(in_w-1280)/2:(in_h-720)/2' }
) )
# --- Optional -Size: overrides the $cropProfiles lookup for every video ---
# Named presets are stored in landscape form and transposed for portrait sources, so a 9:16
# clip stays 9:16. A custom 'width:height' is always used exactly as given. Parsing happens
# once here so an invalid value fails before any video is touched.
$sizePresets = @{
'320p' = @{ Width = 570; Height = 320 }
'384p' = @{ Width = 684; Height = 384 }
'480p' = @{ Width = 854; Height = 480 }
'720p' = @{ Width = 1280; Height = 720 }
'900p' = @{ Width = 1600; Height = 900 }
'1080p' = @{ Width = 1920; Height = 1080 }
'HD' = @{ Width = 1920; Height = 1080 }
'1440p' = @{ Width = 2560; Height = 1440 }
'2K' = @{ Width = 2560; Height = 1440 }
'2160p' = @{ Width = 3840; Height = 2160 }
'4K' = @{ Width = 3840; Height = 2160 }
}
$sizePreset = $null
$sizeExact = $null
if (-not [string]::IsNullOrWhiteSpace($Size)) {
if ($sizePresets.ContainsKey($Size)) {
$sizePreset = $sizePresets[$Size]
}
elseif ($Size -match '^(\d+):(\d+)$') {
$sizeExact = @{ Width = [int]$Matches[1]; Height = [int]$Matches[2] }
}
else {
throw "Invalid -Size value: $Size. Possible values: 320p, 384p, 480p, 720p, 900p, 1080p, HD, 1440p, 2K, 2160p, 4K, or custom size in format ""width:height""."
}
}
$sourceFull = (Resolve-Path -LiteralPath $Source).Path $sourceFull = (Resolve-Path -LiteralPath $Source).Path
$targetFull = (Resolve-Path -LiteralPath $Target).Path $targetFull = (Resolve-Path -LiteralPath $Target).Path
@@ -162,6 +208,36 @@ foreach ($video in $videos) {
$inQuality = Format-Quality -BitRate $inInfo.BitRate $inQuality = Format-Quality -BitRate $inInfo.BitRate
$inAudioText = if ($inInfo.HasAudio) { 'yes' } else { 'no' } $inAudioText = if ($inInfo.HasAudio) { 'yes' } else { 'no' }
if ($sizePreset -or $sizeExact) {
# -Size given: ignore the resolution table and fit every video to the requested size by
# cover-scaling (force_original_aspect_ratio=increase keeps the pixel aspect ratio and
# guarantees full coverage) then centre-cropping to the exact size.
if ($sizeExact) {
$outW = $sizeExact.Width
$outH = $sizeExact.Height
}
elseif ($inInfo.Height -gt $inInfo.Width) {
# Portrait source: use the portrait form of the preset.
$outW = $sizePreset.Height
$outH = $sizePreset.Width
}
else {
$outW = $sizePreset.Width
$outH = $sizePreset.Height
}
# libx264/yuv420p requires even dimensions.
if ($outW % 2) { $outW++ }
if ($outH % 2) { $outH++ }
if ($inInfo.Width -eq $outW -and $inInfo.Height -eq $outH) {
Write-Host "Skipping: $($inInfo.FileName) (already ${outW}x${outH})"
continue
}
$vf = 'scale={0}:{1}:force_original_aspect_ratio=increase,crop={0}:{1}' -f $outW, $outH
}
else {
$cropProfile = $cropProfiles | Where-Object { $_.Width -eq $inInfo.Width -and $_.Height -eq $inInfo.Height } | Select-Object -First 1 $cropProfile = $cropProfiles | Where-Object { $_.Width -eq $inInfo.Width -and $_.Height -eq $inInfo.Height } | Select-Object -First 1
if ($cropProfile) { if ($cropProfile) {
@@ -179,6 +255,7 @@ foreach ($video in $videos) {
Write-Host "Skipping: $($inInfo.FileName) (resolution $inRes)" Write-Host "Skipping: $($inInfo.FileName) (resolution $inRes)"
continue continue
} }
}
Write-Host "Input : Filename=$($inInfo.FileName) Resolution=$inRes FPS=$inFpsText Quality=$inQuality Audio=$inAudioText" Write-Host "Input : Filename=$($inInfo.FileName) Resolution=$inRes FPS=$inFpsText Quality=$inQuality Audio=$inAudioText"
@@ -244,6 +321,9 @@ foreach ($video in $videos) {
Write-Host "WhatIf: Would crop video with ffmpeg $($formattedArgs -join ' ')" Write-Host "WhatIf: Would crop video with ffmpeg $($formattedArgs -join ' ')"
} }
# Under -WhatIf (or -Confirm declined) nothing was written, so there is no output file to
# probe; reporting is skipped rather than failing on the missing file.
if (Test-Path -LiteralPath $outFinalPath) {
$outInfo = Get-VideoInfo -Path $outFinalPath $outInfo = Get-VideoInfo -Path $outFinalPath
$outRes = "{0}x{1}" -f $outInfo.Width, $outInfo.Height $outRes = "{0}x{1}" -f $outInfo.Width, $outInfo.Height
$outFpsText = if ($outInfo.Fps) { "{0:0.###}" -f $outInfo.Fps } else { 'unknown' } $outFpsText = if ($outInfo.Fps) { "{0:0.###}" -f $outInfo.Fps } else { 'unknown' }
@@ -251,6 +331,7 @@ foreach ($video in $videos) {
$outAudioText = if ($outInfo.HasAudio) { 'yes' } else { 'no' } $outAudioText = if ($outInfo.HasAudio) { 'yes' } else { 'no' }
Write-Host "Output: Filename=$($outInfo.FileName) Resolution=$outRes FPS=$outFpsText Quality=$outQuality Audio=$outAudioText" Write-Host "Output: Filename=$($outInfo.FileName) Resolution=$outRes FPS=$outFpsText Quality=$outQuality Audio=$outAudioText"
}
$processedCount++ $processedCount++
} }