Files
Video-Processing-Scripts/Stich-InBetween.ps1
T
2026-05-19 14:55:09 -04:00

232 lines
7.6 KiB
PowerShell

<#
.SYNOPSIS
Generates interpolated frames between two images using RIFE and creates a video.
.DESCRIPTION
This script uses rife-ncnn-vulkan to generate intermediate frames between a first
and last image, then encodes the result into an H.264 video using ffmpeg.
The script:
- Validates input image files exist
- Creates a dedicated temporary directory for input images
- Copies the first and last images to the dedicated temp directory
- Runs rife-ncnn-vulkan to generate interpolated frames
- Filters generated frames to keep only the desired sequence
- Encodes final video with specified FPS
.PARAMETER First
Full or relative path to the first image file (e.g., "First.png" or "D:\Images\First.png").
.PARAMETER Last
Full or relative path to the last image file (e.g., "Last.png" or "D:\Images\Last.png").
.PARAMETER TempDir
Full absolute path to a temporary directory for intermediate PNG frames.
Must end with a backslash (\). Defaults to a system temp subdirectory.
.PARAMETER Frames
Number of frames to generate in the output video. Must be greater than 0.
.PARAMETER Video
Output video filename. If relative, will be resolved against current directory.
.PARAMETER FPS
Frames per second for the output video. Default is 24.
.EXAMPLE
.\Stich-InBetween.ps1 -First "frame1.png" -Last "frame2.png" -Frames 30 -Video "output.mp4"
.EXAMPLE
.\Stich-InBetween.ps1 -First "D:\Images\start.png" -Last "D:\Images\end.png" -Frames 60 -Video "D:\Output\video.mp4" -FPS 30
.NOTES
Requires:
- rife-ncnn-vulkan.exe in the system PATH
- ffmpeg in the system PATH
- PowerShell 7 or later
The script automatically locates rife-ncnn-vulkan.exe from PATH and expects
the rife-v4 model directory to be in the same directory as the executable.
Supports -WhatIf and -Confirm for safe execution.
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory = $true)]
[string]$First,
[Parameter(Mandatory = $true)]
[string]$Last,
[Parameter(Mandatory = $false)]
[string]$TempDir,
[Parameter(Mandatory = $true)]
[ValidateScript({ $_ -gt 0 })]
[int]$Frames,
[Parameter(Mandatory = $true)]
[string]$Video,
[Parameter(Mandatory = $false)]
[int]$FPS = 24
)
$ErrorActionPreference = "Stop"
# --- Validate and expand parameters ---
# First and Last - resolve to absolute paths
if (-not [System.IO.Path]::IsPathRooted($First)) {
$First = Join-Path (Get-Location).Path $First
}
if (-not [System.IO.Path]::IsPathRooted($Last)) {
$Last = Join-Path (Get-Location).Path $Last
}
# TempDir
if (-not $TempDir) {
$TempDir = [System.IO.Path]::GetTempPath() + [System.Guid]::NewGuid().ToString() + '\'
} else {
if (-not [System.IO.Path]::IsPathRooted($TempDir)) {
throw "TempDir must be a full absolute path, not a relative path: '$TempDir'"
}
if (-not $TempDir.EndsWith('\')) {
throw "TempDir must end with a backslash (\): '$TempDir'"
}
}
# Video - if relative, resolve against current directory
if (-not [System.IO.Path]::IsPathRooted($Video)) {
$Video = Join-Path (Get-Location).Path $Video
}
# --- Compute derived variables ---
$Count = 4 + (($Frames - 1) * 2)
$EndsAt = [int]($Count / 2)
# --- Validate source files ---
if (-not (Test-Path -LiteralPath $First -PathType Leaf)) {
throw "First image does not exist: $First"
}
if (-not (Test-Path -LiteralPath $Last -PathType Leaf)) {
throw "Last image does not exist: $Last"
}
# --- Delete output video if it exists ---
if (Test-Path -LiteralPath $Video -PathType Leaf) {
if ($PSCmdlet.ShouldProcess($Video, 'Delete existing output video')) {
Remove-Item -LiteralPath $Video -Force
Write-Host "Deleted existing output video: $Video"
}
}
# --- Create dedicated temp directory for input images ---
$InputTempDir = [System.IO.Path]::GetTempPath() + [System.Guid]::NewGuid().ToString() + '\'
$inputTempDirPath = $InputTempDir.TrimEnd('\')
if ($PSCmdlet.ShouldProcess($InputTempDir, 'Create dedicated input temp directory')) {
New-Item -ItemType Directory -Path $inputTempDirPath | Out-Null
Write-Host "Created input temp directory: $InputTempDir"
}
# --- Copy input images to dedicated temp directory ---
$FirstFilename = [System.IO.Path]::GetFileName($First)
$LastFilename = [System.IO.Path]::GetFileName($Last)
$InputFirstPath = Join-Path $inputTempDirPath $FirstFilename
$InputLastPath = Join-Path $inputTempDirPath $LastFilename
if ($PSCmdlet.ShouldProcess($First, "Copy to $InputFirstPath")) {
Copy-Item -LiteralPath $First -Destination $InputFirstPath -Force
Write-Host "Copied $First -> $InputFirstPath"
}
if ($PSCmdlet.ShouldProcess($Last, "Copy to $InputLastPath")) {
Copy-Item -LiteralPath $Last -Destination $InputLastPath -Force
Write-Host "Copied $Last -> $InputLastPath"
}
# --- Locate rife-ncnn-vulkan.exe ---
$rifeExe = Get-Command -Name "rife-ncnn-vulkan.exe" -ErrorAction SilentlyContinue
if (-not $rifeExe) {
throw "rife-ncnn-vulkan.exe not found in PATH. Please ensure it is installed and added to the system PATH."
}
$rifeExePath = $rifeExe.Source
$rifeDir = Split-Path -Parent $rifeExePath
$rifeModelPath = Join-Path $rifeDir "rife-v4"
if (-not (Test-Path -LiteralPath $rifeModelPath -PathType Container)) {
throw "RIFE model directory not found at: $rifeModelPath"
}
Write-Host "Found rife-ncnn-vulkan.exe at: $rifeExePath"
Write-Host "Using model path: $rifeModelPath"
# --- Prepare TempDir ---
$tempDirPath = $TempDir.TrimEnd('\')
if (Test-Path -LiteralPath $tempDirPath) {
if ($PSCmdlet.ShouldProcess($TempDir, 'Clear temporary directory')) {
Get-ChildItem -LiteralPath $tempDirPath -Force | Remove-Item -Recurse -Force
Write-Host "Cleared TempDir: $TempDir"
}
} else {
if ($PSCmdlet.ShouldProcess($TempDir, 'Create temporary directory')) {
New-Item -ItemType Directory -Path $tempDirPath | Out-Null
Write-Host "Created TempDir: $TempDir"
}
}
# --- Execute rife-ncnn-vulkan ---
if ($PSCmdlet.ShouldProcess($TempDir, "Generate interpolated frames with rife-ncnn-vulkan (Count=$Count)")) {
Write-Host "Executing rife-ncnn-vulkan.exe (Count=$Count, EndsAt=$EndsAt)..."
& $rifeExePath -0 $FirstFilename -1 $LastFilename -i $InputTempDir -o $TempDir -n $Count -m $rifeModelPath
if ($LASTEXITCODE -ne 0) {
throw "rife-ncnn-vulkan.exe failed with exit code $LASTEXITCODE"
}
# --- Clean up input temp directory ---
if (Test-Path -LiteralPath $inputTempDirPath) {
Remove-Item -LiteralPath $inputTempDirPath -Recurse -Force
Write-Host "Cleaned up input temp directory: $InputTempDir"
}
# --- Delete unwanted frames from TempDir ---
# Always delete 00000001.png; delete all files with numeric index > $EndsAt
$filesToDelete = Get-ChildItem -LiteralPath $tempDirPath -Filter "*.png" | Where-Object {
$index = [int]($_.BaseName)
$index -eq 1 -or $index -gt $EndsAt
}
foreach ($file in $filesToDelete) {
if ($PSCmdlet.ShouldProcess($file.FullName, 'Delete unwanted intermediate frame')) {
Remove-Item -LiteralPath $file.FullName -Force
Write-Host "Deleted: $($file.Name)"
}
}
}
# --- Execute ffmpeg ---
if ($PSCmdlet.ShouldProcess($Video, 'Create output video with ffmpeg')) {
Write-Host "Executing ffmpeg (FPS=$FPS)..."
& ffmpeg -y -framerate $FPS -i "${TempDir}%08d.png" -crf 8 -c:v libx264 -pix_fmt yuv420p "$Video"
if ($LASTEXITCODE -ne 0) {
throw "ffmpeg failed with exit code $LASTEXITCODE"
}
Write-Host "Video created successfully: $Video" -ForegroundColor Green
}