Inital push
This commit is contained in:
+160
@@ -0,0 +1,160 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Extracts specific range of frames of a video file to PNG images using ffmpeg.
|
||||
.PARAMETER Video
|
||||
Path to the input video file. Can be absolute or relative to current directory.
|
||||
.PARAMETER Images
|
||||
Path to output PNG images files base name. Can be absolute or relative to current directory.
|
||||
If not provided, the output files will be in subdirectory 'Frames' and named as the video file with a frame number suffix padded with zeros to 4 digits.
|
||||
.PARAMETER Frames
|
||||
0-based frames index to extract. Supports the following formats:
|
||||
- start-end: Extract frames from start to end (e.g., "100-200")
|
||||
- positive_number: Extract frames from 0 to that number (e.g., "50")
|
||||
- -negative_number: Extract last N frames (e.g., "-10")
|
||||
- %: Represents the last frame index. Can be used alone or in ranges (e.g., "%", "10-%", "-%")
|
||||
- all: Extract all frames (same as "0-%")
|
||||
.DESCRIPTION
|
||||
Extracts specific range of frames of a video file to PNG images using ffmpeg.
|
||||
Supports -WhatIf and -Confirm for safe execution.
|
||||
#>
|
||||
[CmdletBinding(SupportsShouldProcess)]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Video,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Images = '',
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Frames = ''
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
$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 $totalFrames -notmatch '^\d+$') {
|
||||
Write-Error ('Could not determine frame count for: {0}' -f $VideoPath)
|
||||
exit 1
|
||||
}
|
||||
|
||||
$totalFramesInt = [int]$totalFrames
|
||||
$maxFrameIndex = $totalFramesInt - 1
|
||||
|
||||
$startFrame = 0
|
||||
$endFrame = 0
|
||||
|
||||
# Replace % with actual last frame index, and handle 'all' keyword
|
||||
$framesPattern = $Frames -replace '%', $maxFrameIndex
|
||||
if ($framesPattern -ieq 'all') {
|
||||
$framesPattern = "0-$maxFrameIndex"
|
||||
}
|
||||
|
||||
if ($framesPattern -match '^(\d+)-(\d+)$') {
|
||||
$startFrame = [int]$matches[1]
|
||||
$endFrame = [int]$matches[2]
|
||||
}
|
||||
elseif ($framesPattern -match '^-(\d+)$') {
|
||||
$negativeCount = [int]$matches[1]
|
||||
$startFrame = [Math]::Max(0, $maxFrameIndex - $negativeCount)
|
||||
$endFrame = $maxFrameIndex
|
||||
}
|
||||
elseif ($framesPattern -match '^\d+$') {
|
||||
$positiveCount = [int]$framesPattern
|
||||
$startFrame = 0
|
||||
$endFrame = [Math]::Min($positiveCount, $maxFrameIndex)
|
||||
}
|
||||
else {
|
||||
Write-Error ('Invalid Frames format: {0}. Expected: start-end, positive number, negative number, % for last frame, or "all"' -f $Frames)
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($startFrame -lt 0 -or $startFrame -gt $maxFrameIndex) {
|
||||
Write-Error ('Start frame {0} is out of range. Valid range: 0 to {1}' -f $startFrame, $maxFrameIndex)
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($endFrame -lt $startFrame -or $endFrame -gt $maxFrameIndex) {
|
||||
Write-Error ('End frame {0} is out of range. Valid range: {1} to {2}' -f $endFrame, $startFrame, $maxFrameIndex)
|
||||
exit 1
|
||||
}
|
||||
|
||||
$frameCount = $endFrame - $startFrame + 1
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Images)) {
|
||||
$videoBaseName = [System.IO.Path]::GetFileNameWithoutExtension($Video)
|
||||
$outputDir = Split-Path -Parent $VideoPath
|
||||
$Images = Join-Path $outputDir 'Frames' ($videoBaseName + '_%04d.png')
|
||||
} else {
|
||||
$outputPattern = $Images
|
||||
|
||||
if ($outputPattern -notmatch '%\d+d') {
|
||||
$outputPattern = $outputPattern -replace '\.png$', ''
|
||||
$outputPattern = $outputPattern + '_%04d.png'
|
||||
}
|
||||
|
||||
$Images = $outputPattern
|
||||
}
|
||||
|
||||
$outputDir = Split-Path -Parent $Images
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ('Extracting frames from video: {0}' -f $Video)
|
||||
Write-Host (' Frame range: {0} to {1} ({2} frames)' -f $startFrame, $endFrame, $frameCount)
|
||||
|
||||
$outputBaseName = $Images -replace '%\d+d\.png$', ''
|
||||
$outputExtension = '.png'
|
||||
|
||||
for ($frameIndex = $startFrame; $frameIndex -le $endFrame; $frameIndex++) {
|
||||
$outputFile = ('{0}{1:D4}{2}' -f $outputBaseName, $frameIndex, $outputExtension)
|
||||
|
||||
$videoFilter = 'select=eq(n\,{0})' -f $frameIndex
|
||||
|
||||
$ffmpegArgs = @(
|
||||
'-i', $VideoPath
|
||||
'-vf', $videoFilter
|
||||
'-vsync', 'vfr'
|
||||
'-frames:v', '1'
|
||||
'-q:v', '1'
|
||||
'--', $outputFile
|
||||
)
|
||||
|
||||
if ($PSCmdlet.ShouldProcess($outputFile, ('Extract frame {0} to PNG' -f $frameIndex))) {
|
||||
Write-Host (' Extracting frame {0} to: {1}' -f $frameIndex, (Split-Path -Leaf $outputFile))
|
||||
|
||||
$ffmpegOutput = & ffmpeg @ffmpegArgs 2>&1
|
||||
$ffmpegExit = $LASTEXITCODE
|
||||
|
||||
if ($ffmpegExit -ne 0) {
|
||||
$ffmpegOutput | ForEach-Object { Write-Error $_ }
|
||||
Write-Error ('ffmpeg failed to extract frame {0}' -f $frameIndex)
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ('Frames extracted successfully')
|
||||
Reference in New Issue
Block a user