From cf81ee6eb8aa748f98393a6f4f8430a07da85fa3 Mon Sep 17 00:00:00 2001 From: patrickenfuego Date: Sun, 21 Aug 2022 17:47:14 -0400 Subject: [PATCH 1/3] Add frame count module variable --- modules/FFTools/FFTools.psd1 | 3 ++- modules/FFTools/FFTools.psm1 | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/FFTools/FFTools.psd1 b/modules/FFTools/FFTools.psd1 index 03f5636..f3a16f7 100644 --- a/modules/FFTools/FFTools.psd1 +++ b/modules/FFTools/FFTools.psd1 @@ -91,7 +91,8 @@ AliasesToExport = 'iffmpeg', 'cropfile', 'cropdim' FileList = 'FFTools.psd1', 'FFTools.psm1', 'Private\Set-AudioPreference.ps1', 'Private\Get-SubtitleStream', 'Private\Set-SubtitlePreference', 'Private\Get-HDRMetadata.ps1', 'Public\Invoke-FFMpeg.ps1', 'Public\Invoke-TwoPassFFMpeg.ps1', 'Public\New-CropFile.ps1', 'Public\Measure-CropDimensions.ps1', 'Public\Invoke-VMAF.ps1', 'Private\ConvertTo-Stereo.ps1', 'Private\Set-PresetParameters.ps1', 'Private\Set-FFMPegArgs.ps1', 'Private\Set-VideoFilter.ps1', 'Private\Set-TestParameters.ps1', - 'Private\Confirm-Parameters.ps1', 'Private\Set-DVArgs.ps1', 'Utils\Invoke-DeeEncoder.ps1','Utils\Confirm-ScaleFilter.ps1','Utils\Write-Report.ps1', + 'Private\Watch-ScriptTerminated.ps1', 'Private\Confirm-Parameters.ps1', 'Private\Set-DVArgs.ps1', + 'Utils\Invoke-DeeEncoder.ps1','Utils\Confirm-ScaleFilter.ps1','Utils\Write-Report.ps1', 'Utils\Invoke-MkvMerge.ps1', 'Utils\Confirm-HDR10Plus.ps1', 'Utils\Confirm-DolbyVision.ps1', 'Utils\Remove-FilePrompt.ps1', 'Utils\Read-Config.ps1' # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. diff --git a/modules/FFTools/FFTools.psm1 b/modules/FFTools/FFTools.psm1 index 6b2a728..5d87b4b 100644 --- a/modules/FFTools/FFTools.psm1 +++ b/modules/FFTools/FFTools.psm1 @@ -66,6 +66,10 @@ $Script:dee = @{ DeeArgs = @('dee_ddp', 'dee_eac3', 'dee_dd', 'dee_ac3', 'dee_thd', 'dee_ddp_51', 'dee_eac3_51') DeeUsed = $false } +# Keep track of frame count for 2-pass encodes +$Script:frame = @{ + FrameCount = 0 +} # Detect operating system info if ($isMacOs) { @@ -133,7 +137,7 @@ ___________ .__ __ .__ ______________________ '@ # Current script release version -[version]$release = '2.2.0' +[version]$release = '2.2.1' #### End module variables #### From bc66ce7b6ea5f0e238f1839eb91013b270369dbc Mon Sep 17 00:00:00 2001 From: patrickenfuego Date: Sun, 21 Aug 2022 17:47:26 -0400 Subject: [PATCH 2/3] Move to dedicated function --- .../Private/Watch-ScriptTerminated.ps1 | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 modules/FFTools/Private/Watch-ScriptTerminated.ps1 diff --git a/modules/FFTools/Private/Watch-ScriptTerminated.ps1 b/modules/FFTools/Private/Watch-ScriptTerminated.ps1 new file mode 100644 index 0000000..3ed6c25 --- /dev/null +++ b/modules/FFTools/Private/Watch-ScriptTerminated.ps1 @@ -0,0 +1,31 @@ +<# + .SYNOPSIS + Watches for CTRL+C interrupts and clean exits all running jobs and scripts +#> +function Watch-ScriptTerminated { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true, Position = 0)] + [string]$Message + ) + + if ($Host.UI.RawUI.KeyAvailable -and ($key = $Host.UI.RawUI.ReadKey("AllowCtrlC,NoEcho,IncludeKeyUp"))) { + # If user hits Ctrl+C + if ([int]$key.Character -eq 3) { + Write-Progress "Terminated" -Completed + Write-Warning "CTRL+C was detected - shutting down all running jobs before exiting the script" + # Clean up all running jobs before exiting + Get-Job | Stop-Job -PassThru | Remove-Job -Force -Confirm:$false + + $psReq ? (Write-Host "$($aRed)$Message$($reset)") : + (Write-Host $Message @errColors) + + $console.WindowTitle = $currentTitle + [console]::TreatControlCAsInput = $false + exit 77 + } + + # Flush the key buffer again for the next loop + $Host.UI.RawUI.FlushInputBuffer() + } +} \ No newline at end of file From 8f3c6bba2138934766185452f685553d78768324 Mon Sep 17 00:00:00 2001 From: patrickenfuego Date: Sun, 21 Aug 2022 17:48:14 -0400 Subject: [PATCH 3/3] Improve error handling for progress Moved Watch-ScriptTerminated to its own function as well --- .../FFTools/Private/Write-EncodeProgress.ps1 | 58 +++++-------- modules/FFTools/Public/Invoke-FFMpeg.ps1 | 86 ++++++++++++++++--- 2 files changed, 96 insertions(+), 48 deletions(-) diff --git a/modules/FFTools/Private/Write-EncodeProgress.ps1 b/modules/FFTools/Private/Write-EncodeProgress.ps1 index eaebae4..77c9dc4 100644 --- a/modules/FFTools/Private/Write-EncodeProgress.ps1 +++ b/modules/FFTools/Private/Write-EncodeProgress.ps1 @@ -31,53 +31,35 @@ function Write-EncodeProgress { [bool]$DolbyVision ) - <# - .SYNOPSIS - Watches for CTRL+C interrupts and clean exits all running jobs and scripts - #> - function Watch-ScriptTerminated ([string]$Message) { - if ($Host.UI.RawUI.KeyAvailable -and ($Key = $Host.UI.RawUI.ReadKey("AllowCtrlC,NoEcho,IncludeKeyUp"))) { - # If user hits Ctrl+C - if ([int]$Key.Character -eq 3) { - Write-Progress "Terminated" -Completed - Write-Warning "CTRL+C was detected - shutting down all running jobs before exiting the script" - # Clean up all running jobs before exiting - Get-Job | Stop-Job -PassThru | Remove-Job -Force -Confirm:$false - - $psReq ? (Write-Host "$($aRed)$Message$($reset)") : - (Write-Host $Message @errColors) - $console.WindowTitle = $currentTitle - [console]::TreatControlCAsInput = $False - exit 77 - } - # Flush the key buffer again for the next loop - $Host.UI.RawUI.FlushInputBuffer() - } - } - # Intercept ctrl+C for graceful shutdown of jobs - [console]::TreatControlCAsInput = $True + [console]::TreatControlCAsInput = $true Start-Sleep -Milliseconds 500 $Host.UI.RawUI.FlushInputBuffer() if ($PSBoundParameters['TestFrames']) { - $frameCount = $TestFrames + $frame['FrameCount'] = $TestFrames } - # Gather total frame count without demuxing - else { - if (!$SecondPass) { - Write-Progress "Gathering frame count for progress display..." - $frameStr = ffmpeg -hide_banner -i $InputFile -map 0:v:0 -c:v copy -f null - 2>&1 - } + elseif (!$SecondPass -or ($frame['FrameCount'] -le 0)) { + Write-Progress "Gathering frame count for progress display..." + $frameStr = ffmpeg -hide_banner -i $InputFile -map 0:v:0 -c:v copy -f null - 2>&1 + # Select-String does not work on this output for some reason? $tmp = $frameStr | Select-Object -Index ($frameStr.Count - 2) - [int]$frameCount = $tmp | + [int]$frame['FrameCount'] = $tmp | Select-String -Pattern '^frame=\s*(\d+)\s.*' | ForEach-Object { $_.Matches.Groups[1].Value } - if (!$frameCount) { - Write-Progress "Could not retrieve frame count" -Completed - return + if (!$frame['FrameCount']) { + Write-Progress "Error" -Completed + $msg = "Failed to parse frame count from string" + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + ([System.ArgumentNullException]$msg), + 'frame', + [System.Management.Automation.ErrorCategory]::InvalidResult, + $frame['FrameCount'] + ) + ) } } @@ -123,9 +105,9 @@ function Write-EncodeProgress { } if ($currentFrame -and $fps) { - $progress = ($currentFrame / $frameCount) * 100 + $progress = ($currentFrame / $frame['FrameCount']) * 100 $status = '{0:N1}% Complete' -f $progress - $activity = "Encoding Frame $currentFrame of $frameCount, $('{0:N2}' -f $fps) FPS" + $activity = "Encoding Frame $currentFrame of $($frame['FrameCount']), $('{0:N2}' -f $fps) FPS" $params = @{ PercentComplete = $progress diff --git a/modules/FFTools/Public/Invoke-FFMpeg.ps1 b/modules/FFTools/Public/Invoke-FFMpeg.ps1 index 3a6887c..e3682b7 100644 --- a/modules/FFTools/Public/Invoke-FFMpeg.ps1 +++ b/modules/FFTools/Public/Invoke-FFMpeg.ps1 @@ -213,7 +213,10 @@ function Invoke-FFMpeg { [switch]$DisableProgress ) - # Writes the banner information during encoding + <# + .SYNOPSIS + Writes the banner information during encoding + #> function Write-Banner { if ($TestFrames) { $startStr = switch -Wildcard ($TestStart) { @@ -247,6 +250,31 @@ function Invoke-FFMpeg { } } + <# + .SYNOPSIS + Wait for encode jobs to finish if progress bar returns an error + .DESCRIPTION + If the progress bar function returns an error, this function will wait for the job + to complete while intercepting CTRL+C and gracefully exiting if needed + #> + function Wait-Completed ($JobName, $Exception) { + Write-Host "ERROR: The progress bar failed to initialize`n" @errColors + Write-Verbose "ERROR: $($Exception.Message)" + Write-Verbose "LINE: $($Exception.InvocationInfo.ScriptLineNumber)" + + # Intercept ctrl+C for graceful shutdown of jobs + [console]::TreatControlCAsInput = $true + Start-Sleep -Milliseconds 500 + $Host.UI.RawUI.FlushInputBuffer() + + while ((Get-Job -Name $JobName).State -ne 'Completed') { + Watch-ScriptTerminated -Message $exitBanner + Start-Sleep -Milliseconds 750 + } + + return + } + # Infer primary language based on streams (for muxing) - NOT always accurate, but pretty close $streams = ffprobe $Paths.InputFile -show_entries stream=index:stream_tags=language ` -select_streams a -v 0 -of compact=p=0:nk=1 @@ -279,7 +307,8 @@ function Invoke-FFMpeg { $HDR = Get-HDRMetadata @params } catch [System.ArgumentNullException] { - Write-Host "`u{203C} Failed to get HDR metadata: $($_.Exception.Message). Metadata will not be copied" @errColors + $err = $_.Exception.Message + Write-Host "`u{203C} Failed to get HDR metadata: $err. Metadata will not be copied" @errColors $HDR = $null } } @@ -397,7 +426,13 @@ function Invoke-FFMpeg { RCLookahead = $RCLookahead } # Set preset based arguments based on user input - $presetParams = Set-PresetParameters -Settings $presetArgs -Preset $Preset -Encoder $Encoder -Verbose:$setVerbose + $params = @{ + Settings = $presetArgs + Preset = $Preset + Encoder = $Encoder + Verbose = $setVerbose + } + $presetParams = Set-PresetParameters @params Write-Verbose "PRESET PARAMETER VALUES:`n$($presetParams | Out-String)`n" <# @@ -558,7 +593,12 @@ function Invoke-FFMpeg { DolbyVision = $dovi Verbose = $setVerbose } - Write-EncodeProgress @params + try { + Write-EncodeProgress @params + } + catch { + Wait-Completed -JobName '1st Pass' -Exception $_.Exception + } } Write-Host @@ -584,7 +624,12 @@ function Invoke-FFMpeg { DolbyVision = $dovi Verbose = $setVerbose } - Write-EncodeProgress @params + try { + Write-EncodeProgress @params + } + catch { + Wait-Completed -JobName '2nd Pass' -Exception $_.Exception + } } } # CRF/One pass x265 encode @@ -625,7 +670,12 @@ function Invoke-FFMpeg { DolbyVision = $dovi Verbose = $setVerbose } - Write-EncodeProgress @params + try { + Write-EncodeProgress @params + } + catch { + Wait-Completed -JobName 'crf' -Exception $_.Exception + } } } # Mux/convert audio and subtitle streams separately from elementary hevc stream @@ -727,7 +777,12 @@ function Invoke-FFMpeg { Verbose = $setVerbose DolbyVision = $dovi } - Write-EncodeProgress @params + try { + Write-EncodeProgress @params + } + catch { + Wait-Completed -JobName 'ffmpeg 1st Pass' -Exception $_.Exception + } } } @@ -753,7 +808,12 @@ function Invoke-FFMpeg { Verbose = $setVerbose DolbyVision = $dovi } - Write-EncodeProgress @params + try { + Write-EncodeProgress @params + } + catch { + Wait-Completed -JobName 'ffmpeg 2nd Pass' -Exception $_.Exception + } } } # CRF encode @@ -792,8 +852,9 @@ function Invoke-FFMpeg { } # Should be unreachable. Throw error and exit script if rate control cannot be detected else { + $msg = 'Rate control method could not be determined from input parameters' $params = @{ - Exception = [System.FieldAccessException]::new('Rate control method could not be determined from input parameters') + Exception = [System.FieldAccessException]::new($msg) Category = 'InvalidResult' TargetObject = $RateControl ErrorId = 101 @@ -821,7 +882,12 @@ function Invoke-FFMpeg { DolbyVision = $dovi Verbose = $setVerbose } - Write-EncodeProgress @params + try { + Write-EncodeProgress @params + } + catch { + Wait-Completed -JobName 'ffmpeg' -Exception $_.Exception + } } # Remove ffmpeg jobs