Add printf-style numbering token support to Get-LastFrame.ps1 for -Image parameter (e.g. frame_%04d.png), resolving token to actual 0-based frame index before passing to ffmpeg to avoid image2 sequence counter behavior, with automatic output directory creation when path does not exist and updated documentation matching Get-FramesRange.ps1 functionality

This commit is contained in:
guru committed 2026-10-22 01:44:58 -04:00
1 parent c057f804bf
commit 9088022ef7
1 file changed
+41 -3
+41 -3
View File
@@ -5,6 +5,10 @@
Path to the input video file. Can be absolute or relative to current directory. Path to the input video file. Can be absolute or relative to current directory.
.PARAMETER Image .PARAMETER Image
Path to the output PNG image file. Can be absolute or relative to current directory. Path to the output PNG image file. Can be absolute or relative to current directory.
Supports a printf-style numbering token (e.g. frame_%04d.png or C:\Output\frame_%04d.png), which is
replaced with the extracted 0-based frame index, zero-padded to the width given in the token
(%d = no padding, %04d = 4 digits). Without a token the path is used literally.
The output directory is created when it does not exist.
.PARAMETER Frame .PARAMETER Frame
0-based frame index to extract. Negative values count from the end: 0-based frame index to extract. Negative values count from the end:
-1 extracts the last frame (default), -2 extracts second-to-last, etc. -1 extracts the last frame (default), -2 extracts second-to-last, etc.
@@ -12,6 +16,7 @@
.DESCRIPTION .DESCRIPTION
Extracts a specific frame of a video file to a PNG image using ffmpeg. Extracts a specific frame of a video file to a PNG image using ffmpeg.
Supports negative frame indices to count from the end of the video. Supports negative frame indices to count from the end of the video.
Supports a printf-style numbering token in -Image, like Get-FramesRange.ps1.
Supports -WhatIf and -Confirm for safe execution. Supports -WhatIf and -Confirm for safe execution.
#> #>
[CmdletBinding(SupportsShouldProcess)] [CmdletBinding(SupportsShouldProcess)]
@@ -64,9 +69,42 @@ $frameToExtract = if ($Frame -lt 0) {
Write-Host ('Total frames: {0} - extracting frame index {1}' -f $totalFramesInt, $frameToExtract) Write-Host ('Total frames: {0} - extracting frame index {1}' -f $totalFramesInt, $frameToExtract)
if ($PSCmdlet.ShouldProcess($Image, ('Extract frame {0} to PNG' -f $frameToExtract))) { # Resolve any printf-style numbering token (%d, %04d) in -Image to the extracted frame
# index. The substitution is done here rather than left to ffmpeg: ffmpeg would treat the
# token as an image2 sequence counter starting at 1 instead of the actual video frame index.
$ImagePath = $Image
$tokenPattern = [regex]'%(\d*)d'
while ($ImagePath -match $tokenPattern) {
$padWidth = if ($Matches[1]) { [int]$Matches[1] } else { 0 }
$ImagePath = $tokenPattern.Replace($ImagePath, ([string]$frameToExtract).PadLeft($padWidth, '0'), 1)
}
if ($ImagePath -ne $Image) {
Write-Host ('Output pattern resolved to: {0}' -f $ImagePath)
}
$outputDir = Split-Path -Parent $ImagePath
if ($outputDir -and -not (Test-Path -LiteralPath $outputDir)) {
if ($PSCmdlet.ShouldProcess($outputDir, 'Create output directory')) {
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
Write-Host ('Created output directory: {0}' -f $outputDir)
}
}
if ($PSCmdlet.ShouldProcess($ImagePath, ('Extract frame {0} to PNG' -f $frameToExtract))) {
$videoFilter = 'select=eq(n\,{0})' -f $frameToExtract $videoFilter = 'select=eq(n\,{0})' -f $frameToExtract
$ffmpegStderr = & ffmpeg -y -i $VideoPath -vf $videoFilter -vsync vfr -frames:v 1 -q:v 1 $Image 2>&1
$ffmpegArgs = @(
'-y'
'-i', $VideoPath
'-vf', $videoFilter
'-vsync', 'vfr'
'-frames:v', '1'
'-q:v', '1'
'--', $ImagePath
)
$ffmpegStderr = & ffmpeg @ffmpegArgs 2>&1
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
$ffmpegStderr | ForEach-Object { Write-Error $_ } $ffmpegStderr | ForEach-Object { Write-Error $_ }
@@ -74,5 +112,5 @@ if ($PSCmdlet.ShouldProcess($Image, ('Extract frame {0} to PNG' -f $frameToExtra
exit 1 exit 1
} }
Write-Host ('Frame saved to: {0}' -f $Image) Write-Host ('Frame saved to: {0}' -f $ImagePath)
} }