Private/Invoke-BsnCastRender.ps1
|
<#
.SYNOPSIS Renders a cast file to a video format (GIF, WebM or MP4). .DESCRIPTION Implements the -RecordingFormat semantics of RFC 421 (Terminal Session Recording) at module scope: the .cast file is always the source of truth, video files are render products of it. Rendering uses external tools when they are discoverable on PATH: - Gif: rendered directly by 'agg'. - WebM/Mp4: rendered to a temporary GIF by 'agg', then transcoded by 'ffmpeg' (-pix_fmt yuv420p). When a required tool is missing, the cmdlet warns and returns $null - exactly the RFC's degradation contract: the .cast file remains complete and valid, and can be re-rendered later with different tools. #> function Invoke-BsnCastRender { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Rendering a file is not a state-changing operation on system state.')] [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory = $true)] [string]$CastPath, [Parameter(Mandatory = $true)] [BsnRecordingFormat]$Format ) $extension = $Format.ToString().ToLowerInvariant() $videoPath = [System.IO.Path]::ChangeExtension($CastPath, $extension) $aggCommand = Get-Command -Name 'agg' -ErrorAction 'SilentlyContinue' if (-not $aggCommand) { Write-Warning -Message ("Rendering to {0} requires the 'agg' renderer on PATH; the cast file '{1}' is kept as the authoritative recording." -f $Format, $CastPath) return $null } if ($Format -eq [BsnRecordingFormat]::Gif) { & $aggCommand.Source $CastPath $videoPath if (([int]$LASTEXITCODE -ne 0) -or (-not (Test-Path -LiteralPath $videoPath))) { Write-Warning -Message ("agg failed to render '{0}'; the cast file remains the authoritative recording." -f $CastPath) return $null } return $videoPath } $ffmpegCommand = Get-Command -Name 'ffmpeg' -ErrorAction 'SilentlyContinue' if (-not $ffmpegCommand) { Write-Warning -Message ("Rendering to {0} requires 'agg' and 'ffmpeg' on PATH; the cast file '{1}' is kept as the authoritative recording." -f $Format, $CastPath) return $null } $temporaryGifPath = [System.IO.Path]::ChangeExtension([System.IO.Path]::GetTempFileName(), 'gif') try { & $aggCommand.Source $CastPath $temporaryGifPath if (([int]$LASTEXITCODE -ne 0) -or (-not (Test-Path -LiteralPath $temporaryGifPath))) { Write-Warning -Message ("agg failed to render '{0}'; the cast file remains the authoritative recording." -f $CastPath) return $null } & $ffmpegCommand.Source -y -i $temporaryGifPath -pix_fmt 'yuv420p' $videoPath if (([int]$LASTEXITCODE -ne 0) -or (-not (Test-Path -LiteralPath $videoPath))) { Write-Warning -Message ("ffmpeg failed to transcode '{0}'; the cast file remains the authoritative recording." -f $CastPath) return $null } return $videoPath } finally { if (Test-Path -LiteralPath $temporaryGifPath) { Remove-Item -LiteralPath $temporaryGifPath -Force -ErrorAction 'SilentlyContinue' } } } |