<# .SYNOPSIS Extracts a specific frame or the last frame of a video file to a PNG image using ffmpeg. .PARAMETER Video Path to the input video file. Can be absolute or relative to current directory. .PARAMETER Image Path to the output PNG image 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). .DESCRIPTION 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 -WhatIf and -Confirm for safe execution. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory = $true)] [string]$Video, [Parameter(Mandatory = $true)] [string]$Image, [Parameter(Mandatory = $false)] [int]$Frame = -1 ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $VideoPath = $Video if (-not (Test-Path $VideoPath)) { Write-Error ('Video file not found: {0}' -f $VideoPath) exit 1 } $PSNativeCommandUseErrorActionPreference = $false $totalFrames = & ffprobe -v error -select_streams v:0 -count_frames -show_entries stream=nb_read_frames -of csv=p=0 $VideoPath 2>&1 | Select-Object -First 1 if (-not $totalFrames -or $totalFrames -notmatch '^\d+$') { Write-Error ('Could not determine frame count for: {0}' -f $VideoPath) exit 1 } $totalFramesInt = [int]$totalFrames $maxFrameIndex = $totalFramesInt - 1 $frameToExtract = if ($Frame -lt 0) { # Negative values count from the end $calculatedFrame = $totalFramesInt + $Frame # Clamp to valid range [0, maxFrameIndex] [Math]::Max(0, [Math]::Min($calculatedFrame, $maxFrameIndex)) } else { # Positive values are direct indices if ($Frame -gt $maxFrameIndex) { Write-Error ('Frame index {0} is out of range. Valid range: 0 to {1}' -f $Frame, $maxFrameIndex) exit 1 } $Frame } Write-Host ('Total frames: {0} - extracting frame index {1}' -f $totalFramesInt, $frameToExtract) if ($PSCmdlet.ShouldProcess($Image, ('Extract frame {0} to PNG' -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 if ($LASTEXITCODE -ne 0) { $ffmpegStderr | ForEach-Object { Write-Error $_ } Write-Error 'ffmpeg failed to extract the last frame.' exit 1 } Write-Host ('Frame saved to: {0}' -f $Image) }