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
+101 -20
+101 -20
View File
@@ -5,10 +5,20 @@
Source directory containing video files.
.PARAMETER Target
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
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,
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.
#>
[CmdletBinding(SupportsShouldProcess)]
@@ -17,7 +27,10 @@ param(
[string]$Source = '.',
[Parameter(Mandatory = $false)]
[string]$Target = '.'
[string]$Target = '.',
[Parameter(Mandatory = $false)]
[string]$Size = $null
)
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' }
)
# --- 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
$targetFull = (Resolve-Path -LiteralPath $Target).Path
@@ -162,22 +208,53 @@ foreach ($video in $videos) {
$inQuality = Format-Quality -BitRate $inInfo.BitRate
$inAudioText = if ($inInfo.HasAudio) { 'yes' } else { 'no' }
$cropProfile = $cropProfiles | Where-Object { $_.Width -eq $inInfo.Width -and $_.Height -eq $inInfo.Height } | Select-Object -First 1
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
}
if ($cropProfile) {
$vf = $cropProfile.Filter
}
elseif ($inInfo.Width -eq $inInfo.Height -and $inInfo.Width -ne 1080) {
# Any square (1:1) source other than 1080x1080 is scaled to 1080x1080; no crop needed.
$vf = 'scale=1080:1080'
# 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 {
$vf = $null
}
$cropProfile = $cropProfiles | Where-Object { $_.Width -eq $inInfo.Width -and $_.Height -eq $inInfo.Height } | Select-Object -First 1
if (-not $vf) {
Write-Host "Skipping: $($inInfo.FileName) (resolution $inRes)"
continue
if ($cropProfile) {
$vf = $cropProfile.Filter
}
elseif ($inInfo.Width -eq $inInfo.Height -and $inInfo.Width -ne 1080) {
# Any square (1:1) source other than 1080x1080 is scaled to 1080x1080; no crop needed.
$vf = 'scale=1080:1080'
}
else {
$vf = $null
}
if (-not $vf) {
Write-Host "Skipping: $($inInfo.FileName) (resolution $inRes)"
continue
}
}
Write-Host "Input : Filename=$($inInfo.FileName) Resolution=$inRes FPS=$inFpsText Quality=$inQuality Audio=$inAudioText"
@@ -244,13 +321,17 @@ foreach ($video in $videos) {
Write-Host "WhatIf: Would crop video with ffmpeg $($formattedArgs -join ' ')"
}
$outInfo = Get-VideoInfo -Path $outFinalPath
$outRes = "{0}x{1}" -f $outInfo.Width, $outInfo.Height
$outFpsText = if ($outInfo.Fps) { "{0:0.###}" -f $outInfo.Fps } else { 'unknown' }
$outQuality = Format-Quality -BitRate $outInfo.BitRate
$outAudioText = if ($outInfo.HasAudio) { 'yes' } else { 'no' }
# 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
$outRes = "{0}x{1}" -f $outInfo.Width, $outInfo.Height
$outFpsText = if ($outInfo.Fps) { "{0:0.###}" -f $outInfo.Fps } else { 'unknown' }
$outQuality = Format-Quality -BitRate $outInfo.BitRate
$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++
}