Add -Crop parameter to Clone-Video.ps1 for center-cropping instead of resizing when combined with -Size, implementing Get-SizeFilter function that crops only dimensions larger than requested size with even-number rounding, applying crop filter across image-to-video, PNG sequence, and standard video modes, and updating documentation and output messages to indicate cropped dimensions
This commit is contained in:
1 parent
aaa293734e
commit
70b4b83be0
1 file changed
+69
-13
+69
-13
@@ -14,11 +14,11 @@
|
||||
from the image sequence, ordered alphabetically (or reverse alphabetically if -Reverse is specified).
|
||||
- -Sequence and -Metadata are not allowed in this mode.
|
||||
- Defaults: 24 FPS, CRF 8, no audio (unless -Audio is provided).
|
||||
- -Size, -FPS, -CRF, -Audio, -NoAudio, -StartFrame, -EndFrame, -Frames, -Reverse and -LTX are all honoured.
|
||||
- -Size, -FPS, -CRF, -Audio, -NoAudio, -StartFrame, -EndFrame, -Frames, -Reverse, -LTX and -Crop are all honoured.
|
||||
When Source is a supported image file (.png, .jpg, .jpeg, .webp, .bmp, .tiff, .tif, .gif) and Audio is provided, the script builds a video of the static image for the duration of the audio file.
|
||||
- -NoAudio is not allowed in this mode.
|
||||
- Defaults: 24 FPS, CRF 8, source image dimensions.
|
||||
- -Size, -FPS, -CRF and -LTX are honoured.
|
||||
- -Size, -FPS, -CRF, -LTX and -Crop are honoured.
|
||||
- -StartFrame, -EndFrame, -Frames, -Sequence, -Metadata and -Reverse are ignored.
|
||||
.PARAMETER Source
|
||||
Path to the input video file, or a directory containing PNG files with sequence numbers (e.g. frame_0000.png).
|
||||
@@ -74,6 +74,16 @@
|
||||
- 720p: 1280x704 (16:9), 704x1280 (9:16), 1088x576 (21:9), 576x1088 (9:21)
|
||||
- 480p: 832x448 (16:9), 448x832 (9:16), 896x512 (4:3), 512x896 (3:4)
|
||||
Forces a video re-encode (same as -Size).
|
||||
.PARAMETER Crop
|
||||
When present, the dimensions selected by -Size (including -LTX buckets) are obtained by center-cropping
|
||||
the source instead of resizing it, so the original pixel aspect ratio is kept (no stretching or scaling).
|
||||
Each dimension is cropped only when the source is bigger than the requested size; a dimension where the
|
||||
source is equal or smaller is left untouched (no upscaling or padding). Cropped dimensions are rounded
|
||||
down to an even number. Examples for a 1080x1080 source:
|
||||
- -Size 960:1080 -Crop: cropped horizontally to 960x1080.
|
||||
- -Size 720:720 -Crop: cropped horizontally and vertically to 720x720.
|
||||
Requires -Size. Without -Crop, the source is resized (default behaviour).
|
||||
Forces a video re-encode (same as -Size).
|
||||
.DESCRIPTION
|
||||
This script uses ffmpeg to clone (copy) a video file with optional frame range and audio exclusion.
|
||||
When Sequence is provided, it extracts a specific frame to PNG and then clones the video accordingly.
|
||||
@@ -123,7 +133,10 @@ param(
|
||||
[switch]$Reverse,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$LTX
|
||||
[switch]$LTX,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$Crop
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
@@ -271,6 +284,31 @@ if (-not [string]::IsNullOrWhiteSpace($Size)) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($Crop -and [string]::IsNullOrWhiteSpace($Size)) {
|
||||
Write-Error '-Crop requires -Size'
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Builds the ffmpeg size filter for the requested output dimensions.
|
||||
# Without -Crop the source is resized (scale). With -Crop the source is center-cropped,
|
||||
# only on the dimensions where it is bigger than the requested size.
|
||||
function Get-SizeFilter {
|
||||
param([int]$SrcWidth, [int]$SrcHeight, [int]$OutWidth, [int]$OutHeight)
|
||||
if (-not $Crop) {
|
||||
return @{ Filter = ('scale={0}:{1}' -f $OutWidth, $OutHeight); Width = $OutWidth; Height = $OutHeight }
|
||||
}
|
||||
if ($SrcWidth -le 0 -or $SrcHeight -le 0) {
|
||||
Write-Error 'Could not determine source dimensions; -Crop cannot compute the crop area.'
|
||||
exit 1
|
||||
}
|
||||
$cropWidth = [Math]::Min($OutWidth, $SrcWidth)
|
||||
$cropHeight = [Math]::Min($OutHeight, $SrcHeight)
|
||||
$cropWidth -= $cropWidth % 2
|
||||
$cropHeight -= $cropHeight % 2
|
||||
return @{ Filter = ('crop={0}:{1}' -f $cropWidth, $cropHeight); Width = $cropWidth; Height = $cropHeight }
|
||||
}
|
||||
$cropNote = if ($Crop) { ' (cropped)' } else { '' }
|
||||
|
||||
$PSNativeCommandUseErrorActionPreference = $false
|
||||
|
||||
# Image-to-video mode: when $Source is a supported image file and $Audio is provided.
|
||||
@@ -356,6 +394,12 @@ if (($supportedImageExts -contains $sourceExt) -and $AudioPath) {
|
||||
if ($imgOutWidth % 2) { $imgOutWidth++ }
|
||||
if ($imgOutHeight % 2) { $imgOutHeight++ }
|
||||
|
||||
if ($imgOutWidth -gt 0) {
|
||||
$imgSizeFilter = Get-SizeFilter $imgSrcWidth $imgSrcHeight $imgOutWidth $imgOutHeight
|
||||
$imgOutWidth = $imgSizeFilter.Width
|
||||
$imgOutHeight = $imgSizeFilter.Height
|
||||
}
|
||||
|
||||
$imgFfmpegArgs = @(
|
||||
'-y'
|
||||
'-loop', '1'
|
||||
@@ -365,7 +409,7 @@ if (($supportedImageExts -contains $sourceExt) -and $AudioPath) {
|
||||
)
|
||||
|
||||
if ($imgOutWidth -gt 0) {
|
||||
$imgFfmpegArgs += @('-vf', ('scale={0}:{1}' -f $imgOutWidth, $imgOutHeight))
|
||||
$imgFfmpegArgs += @('-vf', $imgSizeFilter.Filter)
|
||||
}
|
||||
|
||||
$imgFfmpegArgs += $targetVideoEncodeArgs
|
||||
@@ -391,9 +435,9 @@ if (($supportedImageExts -contains $sourceExt) -and $AudioPath) {
|
||||
Write-Host (' FPS: {0}' -f $imgFPS)
|
||||
if ($imgOutWidth -gt 0) {
|
||||
if ($LTX) {
|
||||
Write-Host (' Size: {0}x{1} (LTX-2)' -f $imgOutWidth, $imgOutHeight)
|
||||
Write-Host (' Size: {0}x{1} (LTX-2){2}' -f $imgOutWidth, $imgOutHeight, $cropNote)
|
||||
} else {
|
||||
Write-Host (' Size: {0}x{1}' -f $imgOutWidth, $imgOutHeight)
|
||||
Write-Host (' Size: {0}x{1}{2}' -f $imgOutWidth, $imgOutHeight, $cropNote)
|
||||
}
|
||||
}
|
||||
Write-Host (' CRF: {0}' -f $imgCRF)
|
||||
@@ -542,6 +586,12 @@ if (Test-Path -LiteralPath $SourcePath -PathType Container) {
|
||||
if ($seqOutWidth % 2) { $seqOutWidth++ }
|
||||
if ($seqOutHeight % 2) { $seqOutHeight++ }
|
||||
|
||||
if ($seqOutWidth -gt 0) {
|
||||
$seqSizeFilter = Get-SizeFilter $seqSrcWidth $seqSrcHeight $seqOutWidth $seqOutHeight
|
||||
$seqOutWidth = $seqSizeFilter.Width
|
||||
$seqOutHeight = $seqSizeFilter.Height
|
||||
}
|
||||
|
||||
$concatTempFile = [System.IO.Path]::GetTempFileName()
|
||||
try {
|
||||
$frameDuration = (1.0 / $seqFPS).ToString('0.######', [System.Globalization.CultureInfo]::InvariantCulture)
|
||||
@@ -568,7 +618,7 @@ if (Test-Path -LiteralPath $SourcePath -PathType Container) {
|
||||
$seqFfmpegArgs += @('-r', $seqFPS.ToString('0.######', [System.Globalization.CultureInfo]::InvariantCulture))
|
||||
|
||||
if ($seqOutWidth -gt 0) {
|
||||
$seqFfmpegArgs += @('-vf', ('scale={0}:{1}' -f $seqOutWidth, $seqOutHeight))
|
||||
$seqFfmpegArgs += @('-vf', $seqSizeFilter.Filter)
|
||||
}
|
||||
|
||||
$seqFfmpegArgs += $targetVideoEncodeArgs
|
||||
@@ -593,9 +643,9 @@ if (Test-Path -LiteralPath $SourcePath -PathType Container) {
|
||||
Write-Host (' FPS: {0}' -f $seqFPS)
|
||||
if ($seqOutWidth -gt 0) {
|
||||
if ($LTX) {
|
||||
Write-Host (' Size: {0}x{1} (LTX-2)' -f $seqOutWidth, $seqOutHeight)
|
||||
Write-Host (' Size: {0}x{1} (LTX-2){2}' -f $seqOutWidth, $seqOutHeight, $cropNote)
|
||||
} else {
|
||||
Write-Host (' Size: {0}x{1}' -f $seqOutWidth, $seqOutHeight)
|
||||
Write-Host (' Size: {0}x{1}{2}' -f $seqOutWidth, $seqOutHeight, $cropNote)
|
||||
}
|
||||
}
|
||||
Write-Host (' CRF: {0}' -f $seqCRF)
|
||||
@@ -758,6 +808,12 @@ if ($LTX) {
|
||||
$scaleHeight = $best.H
|
||||
}
|
||||
|
||||
if ($scaleWidth -gt 0) {
|
||||
$sizeFilter = Get-SizeFilter $sourceWidth $sourceHeight $scaleWidth $scaleHeight
|
||||
$scaleWidth = $sizeFilter.Width
|
||||
$scaleHeight = $sizeFilter.Height
|
||||
}
|
||||
|
||||
$ffprobeBitrateOutput = & ffprobe -v error -select_streams v:0 -show_entries stream=bit_rate -of default=noprint_wrappers=1:nokey=1 -- $SourcePath 2>&1
|
||||
$ffprobeExit = $LASTEXITCODE
|
||||
|
||||
@@ -882,9 +938,9 @@ if ($AudioPath -and $externalAudioCodec) {
|
||||
Write-Host (' Frame range: {0} to {1} ({2} frames)' -f $StartFrame, $actualEndFrame, $frameCount)
|
||||
if ($scaleWidth -gt 0) {
|
||||
if ($LTX) {
|
||||
Write-Host (' Size: {0}x{1} (LTX-2)' -f $scaleWidth, $scaleHeight)
|
||||
Write-Host (' Size: {0}x{1} (LTX-2){2}' -f $scaleWidth, $scaleHeight, $cropNote)
|
||||
} else {
|
||||
Write-Host (' Size: {0}x{1}' -f $scaleWidth, $scaleHeight)
|
||||
Write-Host (' Size: {0}x{1}{2}' -f $scaleWidth, $scaleHeight, $cropNote)
|
||||
}
|
||||
}
|
||||
if ($FPS -gt 0) {
|
||||
@@ -910,7 +966,7 @@ if ($StartFrame -gt 0 -or $actualEndFrame -lt $maxFrameIndex) {
|
||||
$filterParts = @('select=between(n\,{0}\,{1})' -f $StartFrame, $actualEndFrame)
|
||||
$filterParts += 'setpts=PTS-STARTPTS'
|
||||
if ($scaleWidth -gt 0) {
|
||||
$filterParts += ('scale={0}:{1}' -f $scaleWidth, $scaleHeight)
|
||||
$filterParts += $sizeFilter.Filter
|
||||
}
|
||||
if ($FPS -gt 0) {
|
||||
$filterParts += ('fps={0}' -f $FPS)
|
||||
@@ -937,7 +993,7 @@ if ($StartFrame -gt 0 -or $actualEndFrame -lt $maxFrameIndex) {
|
||||
} elseif ($needsReencode) {
|
||||
$filterParts = @()
|
||||
if ($scaleWidth -gt 0) {
|
||||
$filterParts += ('scale={0}:{1}' -f $scaleWidth, $scaleHeight)
|
||||
$filterParts += $sizeFilter.Filter
|
||||
}
|
||||
if ($FPS -gt 0) {
|
||||
$filterParts += ('fps={0}' -f $FPS)
|
||||
|
||||
Reference in new issue
Block a user