79 lines
2.6 KiB
PowerShell
79 lines
2.6 KiB
PowerShell
<#
|
|
.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)
|
|
}
|