<# .SYNOPSIS Extracts a specific frame of a video file to a PNG image using Get-LastFrame.ps1, requiring -Video and -Frame parameters from that script. The image filename used for the -Image parameter is automatically derived from the -Video filename, with a "-" followed by the "-Frame" value padded with zeros (maximum of 4 digits) inserted before the image file extension (e.g., P01.mp4 for frame 10 becomes P01-0010.png). .PARAMETER Video Path to the input video file. Can be absolute or relative to current directory. .PARAMETER Frame 0-based frame index to extract. Negative values count from the end: -1 extracts the last frame (default), -2 extracts second-to-last, etc. The resulting frame index is clamped to the valid range (0 to total frames - 1). #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory = $true)] [string]$Video, [Parameter(Mandatory = $true)] [int]$Frame ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $VideoPath = $Video if (-not (Test-Path -LiteralPath $VideoPath)) { Write-Error ('Video file not found: {0}' -f $VideoPath) exit 1 } $PSNativeCommandUseErrorActionPreference = $false # Probe total frame count to resolve negative frame indices and derive the output filename $totalFramesOutput = & ffprobe -v error -select_streams v:0 -count_frames -show_entries stream=nb_read_frames -of csv=p=0 -- $VideoPath 2>&1 $ffprobeExit = $LASTEXITCODE if ($ffprobeExit -ne 0) { $totalFramesOutput | ForEach-Object { Write-Error $_ } Write-Error ('ffprobe failed to read video file: {0}' -f $VideoPath) exit 1 } $totalFrames = $totalFramesOutput | Select-Object -First 1 if (-not $totalFrames -or ([string]$totalFrames).Trim() -notmatch '^\d+$') { Write-Error ('Could not determine frame count for: {0}' -f $VideoPath) exit 1 } $totalFramesInt = [int]([string]$totalFrames).Trim() $maxFrameIndex = $totalFramesInt - 1 # Resolve frame index (handles negative values that count from the end) $frameToExtract = if ($Frame -lt 0) { $calculatedFrame = $totalFramesInt + $Frame [Math]::Max(0, [Math]::Min($calculatedFrame, $maxFrameIndex)) } else { if ($Frame -gt $maxFrameIndex) { Write-Error ('Frame index {0} is out of range. Valid range: 0 to {1}' -f $Frame, $maxFrameIndex) exit 1 } $Frame } # Derive the output image path: insert "-XXXX" (zero-padded resolved frame index) before .png extension $videoBase = [System.IO.Path]::GetFileNameWithoutExtension($VideoPath) $videoDir = [System.IO.Path]::GetDirectoryName($VideoPath) $framePadded = $frameToExtract.ToString().PadLeft(4, '0') $imageName = "$videoBase-$framePadded.png" $imagePath = if ([string]::IsNullOrEmpty($videoDir)) { $imageName } else { Join-Path $videoDir $imageName } $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $getLastFrameScript = Join-Path $scriptDir 'Get-LastFrame.ps1' # Forward common parameters to Get-LastFrame.ps1 $passThrough = @() if ($PSBoundParameters.ContainsKey('WhatIf')) { $passThrough += '-WhatIf' } if ($PSBoundParameters.ContainsKey('Confirm')) { $passThrough += '-Confirm' } & $getLastFrameScript -Video $VideoPath -Image $imagePath -Frame $frameToExtract @passThrough exit $LASTEXITCODE