<# .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 }