From 7c830dcf94a06e5280f27585e407d1913b8b64b9 Mon Sep 17 00:00:00 2001 From: patrickenfuego Date: Wed, 27 Jul 2022 22:16:34 -0400 Subject: [PATCH 01/10] bump revision --- bin/windows/dee_wrapper/config.toml | 1 + modules/FFTools/FFTools.psm1 | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/windows/dee_wrapper/config.toml b/bin/windows/dee_wrapper/config.toml index 836247d..b043f5e 100644 --- a/bin/windows/dee_wrapper/config.toml +++ b/bin/windows/dee_wrapper/config.toml @@ -54,5 +54,6 @@ threads = 6 # You can overwrite this with -t/--threads. The threads number will + diff --git a/modules/FFTools/FFTools.psm1 b/modules/FFTools/FFTools.psm1 index c65cda0..ea32db1 100644 --- a/modules/FFTools/FFTools.psm1 +++ b/modules/FFTools/FFTools.psm1 @@ -133,7 +133,7 @@ ___________ .__ __ .__ ______________________ '@ # Current script release version -[version]$release = '2.0.3' +[version]$release = '2.1.0' #### End module variables #### From df1a1d9ae97a3c4e09f9fc79dc113fd18f5c6da1 Mon Sep 17 00:00:00 2001 From: patrickenfuego Date: Wed, 27 Jul 2022 22:18:50 -0400 Subject: [PATCH 02/10] add unsharp & vapoursynth support --- modules/FFTools/Private/Set-DVArgs.ps1 | 94 ++++++--- modules/FFTools/Private/Set-FFMpegArgs.ps1 | 59 ++++-- modules/FFTools/Private/Set-VideoFilter.ps1 | 160 +++++++++++---- modules/FFTools/Public/Invoke-FFMpeg.ps1 | 217 ++++++++++---------- modules/FFTools/Utils/Read-TimedInput.ps1 | 6 +- 5 files changed, 338 insertions(+), 198 deletions(-) diff --git a/modules/FFTools/Private/Set-DVArgs.ps1 b/modules/FFTools/Private/Set-DVArgs.ps1 index e70e2e7..5b4712b 100644 --- a/modules/FFTools/Private/Set-DVArgs.ps1 +++ b/modules/FFTools/Private/Set-DVArgs.ps1 @@ -79,6 +79,10 @@ function Set-DVArgs { [Parameter(Mandatory = $false)] [hashtable]$NLMeans, + # Sharpen/blur filter + [Parameter(Mandatory = $false)] + [hashtable]$Unsharp, + # Number of frame threads the encoder should use [Parameter(Mandatory = $false)] [int]$Threads, @@ -267,11 +271,21 @@ function Set-DVArgs { <# SET BASE ARGUMENT ARRAYS + vapoursynth vspipe array (if used) ffmpeg base array for video ffmpeg base array for audio/subs x265 base array #> + if (![string]::IsNullOrEmpty($Paths.VPY)) { + $vapoursynthBaseArray = [ArrayList]@( + 'vspipe' + '--y4m' + "`"$($Paths.VPY)`"" + '- ' + ) + } + $ffmpegBaseVideoArray = [ArrayList]@( '-i' $inputPath @@ -348,8 +362,6 @@ function Set-DVArgs { "$($PresetParams.RCLookahead)" '--aq-strength' $AqStrength - '--min-keyint' - 24 '--psy-rd' $PSBoundParameters['PsyRd'] ? $PsyRd : '2.00' '--tu-intra-depth' @@ -373,6 +385,10 @@ function Set-DVArgs { '-F' $Threads } + if (![string]::IsNullOrEmpty($Paths.VPY) -and $TestFrames) { + '--frames' + $TestFrames + } ) <# @@ -382,22 +398,25 @@ function Set-DVArgs { Set situational arguments based on parameters #> - # Set video specific filter arguments - $vfHash = @{ - CropDimensions = $CropDimensions - Scale = $Scale - FFMpegExtra = $FFMpegExtra - Deinterlace = $Deinterlace - Verbose = $setVerbose - } - try { - $vfArray = Set-VideoFilter @vfHash - } - catch { - Write-Error "Video filter exception: $($_.Exception)" -ErrorAction Stop - } + # Set video specific filter arguments unless VS is used + if ([string]::IsNullOrEmpty($Paths.VPY)) { + $vfHash = @{ + CropDimensions = $CropDimensions + Scale = $Scale + FFMpegExtra = $FFMpegExtra + Deinterlace = $Deinterlace + Unsharp = $Unsharp + Verbose = $setVerbose + } + try { + $vfArray = Set-VideoFilter @vfHash + } + catch { + Write-Error "Video filter exception: $($_.Exception)" -ErrorAction Stop + } - if ($vfArray) { $ffmpegBaseVideoArray.AddRange($vfArray) } + if ($vfArray) { $ffmpegBaseVideoArray.AddRange($vfArray) } + } # Set test frames if passed. Insert start time before input if ($PSBoundParameters['TestFrames']) { @@ -412,8 +431,12 @@ function Set-DVArgs { } elseif (!$PSBoundParameters['TestFrames'] -and $ffmpegExtraArray -contains '-ss') { $i = $ffmpegExtraArray.IndexOf('-ss') - $ffmpegBaseVideoArray.InsertRange($ffmpegBaseVideoArray.IndexOf('-i'), @($ffmpegExtraArray[$i], $ffmpegExtraArray[$i + 1])) - $ffmpegOtherArray.InsertRange($ffmpegOtherArray.IndexOf('-i'), @($ffmpegExtraArray[$i], $ffmpegExtraArray[$i + 1])) + $ffmpegBaseVideoArray.InsertRange( + $ffmpegBaseVideoArray.IndexOf('-i'), @($ffmpegExtraArray[$i], $ffmpegExtraArray[$i + 1]) + ) + $ffmpegOtherArray.InsertRange( + $ffmpegOtherArray.IndexOf('-i'), @($ffmpegExtraArray[$i], $ffmpegExtraArray[$i + 1]) + ) $ffmpegExtraArray.RemoveRange($i, 2) } @@ -439,12 +462,12 @@ function Set-DVArgs { ($PresetParams.BIntra -eq 1) ? - ($x265BaseArray.Add('--b-intra') > $null) : - ($x265BaseArray.Add('--no-b-intra') > $null) + ($x265BaseArray.Add('--b-intra') > $null) : + ($x265BaseArray.Add('--no-b-intra') > $null) ($IntraSmoothing -eq 0) ? - ($x265BaseArray.Add('--no-strong-intra-smoothing') > $null) : - ($x265BaseArray.Add('--strong-intra-smoothing') > $null) + ($x265BaseArray.Add('--no-strong-intra-smoothing') > $null) : + ($x265BaseArray.Add('--strong-intra-smoothing') > $null) <# SET RATE CONTROL @@ -466,8 +489,11 @@ function Set-DVArgs { $x265BaseArray.AddRange(@('--bitrate', $val)) } - Write-Verbose "FFMPEG VIDEO ARGS ARE: `n $($ffmpegBaseVideoArray -join " ")`n" - Write-Verbose "FFMPEG SUB/AUDIO ARGS ARE: `n $($ffmpegOtherArray -join " ")`n" + ($null -ne $Paths.VPY) ? + (Write-Verbose "VAPOURSYNTH ARGS ARE :`n $($vapoursynthBaseArray -join " ")`n") : + (Write-Verbose "FFMPEG VIDEO ARGS ARE:`n $($ffmpegBaseVideoArray -join " ")`n") + + Write-Verbose "FFMPEG SUB/AUDIO ARGS ARE:`n $($ffmpegOtherArray -join " ")`n" <# TWO PASS SETTINGS @@ -522,13 +548,14 @@ function Set-DVArgs { $x265FirstPassArray.AddRange(@('--stats', "`"$($Paths.X265Log)`"", '--pass', 1)) $x265SecondPassArray = $x265BaseArray.Clone() - $x265SecondPassArray.AddRange(@('--stats', "`"$($Paths.X265Log)`"", '--pass', 2, '--subme', "$($PresetParams.Subme)")) + $x265SecondPassArray.AddRange( + @('--stats', "`"$($Paths.X265Log)`"", '--pass', 2, '--subme', "$($PresetParams.Subme)") + ) Write-Verbose "DV FIRST PASS ARRAY IS:`n $($x265FirstPassArray -join " ")`n" Write-Verbose "DV SECOND PASS ARRAY IS:`n $($x265SecondPassArray -join " ")`n" $dvHash = @{ - FFMpegVideo = $ffmpegBaseVideoArray FFMpegOther = $ffmpegOtherArray x265Args1 = $x265FirstPassArray x265Args2 = $x265SecondPassArray @@ -541,12 +568,21 @@ function Set-DVArgs { Write-Verbose "x265 ARRAY IS:`n $($x265BaseArray -join " ")`n" $dvHash = @{ - FFMpegVideo = $ffmpegBaseVideoArray FFMpegOther = $ffmpegOtherArray x265Args1 = $x265BaseArray x265Args2 = $null } } + if (![string]::IsNullOrEmpty($Paths.VPY)) { + $dvHash['Vapoursynth'] = $vapoursynthBaseArray + $dvHash['FFMpegVideo'] = $null + } + else { + $dvHash['FFMpegVideo'] = $ffmpegBaseVideoArray + $dvHash['Vapoursynth'] = $null + } + return $dvHash -} \ No newline at end of file +} + diff --git a/modules/FFTools/Private/Set-FFMpegArgs.ps1 b/modules/FFTools/Private/Set-FFMpegArgs.ps1 index 1c30bfc..388bb49 100644 --- a/modules/FFTools/Private/Set-FFMpegArgs.ps1 +++ b/modules/FFTools/Private/Set-FFMpegArgs.ps1 @@ -82,6 +82,10 @@ function Set-FFMpegArgs { [Parameter(Mandatory = $false)] [hashtable]$NLMeans, + # Sharpen/blur filter + [Parameter(Mandatory = $false)] + [hashtable]$Unsharp, + # Number of frame threads the encoder should use [Parameter(Mandatory = $false)] [int]$Threads, @@ -153,7 +157,6 @@ function Set-FFMpegArgs { if ($PSBoundParameters['EncoderExtra']) { $encoderExtraArray = [ArrayList]@() foreach ($arg in $EncoderExtra.GetEnumerator()) { - if ($arg.Name -eq 'sao') { $skip.Sao = $true } elseif ($arg.Name -eq 'open-gop') { $skip.OpenGOP = $true } elseif ($arg.Name -eq 'keyint') { $skip.Keyint = $true } elseif ($arg.Name -eq 'min-keyint') { $skip.MinKeyint = $true } @@ -169,8 +172,16 @@ function Set-FFMpegArgs { [ArrayList]$ffmpegArgsAL = @( '-probesize' '100MB' - '-i' - "`"$($Paths.InputFile)`"" + if (![string]::IsNullOrEmpty($Paths.VPY)) { + '-f' + 'vapoursynth' + '-i' + "`"$($Paths.VPY)`"" + } + else { + '-i' + "`"$($Paths.InputFile)`"" + } if ($TrackTitle['VideoTitle']) { '-metadata:s:v:0' "title=$($TrackTitle['VideoTitle'])" @@ -284,7 +295,9 @@ function Set-FFMpegArgs { # If TestFrames is not used but a start code is passed elseif (!$PSBoundParameters['TestFrames'] -and $ffmpegExtraArray -contains '-ss') { $i = $ffmpegExtraArray.IndexOf('-ss') - $ffmpegArgsAL.InsertRange($ffmpegArgsAL.IndexOf('-i'), @($ffmpegExtraArray[$i], $ffmpegExtraArray[$i + 1])) + $ffmpegArgsAL.InsertRange( + $ffmpegArgsAL.IndexOf('-i'), @($ffmpegExtraArray[$i], $ffmpegExtraArray[$i + 1]) + ) $ffmpegExtraArray.RemoveRange($i, 2) } @@ -295,24 +308,26 @@ function Set-FFMpegArgs { { $skip.MinKeyInt -eq $false } { $encoderBaseArray.Add('min-keyint=24') > $null } } - # Set video specific filter arguments - - $vfHash = @{ - CropDimensions = $CropDimensions - Scale = $Scale - FFMpegExtra = $FFMpegExtra - Deinterlace = $Deinterlace - Verbose = $setVerbose - NLMeans = $NLMeans - } - try { - $vfArray = Set-VideoFilter @vfHash - } - catch { - Write-Error "Video filter exception: $($_.Exception)" -ErrorAction Stop - } + # Set video specific filter arguments unless VS is used + if ([string]::IsNullOrEmpty($Paths.VPY)) { + $vfHash = @{ + CropDimensions = $CropDimensions + Scale = $Scale + FFMpegExtra = $FFMpegExtra + Deinterlace = $Deinterlace + NLMeans = $NLMeans + Unsharp = $Unsharp + Verbose = $setVerbose + } + try { + $vfArray = Set-VideoFilter @vfHash + } + catch { + Write-Error "Video filter exception: $($_.Exception)" -ErrorAction Stop + } - if ($vfArray) { $ffmpegArgsAL.AddRange($vfArray) } + if ($vfArray) { $ffmpegArgsAL.AddRange($vfArray) } + } # Set res and bit depth related arguments for encoders @@ -349,7 +364,7 @@ function Set-FFMpegArgs { # Set ffmpeg extra arguments if passed if ($ffmpegExtraArray) { Write-Verbose "FFMPEG EXTRA ARGS ARE: `n $($ffmpegExtraArray -join ' ')`n" - Write-Verbose "NOTE: If -ss was passed, it was moved before the file input and deleted from the above array" + Write-Verbose "NOTE: If -ss was passed, it was moved before the file input and deleted from the array" $ffmpegArgsAL.AddRange($ffmpegExtraArray) } # Add extra encoder arguments if passed diff --git a/modules/FFTools/Private/Set-VideoFilter.ps1 b/modules/FFTools/Private/Set-VideoFilter.ps1 index c270ce3..3212ba7 100644 --- a/modules/FFTools/Private/Set-VideoFilter.ps1 +++ b/modules/FFTools/Private/Set-VideoFilter.ps1 @@ -15,6 +15,9 @@ function Set-VideoFilter { [Parameter(Mandatory = $false, Position = 1)] [hashtable]$Scale, + + [Parameter(Mandatory = $false)] + [hashtable]$Unsharp, [Parameter(Mandatory = $false, Position = 2)] [array]$FFMpegExtra, @@ -26,7 +29,10 @@ function Set-VideoFilter { [hashtable]$NLMeans ) + # Initialize safe defaults [array]$vfArray = $null + $nlStr = $null + $unsharpStr = $null # Verify NLMeans if passed or use defaults if ($PSBoundParameters['NLMeans']) { @@ -49,27 +55,124 @@ function Set-VideoFilter { $nlStr = "nlmeans=$($NLMeans['s']):$($NLMeans['p']):$($NLMeans['pc']):$($NLMeans['r']):$($NLMeans['rc'])" } - # if manual crop dimensions are passed, parse them out + # Set size & strength of unsharp filter if presets are used + if ($PSBoundParameters['Unsharp'] -and $Unsharp) { + if ($Unsharp.Size -notlike 'custom=*') { + $size, $type = switch ($Unsharp.Size) { + 'luma_small' { 'lx=3:ly=3', 'luma' } + 'luma_medium' { 'lx=5:ly=5', 'luma' } + 'luma_large' { 'lx=7:ly=7', 'luma' } + 'chroma_small' { 'cx=3:cy=3', 'chroma' } + 'chroma_medium' { 'cx=5:cy=5', 'chroma' } + 'chroma_large' { 'cx=7:cy=7', 'chroma' } + 'yuv_small' { 'lx=3:ly=3:cx=3:cy=3', 'yuv' } + 'yuv_medium' { 'lx=5:ly=5:cx=5:cy=5', 'yuv' } + 'yuv_large' { 'lx=7:ly=7:cx=7:cy=7', 'yuv' } + default { + Write-Error "Unknown unsharp size argument. This should be unreachable" -ErrorAction Stop + } + } + # Set strength if preset is used + $strength = switch ($Unsharp.Strength) { + 'sharpen_mild' { + switch ($type) { + 'luma' { 'la=1.0' } + 'chroma' { 'ca=1.0' } + 'yuv' { 'la=1.0:ca=1.0'} + } + } + 'sharpen_medium' { + switch ($type) { + 'luma' { 'la=1.5' } + 'chroma' { 'ca=1.5' } + 'yuv' { 'la=1.5:ca=1.5'} + } + } + 'sharpen_strong' { + switch ($type) { + 'luma' { 'la=2.0' } + 'chroma' { 'ca=2.0' } + 'yuv' { 'la=2.0:ca=2.0'} + } + } + 'blur_mild' { + switch ($type) { + 'luma' { 'la=-1.0' } + 'chroma' { 'ca=-1.0' } + 'yuv' { 'la=-1.0:ca=-1.0'} + } + } + 'blur_medium' { + switch ($type) { + 'luma' { 'la=-1.5' } + 'chroma' { 'ca=-1.5' } + 'yuv' { 'la=-1.5:ca=-1.5'} + } + } + 'blur_strong' { + switch ($type) { + 'luma' { 'la=-2.0' } + 'chroma' { 'ca=-2.0' } + 'yuv' { 'la=-2.0:ca=-2.0'} + } + } + default { + Write-Error "Unknown Unsharp strength or type, filter will be skipped. This should be unreachable" + $null + } + } + + [string]$unsharpStr = $strength ? ("unsharp=$size`:$strength") : $null + } + # User passed a custom unsharp string + elseif ($Unsharp.Size -like 'custom=*') { + if ($Unsharp.Size -like 'unsharp=*') { + [string]$unsharpStr = ($Unsharp.Size.Replace('custom=', '')).Trim() + } + else { + [string]$unsharpStr = ($Unsharp.Size.Replace('custom=', 'unsharp=')).Trim() + } + } + # Failsafe + else { + Write-Error "Unknown argument for unsharp, filter will be skipped. This should be unreachable" + $unsharpStr = $null + } + } + + # If manual crop dimensions are passed, parse them out if ($CropDimensions -contains -1) { - [string]$cropStr = $FFMpegExtra.Where({ $_['-vf'] -match 'crop' }) | + [string]$vfStr = $FFMpegExtra.Where({ $_['-vf'] }, 'SkipUntil', 1) | Select-Object -ExpandProperty '-vf' - if ($cropStr -match "crop=w?=?(?\d{3,4}):h?=?(?\d{3,4})") { - $width, $height = $Matches.width, $Matches.height - $CropDimensions = @($width, $height) + $splitVf = ($vfStr -split ',').Trim() + $cropStr = $splitVf.Where({ $_ -like 'crop*' }) + + $match = [regex]::Matches($cropStr, "crop=w?=?(?\d{3,4}):h?=?(?\d{3,4})") + # Perform regex match to get crop dimensions + if ($match) { + $CropDimensions = @($match.Groups[1].Value, $match.Groups[2].Value) + + # Remove crop args from the vf string and save the rest + $manVfString = $splitVf.Where({ $_ -notlike 'crop*' }) -join ',' } else { - $msg = "Error parsing crop parameter regex in FFMpegExtra" + $msg = "Error parsing crop parameters from video filter string in FFMpegExtra" $params = @{ Exception = [System.ArgumentException]::new($msg) RecommendedAction = 'Verify crop parameters' Category = 'InvalidArgument' CategoryActivity = 'Parsing crop values for scaling' - TargetObject = $cropStr + TargetObject = $vfStr ErrorId = 80 } - Write-Error @params -ErrorAction Stop + Write-Error @params -ErrorAction Stop } - } + } + else { + # Parse out manual video filter if present + $manVfString = $FFMpegExtra.Where({ $_['-vf'] }) | + Select-Object -ExpandProperty '-vf' + } # Setup scaling related variables if ($PSBoundParameters['Scale']) { @@ -90,8 +193,8 @@ function Set-VideoFilter { elseif ($CropDimensions[0] -gt 1300 -and $CropDimensions[0] -lt 3000) { # scale up/down 1080p $widthRes = ($CropDimensions[1] -lt $scaleRes) ? - ($CropDimensions[0] * 2) : - ($CropDimensions[0] / 1.5) + ($CropDimensions[0] * 2) : + ($CropDimensions[0] / 1.5) } # scaling up from 720p elseif ($CropDimensions[0] -lt 1300) { @@ -110,38 +213,22 @@ function Set-VideoFilter { TargetObject = $Scale ErrorId = 81 } - Write-Error @params -ErrorAction Stop - } - } - - # If array contains -1, manual crop params were set via FFMpegExtra parameter - $customCrop = $false - if ($CropDimensions -contains -1) { - $customCrop = $true - $manVfString = $null - foreach ($i in $FFMpegExtra) { - if ($i -is [hashtable]) { - foreach ($j in $i.GetEnumerator()) { - if ($j.Name -eq '-vf') { - [string]$manVfString = $j.Value - break - } - } - } + Write-Error @params -ErrorAction Stop } } # Build argument array and join - [array]$tmpArray = @( + $tmpArray = @( + "crop=w=$($CropDimensions[0]):h=$($CropDimensions[1])" if ($Deinterlace) { - "yadif" - } - if (!$customCrop) { - "crop=w=$($CropDimensions[0]):h=$($CropDimensions[1])" + 'yadif' } if ($PSBoundParameters['Scale']) { "$sType=w=$widthRes`:h=-2:$set=$($Scale.ScaleFilter)" } + if ($unsharpStr) { + $unsharpStr + } if ($manVfString) { $manVfString } @@ -152,10 +239,11 @@ function Set-VideoFilter { # If string is not empty, generate array if ($tmpArray) { - $vfString = $tmpArray -join "," + $vfString = $tmpArray -join ',' $vfArray = @('-vf', "`"$vfString`"") } + else { $vfArray = $null } - Write-Verbose "VIDEO FILTER ARRAY:`n$($vfArray -join ' ')`n" + Write-Verbose "VIDEO FILTER ARRAY:`n $($vfArray -join ' ')`n" return $vfArray } diff --git a/modules/FFTools/Public/Invoke-FFMpeg.ps1 b/modules/FFTools/Public/Invoke-FFMpeg.ps1 index c558046..18b4ebc 100644 --- a/modules/FFTools/Public/Invoke-FFMpeg.ps1 +++ b/modules/FFTools/Public/Invoke-FFMpeg.ps1 @@ -30,75 +30,82 @@ function Invoke-FFMpeg { # Crop dimensions for the output file [Parameter(Mandatory = $true)] - [Alias("Crop", "CropDim")] + [Alias('Crop', 'CropDim')] [int[]]$CropDimensions, # Audio preference for the output file [Parameter(Mandatory = $false)] - [Alias("Audio", "A")] + [Alias('Audio', 'A')] [array]$AudioInput, # Subtitle option [Parameter(Mandatory = $false)] - [Alias("S")] + [Alias('S')] [string]$Subtitles, # x265 preset setting [Parameter(Mandatory = $false)] - [Alias("P")] + [Alias('P')] [string]$Preset, # x265 CRF / 1 pass ABR array of arguments [Parameter(Mandatory = $true)] + [Alias('RC')] [array]$RateControl, # Deblock filter setting [Parameter(Mandatory = $false)] - [Alias("DBF")] + [Alias('DBF')] [int[]]$Deblock, # aq-mode setting. Default is 2 [Parameter(Mandatory = $false)] - [Alias("AQM")] + [Alias('AQM')] [int]$AqMode, # aq-strength. Higher values equate to a lower QP, but can also increase bitrate significantly [Parameter(Mandatory = $false)] - [Alias("AQS")] + [Alias('AQS')] [double]$AqStrength, # psy-rd. Psycho visual setting [Parameter(Mandatory = $false)] + [Alias('PsyRDO')] [string]$PsyRd, # psy-rdoq (trellis). Psycho visual setting [Parameter(Mandatory = $false)] - [Alias("PRDQ")] + [Alias('PRDQ', 'PsyTrellis')] [double]$PsyRdoq, # Filter to help reduce high frequency noise (grain) [Parameter(Mandatory = $false)] - [Alias("NR")] + [Alias('NR')] [int[]]$NoiseReduction, # Powerful denoising filter [Parameter(Mandatory = $false)] - [Alias("NL")] + [Alias('NL')] [hashtable]$NLMeans, + # Sharpen/blur filter + [Parameter(Mandatory = $false)] + [Alias('U')] + [hashtable]$Unsharp, + # Transform unit recursion depth (intra, inter) [Parameter(Mandatory = $false)] - [Alias("TU")] + [Alias('TU')] [int[]]$TuDepth, # Early exit setting for TU recursion depth [Parameter(Mandatory = $false)] - [Alias("LTU")] + [Alias('LTU')] [int]$LimitTu, # Adjusts the quantizer curve compression factor [Parameter(Mandatory = $false)] - [Alias("Q")] + [Alias('Q')] [double]$QComp, # The number of reference frames to use @@ -107,93 +114,101 @@ function Invoke-FFMpeg { # Enable or disable (CU | MB)Tree algorithm [Parameter(Mandatory = $false)] - [Alias("MBTree", "CUTree")] + [Alias('MBTree', 'CUTree')] [int]$Tree, # Motion Estimation range [Parameter(Mandatory = $false)] + [Alias('MR')] [int]$Merange, # Maximum number of consecutive b-frames [Parameter(Mandatory = $false)] + [Alias('B')] [int]$BFrames, # Enables the evaluation of intra modes in B slices [Parameter(Mandatory = $false)] + [Alias('BINT')] [int]$BIntra, # Subpel motion refinement [Parameter(Mandatory = $false)] + [Alias('Subpel')] [int]$Subme, # Enable/disable strong-intra-smoothing [Parameter(Mandatory = $false)] - [Alias("SIS")] + [Alias('SIS')] [int]$IntraSmoothing, # Number of frame threads the encoder should use [Parameter(Mandatory = $false)] + [Alias('FrameThreads')] [int]$Threads, # Rate control lookahead buffer [Parameter(Mandatory = $false)] - [Alias("RCL", "Lookahead")] + [Alias('RCL', 'Lookahead')] [int]$RCLookahead, # Encoder level to use. Default is unset [Parameter(Mandatory = $false)] + [Alias('L')] [string]$Level, # Encoder level to use. Default is unset [Parameter(Mandatory = $false)] + [Alias('VideoBuffer')] [int[]]$VBV, # Additional ffmpeg options [Parameter(Mandatory = $false)] + [Alias('FE', 'FFExtra')] [array]$FFMpegExtra, # Additional encoder-specific options [Parameter(Mandatory = $false)] + [Alias('Extra')] [hashtable]$EncoderExtra, # Path to the log file [Parameter(Mandatory = $true)] - [Alias("L")] [hashtable]$Paths, # Scale setting [Parameter(Mandatory = $false)] - [Alias("Resize", "DS")] + [Alias('Resize')] [hashtable]$Scale, # Switch to enable a test run [Parameter(Mandatory = $false)] - [Alias("T")] + #[Alias('T')] [int]$TestFrames, # Starting Point for test encodes. Integers are treated as a frame # [Parameter(Mandatory = $false)] - [Alias("Start", "TS")] + [Alias('Test')] [string]$TestStart, # Deinterlacing [Parameter(Mandatory = $false)] - [Alias("DI")] + [Alias('DI')] [switch]$Deinterlace, # Skip DV even if present [Parameter(Mandatory = $false)] - [Alias("NoDV", "SDV")] + [Alias('NoDV', 'SkipDV')] [switch]$SkipDolbyVision, # Skip HDR10+ even if present [Parameter(Mandatory = $false)] - [Alias("NoD10P", "STP")] + [Alias('No10P', 'Skip10P')] [switch]$SkipHDR10Plus, # Skip HDR10+ even if present [Parameter(Mandatory = $false)] - [Alias("NoProgressBar")] + [Alias('NoProgressBar')] [switch]$DisableProgress ) @@ -232,9 +247,11 @@ function Invoke-FFMpeg { } # 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 - [string]$lang = $streams -replace '\d\|', '' | Group-Object | Sort-Object -Property Count -Descending | - Select-Object -First 1 -ExpandProperty Name + $streams = ffprobe $Paths.InputFile -show_entries stream=index:stream_tags=language ` + -select_streams a -v 0 -of compact=p=0:nk=1 + [string]$lang = $streams -replace '\d\|', '' | Group-Object | + Sort-Object -Property Count -Descending | + Select-Object -First 1 -ExpandProperty Name $Paths.Language = $lang <# @@ -403,6 +420,7 @@ function Invoke-FFMpeg { AqStrength = $AqStrength NoiseReduction = $NoiseReduction NLMeans = $NLMeans + Unsharp = $Unsharp TuDepth = $TuDepth LimitTu = $LimitTu Tree = $Tree @@ -439,11 +457,7 @@ function Invoke-FFMpeg { # Pull the x265 name from PATH to account for any mods. Selects the first result $x265 = Get-Command 'x265*' | Select-Object -First 1 -ExpandProperty Source | Split-Path -LeafBase - Write-Verbose "x265 executable: $x265`n" - - if (!$DisableProgress) { - $threadArgs = @($dvArgs, $Paths.HevcPath, $Paths.LogPath, $x265) - } + Write-Verbose "x265 EXECUTABLE NAME: $x265`n" # Two pass x265 encode if ($null -ne $dvArgs.x265Args2) { @@ -453,38 +467,44 @@ function Invoke-FFMpeg { Write-Host "Generating 1st pass encoder metrics...`n" Write-Banner - if ($IsLinux -or $IsMacOS) { - if ($DisableProgress) { - bash -c "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | x265 $($dvArgs.x265Args1) -o $($Paths.hevcPath)" 2>$Paths.LogPath - } - else { - Start-ThreadJob -Name 'ffmpeg 1st Pass' -ArgumentList $threadArgs -ScriptBlock { - param ($dvArgs, $out, $log, $x265) - - $out = [regex]::Escape($out) - bash -c "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args1) -o $out" 2>$log - } | Out-Null - } + # If VS script was passed + if (![string]::IsNullOrEmpty($Paths.VPY)) { + $shellArgsPass1 = ($IsLinux -or $IsMacOS) ? + ('bash', '-c', "$($dvArgs.Vapoursynth) | $x265 $($dvArgs.x265Args1) -o $($Paths.hevcPath)") : + ('cmd.exe', '/c', "$($dvArgs.Vapoursynth) | $x265 $($dvArgs.x265Args1) -o `"$($Paths.hevcPath)`"") + + $shellArgsPass2 = ($IsLinux -or $IsMacOS) ? + ('bash', '-c', "$($dvArgs.Vapoursynth) | $x265 $($dvArgs.x265Args2) -o $($Paths.hevcPath)") : + ('cmd.exe', '/c', "$($dvArgs.Vapoursynth) | $x265 $($dvArgs.x265Args2) -o `"$($Paths.hevcPath)`"") } else { - if ($DisableProgress) { - cmd.exe /c "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args1) -o `"$($Paths.hevcPath)`"" 2>$Paths.LogPath - } - else { - Start-ThreadJob -Name 'ffmpeg 1st Pass' -ArgumentList $threadArgs -ScriptBlock { - param ($dvArgs, $out, $log, $x265) - - cmd.exe /c "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args1) -o `"$out`"" 2>$log - } | Out-Null - } + $shellArgsPass1 = ($IsLinux -or $IsMacOS) ? + ('bash', '-c', "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args1) -o $($Paths.hevcPath)") : + ('cmd.exe', '/c', "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args1) -o `"$($Paths.hevcPath)`"") + + $shellArgsPass2 = ($IsLinux -or $IsMacOS) ? + ('bash', '-c', "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args2) -o $($Paths.hevcPath)") : + ('cmd.exe', '/c', "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args2) -o `"$($Paths.hevcPath)`"") } - if (!$DisableProgress) { + Write-Verbose "FULL CLI PASS 1:`n $($shellArgsPass1 -join ' ')`n" + Write-Verbose "FULL CLI PASS 2:`n $($shellArgsPass2 -join ' ')`n" + + if ($DisableProgress) { + & $shellArgsPass1[0] $shellArgsPass1[1] $shellArgsPass1[2] 2>Paths.$LogPath + } + else { + Start-ThreadJob -Name '1st Pass' -ArgumentList $shellArgsPass1, $Paths.LogPath -ScriptBlock { + param ([array]$ShellArgs, [string]$LogPath) + + & $ShellArgs[0] $ShellArgs[1] $ShellArgs[2] 2>$LogPath + } | Out-Null + $params = @{ InputFile = $Paths.InputFile LogPath = $Paths.LogPath TestFrames = $TestFrames - JobName = 'ffmpeg 1st Pass' + JobName = '1st Pass' SecondPass = $false DolbyVision = $dovi Verbose = $setVerbose @@ -495,39 +515,22 @@ function Invoke-FFMpeg { Write-Host Write-Host "$boldOn$("`u{25c7}" * 4) STARTING x265 PIPE PASS 2 $("`u{25c7}" * 4)$boldOff" @progressColors Write-Banner - - if ($IsLinux -or $IsMacOS) { - if ($DisableProgress) { - bash -c "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args2) -o $($Paths.HevcPath)" 2>>$Paths.LogPath - } - else { - Start-ThreadJob -Name 'ffmpeg 2nd Pass' -ArgumentList $threadArgs -ScriptBlock { - param ($dvArgs, $out, $log, $x265) - - $out = [regex]::Escape($out) - bash -c "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args2) -o $out" 2>>$log - } | Out-Null - } + + if ($DisableProgress) { + & $shellArgsPass2[0] $shellArgsPass2[1] $shellArgsPass2[2] 2>Paths.$LogPath } else { - if ($DisableProgress) { - cmd.exe /c "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args2) -o `"$($Paths.hevcPath)`"" 2>>$Paths.LogPath - } - else { - Start-ThreadJob -Name 'ffmpeg 2nd Pass' -ArgumentList $threadArgs -ScriptBlock { - param ($dvArgs, $out, $log, $x265) - - cmd.exe /c "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args2) -o `"$out`"" 2>>$log - } | Out-Null - } - } + Start-ThreadJob -Name '2nd Pass' -ArgumentList $shellArgsPass2, $Paths.LogPath -ScriptBlock { + param ([array]$ShellArgs, [string]$LogPath) + + & $ShellArgs[0] $ShellArgs[1] $ShellArgs[2] 2>$LogPath + } | Out-Null - if (!$DisableProgress) { $params = @{ InputFile = $Paths.InputFile LogPath = $Paths.LogPath TestFrames = $TestFrames - JobName = 'ffmpeg 2nd Pass' + JobName = '2nd Pass' SecondPass = $true DolbyVision = $dovi Verbose = $setVerbose @@ -541,38 +544,34 @@ function Invoke-FFMpeg { Write-Host "$boldOn$("`u{25c7}" * 4) STARTING x265 PIPE $("`u{25c7}" * 4)$boldOff" @progressColors Write-Banner - if ($IsLinux -or $IsMacOS) { - if ($DisableProgress) { - bash -c "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args1) -o $($Paths.HevcPath)" 2>$Paths.LogPath - } - else { - Start-ThreadJob -Name ffmpeg -ArgumentList $threadArgs -ScriptBlock { - param ($dvArgs, $out, $log, $x265) - - $out = [regex]::Escape($out) - bash -c "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args1) -o $out" 2>$log - } | Out-Null - } + if (![string]::IsNullOrEmpty($Paths.VPY)) { + $shellArgs = ($IsLinux -or $IsMacOS) ? + ('bash', '-c', "$($dvArgs.Vapoursynth) | $x265 $($dvArgs.x265Args1) -o $($Paths.hevcPath)") : + ('cmd.exe', '/c', "$($dvArgs.Vapoursynth) | $x265 $($dvArgs.x265Args1) -o `"$($Paths.hevcPath)`"") } else { - if ($DisableProgress) { - cmd.exe /c "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args1) -o `"$($Paths.HevcPath)`"" 2>$Paths.LogPath - } - else { - Start-ThreadJob -Name ffmpeg -ArgumentList $threadArgs -ScriptBlock { - param ($dvArgs, $out, $log, $x265) - - cmd.exe /c "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args1) -o `"$out`"" 2>$log - } | Out-Null - } + $shellArgs = ($IsLinux -or $IsMacOS) ? + ('bash', '-c', "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args1) -o $($Paths.HevcPath)") : + ('cmd.exe', '/c', "ffmpeg -hide_banner -loglevel panic $($dvArgs.FFMpegVideo) | $x265 $($dvArgs.x265Args1) -o `"$($Paths.HevcPath)`"") } - if (!$DisableProgress) { + Write-Verbose "FULL CLI:`n $($shellArgs -join ' ')`n" + + if ($DisableProgress) { + & $shellArgs[0] $shellArgs[1] $shellArgs[2] 2>Paths.$LogPath + } + else { + Start-ThreadJob -Name 'crf' -ArgumentList $shellArgs, $Paths.LogPath -ScriptBlock { + param ([array]$ShellArgs, [string]$LogPath) + + & $ShellArgs[0] $ShellArgs[1] $ShellArgs[2] 2>$LogPath + } | Out-Null + $params = @{ InputFile = $Paths.InputFile LogPath = $Paths.LogPath TestFrames = $TestFrames - JobName = 'ffmpeg' + JobName = 'crf' SecondPass = $false DolbyVision = $dovi Verbose = $setVerbose @@ -586,7 +585,8 @@ function Invoke-FFMpeg { $Paths.TmpOut = $Paths.OutputFile -replace '^(.*)\.(.+)$', '$1-TMP.$2' if ($PSBoundParameters['TestFrames']) { # Cut stream at video frame marker - ffmpeg -hide_banner -loglevel panic -ss 00:01:30 $dvArgs.FFMpegOther -frames:a $($TestFrames + 100) -y $Paths.tmpOut 2>>$Paths.LogPath + ffmpeg -hide_banner -loglevel panic -ss 00:01:30 $dvArgs.FFMpegOther -frames:a $($TestFrames + 100) ` + -y $Paths.tmpOut 2>>$Paths.LogPath } else { ffmpeg -hide_banner -loglevel panic $dvArgs.FFMpegOther -y $Paths.tmpOut 2>>$Paths.LogPath @@ -622,7 +622,7 @@ function Invoke-FFMpeg { $muxPaths.Temp = $Paths.ChapterPath $muxPaths.Chapters = $true } - elseif (!$Paths.ChapterPath -and !$Paths.TmpOut) { + elseif (!$Paths.ChapterPath -and !$Paths.TmpOut) { $muxPaths.VideoOnly = $true } else { @@ -636,6 +636,7 @@ function Invoke-FFMpeg { Write-Host "" } # End DoVi + # Two pass encode elseif ($ffmpegArgs.Count -eq 2 -and $RateControl[0] -eq '-b:v') { Write-Host "$("`u{2726}" * 3) 2-Pass ABR Selected @ $($RateControl[1] -replace '(.*)(\w+)$', '$1 $2')b/s $("`u{2726}" * 3)" @emphasisColors diff --git a/modules/FFTools/Utils/Read-TimedInput.ps1 b/modules/FFTools/Utils/Read-TimedInput.ps1 index 31f333e..25e7713 100644 --- a/modules/FFTools/Utils/Read-TimedInput.ps1 +++ b/modules/FFTools/Utils/Read-TimedInput.ps1 @@ -36,7 +36,7 @@ function Read-TimedInput { [int]$Timeout = 50000, [Parameter(Mandatory = $true)] - [ValidateSet('Yes/No', 'Integer', 'Scale')] + [ValidateSet('Yes/No', 'Integer', 'Select')] [string]$Mode, [Parameter(Mandatory = $true)] @@ -83,11 +83,11 @@ function Read-TimedInput { 'Integer' { (($response -as [int]) -is [int] -and $response -gt 0) ? [int]$response : $null } - 'Scale' { + 'Select' { if ($response -match '^e[xit]?' -or $response -match '^q[uit]?') { Write-Host $exitBanner @errColors -ErrorAction Stop } - ($response -in $InputObject) ? $response : $null + ($response -in $InputObject -xor $response -like 'custom=*') ? $response : $null } } From aad6fc34af347b42de84b9ada376ae5dda1fd4ff Mon Sep 17 00:00:00 2001 From: patrickenfuego Date: Wed, 27 Jul 2022 22:19:10 -0400 Subject: [PATCH 03/10] update banner phrasing --- modules/FFTools/Private/Set-AudioPreference.ps1 | 6 +++--- modules/FFTools/Private/Set-SubtitlePreference.ps1 | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/FFTools/Private/Set-AudioPreference.ps1 b/modules/FFTools/Private/Set-AudioPreference.ps1 index c3e8da7..6cd18e5 100644 --- a/modules/FFTools/Private/Set-AudioPreference.ps1 +++ b/modules/FFTools/Private/Set-AudioPreference.ps1 @@ -253,8 +253,8 @@ function Set-AudioPreference { break } { 0..12 -contains $_ } { - Write-Host "AUDIO STREAM $UserChoice SELECTED" @progressColors - Write-Host "Stream $UserChoice from input will be mapped to stream $Stream in the output" + Write-Host "$("`u{25c7}" * 2) AUDIO STREAM $UserChoice SELECTED $("`u{25c7}" * 2)" @progressColors + Write-Host "Stream $UserChoice from input will be mapped to stream $Stream in the output`n" @('-map', "0:a:$UserChoice`?", "-c:a:$Stream", 'copy') break } @@ -318,7 +318,7 @@ function Set-AudioPreference { # Start a background job to run Dolby Encoder if selected if ($UserChoice -like '*dee*') { Write-Verbose "DEE - Audio bitrate is: $Bitrate" - Write-Host "Spawning dee encoder in a separate process`n" @emphasisColors + Write-Host "Spawning dee encoder in a separate thread`n" @emphasisColors # Create hash of dee params to marshall across process line $deeParams = @{ Paths = $Paths diff --git a/modules/FFTools/Private/Set-SubtitlePreference.ps1 b/modules/FFTools/Private/Set-SubtitlePreference.ps1 index e989860..4da0b8d 100644 --- a/modules/FFTools/Private/Set-SubtitlePreference.ps1 +++ b/modules/FFTools/Private/Set-SubtitlePreference.ps1 @@ -17,7 +17,7 @@ function Set-SubtitlePreference { } elseif ($UserChoice -match "n[one]*$") { Write-Host "$("`u{25c7}" * 2) NO SUBTITLES SELECTED $("`u{25c7}" * 2)" @progressColors - Write-Host "All subtitle streams will be excluded from the output file`n" + Write-Host "No subtitle streams will be included in the output file`n" return '-sn' } elseif ($UserChoice -match "d[efault]*$") { @@ -29,7 +29,7 @@ function Set-SubtitlePreference { if ($UserChoice -like "!*") { $lang = $UserChoice.Replace('!', '').ToUpper() Write-Host "$("`u{25c7}" * 2) SKIP $lang SUBTITLES SELECTED $("`u{25c7}" * 2)" @progressColors - Write-Host "All subtitle streams of this language will be ignored" + Write-Host "No subtitle streams of this language will be copied" } else { Write-Host "$("`u{25c7}" * 2) $($UserChoice.ToUpper()) SUBTITLES SELECTED $("`u{25c7}" * 2)" @progressColors From f3f269a2ece1000068e34352a98aad8f986495cd Mon Sep 17 00:00:00 2001 From: patrickenfuego Date: Wed, 27 Jul 2022 22:20:50 -0400 Subject: [PATCH 04/10] add fps info to progress --- .../FFTools/Private/Write-EncodeProgress.ps1 | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/modules/FFTools/Private/Write-EncodeProgress.ps1 b/modules/FFTools/Private/Write-EncodeProgress.ps1 index 57ba04c..eaebae4 100644 --- a/modules/FFTools/Private/Write-EncodeProgress.ps1 +++ b/modules/FFTools/Private/Write-EncodeProgress.ps1 @@ -67,7 +67,7 @@ function Write-EncodeProgress { else { if (!$SecondPass) { Write-Progress "Gathering frame count for progress display..." - $frameStr = ffmpeg -i $InputFile -map 0:v:0 -c:v copy -f null - 2>&1 + $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) @@ -92,32 +92,40 @@ function Write-EncodeProgress { try { if ($DolbyVision) { - $currentFrameStr = Get-Content $LogPath -Tail 1 | - Select-String -Pattern '^(\d+)' | - ForEach-Object { $_.Matches.Groups[1].Value } + $params = @{ + Pattern = '(?\d+)\/?\d{0,8}(?=\s+frames)[^\d]+(?\d+\.?\d*)(?=\s+fps)' + AllMatches = $true + } + $currentFrameStr, $fpsStr = Get-Content $LogPath -Tail 1 | + Select-String @params | + ForEach-Object { $_.Matches.Groups[1].Value, $_.Matches.Groups[2].Value } } else { - $currentFrameStr = Get-Content $LogPath -Tail 1 | - Select-String -Pattern '^frame=\s*(\d+)\s*.*' | - ForEach-Object { $_.Matches.Groups[1].Value } + $params = @{ + Pattern = '^frame=\s*(?\d+)\s*fps=\s*(?\d+\.?\d*)(?=\s*q)' + AllMatches = $true + } + $currentFrameStr, $fpsStr = Get-Content $LogPath -Tail 1 | + Select-String @params | + ForEach-Object { $_.Matches.Groups[1].Value, $_.Matches.Groups[2].Value } } if (($currentFrameStr -as [int]) -is [int]) { - [int]$currentFrame = $currentFrameStr + [int]$currentFrame = $currentFrameStr } - else { - $currentFrame = $currentFrame + if (($fpsStr -as [double]) -is [double]) { + [double]$fps = $fpsStr } } catch { - Write-Verbose "Error: $currentFrame is not an integer" + Write-Verbose "Error: $currentFrame or $fps is not a number" continue } - if ($currentFrame) { + if ($currentFrame -and $fps) { $progress = ($currentFrame / $frameCount) * 100 - $status = "$([math]::Round($progress, 2))% Complete" - $activity = "Encoding Frame $currentFrame of $frameCount" + $status = '{0:N1}% Complete' -f $progress + $activity = "Encoding Frame $currentFrame of $frameCount, $('{0:N2}' -f $fps) FPS" $params = @{ PercentComplete = $progress @@ -125,7 +133,7 @@ function Write-EncodeProgress { Activity = $activity } Write-Progress @params - Start-Sleep -Seconds 1 + Start-Sleep -Seconds 1.2 } else { Start-Sleep -Milliseconds 500 From c8019b085fa3c1b3225958472ec1898162bbfd66 Mon Sep 17 00:00:00 2001 From: patrickenfuego Date: Wed, 27 Jul 2022 22:22:04 -0400 Subject: [PATCH 05/10] fix resource lock on log file --- FFEncoder.ps1 | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/FFEncoder.ps1 b/FFEncoder.ps1 index 1b35ae9..aabc465 100644 --- a/FFEncoder.ps1 +++ b/FFEncoder.ps1 @@ -650,11 +650,18 @@ function Set-ScriptPaths ([hashtable]$OS) { if ([File]::Exists($logPath) -and ((Get-Process 'ffmpeg' -ErrorAction SilentlyContinue) -or (Get-Process 'x265*' -ErrorAction SilentlyContinue))) { - - $logCount = (Get-ChildItem $root -Filter '*encode*.log' | Measure-Object).Count - if ($logCount) { - Write-Host "Existing encode detected...creating a separate log file" @warnColors - $logPath = [Path]::Join($root, "$title`_encode$($logCount + 1).log") + + # Check if a process is writing to the current log file + $length1 = (Get-Content $logPath).Length + Start-Sleep -Seconds 1.2 + $length2 = (Get-Content $logPath).Length + + if ($length2 -gt $length1) { + $logCount = (Get-ChildItem $root -Filter '*encode*.log' | Measure-Object).Count + if ($logCount) { + Write-Host "Existing encode detected...creating a separate log file" @warnColors + $logPath = [Path]::Join($root, "$title`_encode$($logCount + 1).log") + } } } From 62235b2fc8b1e6e04341c1380677b1dc847f9153 Mon Sep 17 00:00:00 2001 From: patrickenfuego Date: Wed, 27 Jul 2022 22:22:23 -0400 Subject: [PATCH 06/10] add unsharp & vapoursynth support --- FFEncoder.ps1 | 677 +++++++++++++++++++++++++++++--------------------- 1 file changed, 397 insertions(+), 280 deletions(-) diff --git a/FFEncoder.ps1 b/FFEncoder.ps1 index aabc465..e31655e 100644 --- a/FFEncoder.ps1 +++ b/FFEncoder.ps1 @@ -219,99 +219,115 @@ using namespace System.IO -[CmdletBinding(DefaultParameterSetName = "CRF")] +[CmdletBinding(DefaultParameterSetName = 'CRF')] param ( - [Parameter(Mandatory = $true, ParameterSetName = "Help")] - [Alias("H", "?")] + [Parameter(Mandatory = $true, ParameterSetName = 'Help')] + [Alias('H')] [switch]$Help, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [ValidateSet("x264", "x265")] - [Alias("Enc")] - [string]$Encoder = "x265", + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [ValidateSet('x264', 'x265')] + [Alias('Enc')] + [string]$Encoder = 'x265', - [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "CRF")] - [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "VMAF")] - [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "Pass")] + [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'VMAF')] + [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'PASS')] [ValidateScript( { if (Test-Path $_) { $true } else { throw 'Input path does not exist' } } )] - [Alias("I", "Reference", "Source")] + [Alias('I', 'Reference', 'Source')] [string]$InputPath, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, Position = 1, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, Position = 1, ParameterSetName = 'PASS')] + [ValidateScript( + { + if (!(Test-Path $_)) { + throw "Could not locate Vapoursynth script. Check the script path and try again" + } + if (($(ffmpeg 2>&1) -join ' ') -notmatch 'vapoursynth') { + throw "ffmpeg was not compiled with Vapoursynth. Ensure the '--enable-vapoursynth' flag was set during compilation" + } + $true + } + )] + [Alias('VSScript', 'VPY')] + [string]$VapoursynthScript, + + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateSet('copy', 'c', 'copyall', 'ca', 'aac', 'none', 'n', 'ac3', 'dee_dd', 'dee_ac3', 'dd', 'dts', 'flac', 'f', 'eac3', 'ddp', 'dee_ddp', 'dee_eac3', 'dee_ddp_51', 'dee_eac3_51', 'dee_thd', 'fdkaac', 'faac', 'aac_at', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)] - [Alias("A")] - [string]$Audio = "copy", + [Alias('A')] + [string]$Audio = 'copy', - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(-1, 3000)] - [Alias("AB", "ABitrate")] + [Alias('AB', 'ABitrate')] [int]$AudioBitrate, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [Alias("2CH", "ST")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [Alias('2CH', 'ST')] [switch]$Stereo, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateSet('copy', 'c', 'copyall', 'ca', 'aac', 'none', 'n', 'ac3', 'dee_dd', 'dee_ac3', 'dd', 'dts', 'flac', 'f', 'eac3', 'ddp', 'dee_ddp', 'dee_eac3', 'dee_ddp_51', 'dee_eac3_51', 'dee_thd', 'fdkaac', 'faac', 'aac_at', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)] - [Alias("A2")] + [Alias('A2')] [string]$Audio2 = "none", - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(-1, 3000)] - [Alias("AB2", "ABitrate2")] + [Alias('AB2', 'ABitrate2')] [int]$AudioBitrate2, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [Alias("2CH2", "ST2")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [Alias('2CH2', 'ST2')] [switch]$Stereo2, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateSet('all', 'a', 'copyall', 'ca', 'none', 'default', 'd', 'n', 'eng', 'fre', 'ger', 'spa', 'dut', 'dan', 'fin', 'nor', 'cze', 'pol', 'chi', 'zho', 'kor', 'gre', 'rum', 'rus', 'swe', 'est', 'ind', 'slv', 'tur', 'vie', 'hin', 'heb', 'ell', 'bul', 'ara', 'por', 'nld', '!eng', '!fre', '!ger', '!spa', '!dut', '!dan', '!fin', '!nor', '!cze', '!pol', '!chi', '!zho', '!kor', '!ara', '!rum', '!rus', '!swe', '!est', '!ind', '!slv', '!tur', '!vie', '!hin', '!heb', '!gre', '!ell', '!bul', '!por', '!nld')] - [Alias("S", "Subs")] - [string]$Subtitles = "default", + [Alias('S', 'Subs')] + [string]$Subtitles = 'default', - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateSet("placebo", "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast", "superfast", "ultrafast")] - [Alias("P")] - [string]$Preset = "slow", + [Alias('P')] + [string]$Preset = 'slow', - [Parameter(Mandatory = $true, ParameterSetName = "CRF")] + [Parameter(Mandatory = $true, ParameterSetName = 'CRF')] [ValidateRange(0.0, 51.0)] - [Alias("C")] + [Alias('C')] [double]$CRF, - [Parameter(Mandatory = $true, ParameterSetName = "Pass")] - [Alias("VBitrate")] + [Parameter(Mandatory = $true, ParameterSetName = 'PASS')] + [Alias('VBitrate')] [ValidateScript( { $_ -cmatch "(?\d+\.?\d{0,2})(?[K k M]+)" if ($Matches) { switch ($Matches.suffix) { - "K" { + 'K' { if ($Matches.num -gt 99000 -or $Matches.num -lt 1000) { throw "Bitrate out of range. Must be between 1,000-99,000 kb/s" } else { $true } } - "M" { + 'M' { if ($Matches.num -gt 99 -or $Matches.num -le 1) { throw "Bitrate out of range. Must be between 1-99 mb/s" } @@ -325,225 +341,247 @@ param ( )] [string]$VideoBitrate, - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(1, 2)] [int]$Pass = 2, - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateSet('Default', 'd', 'Fast', 'f', 'Custom', 'c')] - [Alias("FPT", "PassType")] - [string]$FirstPassType = "Default", + [Alias('FPT', 'PassType')] + [string]$FirstPassType = 'Default', - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(-6, 6)] [ValidateCount(2, 2)] - [Alias("DBF")] + [Alias('DBF')] [int[]]$Deblock = @(-2, -2), - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(0, 4)] - [Alias("AQM")] + [Alias('AQM')] [int]$AqMode, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(0.0, 3.0)] - [Alias("AQS")] + [Alias('AQS')] [double]$AqStrength = 1.00, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [Alias("PRD", "PsyRDO")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [Alias('PRD', 'PsyRDO')] [string]$PsyRd, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(0.0, 50.0)] - [Alias("PRQ", "PsyTrellis")] + [Alias('PRQ', 'PsyTrellis')] [double]$PsyRdoq, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(1, 16)] [int]$Ref, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(0, 1)] - [Alias("MBTree", "CUTree")] + [Alias('MBTree', 'CUTree')] [int]$Tree = 1, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(1, 32768)] - [Alias("MR")] + [Alias('MR')] [int]$Merange, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(0, 2000)] [ValidateCount(1, 2)] - [Alias("NR")] + [Alias('NR')] [int[]]$NoiseReduction = @(0, 0), - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [ValidateScript( - { - if ($_.Count -eq 0) { throw "NLMeans Hashtable must contain at least 1 value" } - $flag = $false - foreach ($k in $_.Keys) { - if ($k -notin 's', 'p', 'pc', 'r', 'rc') { - throw "Invalid key. Valid keys are 's', 'p', 'pc', 'r', 'rc'" - } - else { $flag = $true } - } - if ($flag = $true) { $true } - else { throw "Invalid NLMeans hashtable. See https://ffmpeg.org/ffmpeg-filters.html#nlmeans-1" } - } - )] - [Alias("NL")] - [hashtable]$NLMeans, - - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(1, 4)] [ValidateCount(2, 2)] - [Alias("TU")] + [Alias('TU')] [int[]]$TuDepth = @(1, 1), - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(1, 4)] - [Alias("LTU")] + [Alias('LTU')] [int]$LimitTu = 0, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(0.0, 1.0)] - #[Alias("Q")] + [Alias("Q")] [double]$QComp = 0.60, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(0, 16)] - [Alias("B")] + [Alias('B')] [int]$BFrames, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(0, 1)] - [Alias("BINT")] + [Alias('BINT')] [int]$BIntra, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(0, 11)] - [Alias("SM", "Subpel")] + [Alias('SM', 'Subpel')] [int]$Subme, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(0, 1)] - [Alias("SIS")] + [Alias('SIS')] [int]$StrongIntraSmoothing = 1, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateSet('1', '1b', '2', '1.1', '1.2', '1.3', '2.1', '21', '2.2', '3.1', '3.2', '4', '4.1', '4.2', '41', - '5', '5.1', '51', '5.2', '52', '6', '6.1', '61', '6.2', '62', '8.5', '85')] + '5', '5.1', '51', '5.2', '52', '6', '6.1', '61', '6.2', '62', '8.5', '85')] [Alias('L')] [string]$Level, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateCount(2, 2)] + [Alias('VideoBuffer')] [int[]]$VBV, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(1, 64)] - [Alias("FrameThreads")] + [Alias('FrameThreads')] [int]$Threads, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateRange(0, 250)] - [Alias("RCL", "Lookahead")] + [Alias('RCL', 'Lookahead')] [int]$RCLookahead, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [Alias("FE", "ffmpeg")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [Alias('FE', 'FFExtra')] [array]$FFMpegExtra, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [Alias("Extra")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [Alias('Extra')] [hashtable]$EncoderExtra, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [Alias("T", "Test")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [Alias('T', 'Test')] [int]$TestFrames, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [Alias("Start", "TS")] - [string]$TestStart = "00:01:30", + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [Alias('Start', 'TS')] + [string]$TestStart = '00:01:30', - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [Alias("Del", "RM")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [Alias('Del', 'RM')] [switch]$RemoveFiles, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [Alias("DI")] + # Filtering related parameters + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [ValidateScript( + { + if ($_.Count -eq 0) { throw "NLMeans Hashtable must contain at least 1 value" } + $flag = $false + foreach ($k in $_.Keys) { + if ($k -notin 's', 'p', 'pc', 'r', 'rc') { + throw "Invalid key. Valid keys are 's', 'p', 'pc', 'r', 'rc'" + } + else { $flag = $true } + } + if ($flag = $true) { $true } + else { throw "Invalid NLMeans hashtable. See https://ffmpeg.org/ffmpeg-filters.html#nlmeans-1" } + } + )] + [Alias('NL')] + [hashtable]$NLMeans, + + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [ArgumentCompletions( + 'luma_small', 'luma_medium', 'luma_large', 'chroma_small', + 'chroma_medium', 'chroma_large', 'yuv_small', 'yuv_medium', + 'yuv_large' + )] + [Alias('U')] + [string]$Unsharp, + + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [ArgumentCompletions( + 'sharpen_mild', 'sharpen_medium', 'sharpen_strong', + 'blur_mild', 'blur_medium', 'blur_strong' + )] + [Alias('UStrength')] + [string]$UnsharpStrength = 'sharpen_mild', + + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [Alias('DI')] [switch]$Deinterlace, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateSet('point', 'spline16', 'spline36', 'bilinear', 'bicubic', 'lanczos', 'fast_bilinear', 'neighbor', 'area', 'gauss', 'sinc', 'spline', 'bicublin')] - [Alias("SF", "ResizeType")] - [string]$Scale = "bilinear", + [Alias('SF', 'ResizeType')] + [string]$Scale = 'bilinear', - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateSet('2160p', '1080p', '720p')] - [Alias("Res", "R")] + [Alias('Res', 'R')] [string]$Resolution, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [alias("Report", "GR")] + # Utility parameters + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [alias('Report', 'GR')] [switch]$GenerateReport, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [Alias("NoDV", "SDV")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [Alias('NoDV', 'SDV')] [switch]$SkipDolbyVision, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [alias("No10P", "STP")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [alias('No10P', 'STP')] [switch]$SkipHDR10Plus, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [alias("Exit")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [Alias('Exit')] [switch]$ExitOnError, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] - [alias("NoProgressBar")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] + [alias('NoProgressBar')] [switch]$DisableProgress, - [Parameter(Mandatory = $false, ParameterSetName = "CRF")] - [Parameter(Mandatory = $false, ParameterSetName = "Pass")] + [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] [ValidateScript( { $flag = $false @@ -560,34 +598,33 @@ param ( else { throw "Invalid MKV Tag hashtable" } } )] - [Alias("CreateTagFile")] + [Alias('CreateTagFile')] [hashtable]$GenerateMKVTagFile, - [Parameter(Mandatory = $true, ParameterSetName = "CRF")] - [Parameter(Mandatory = $true, ParameterSetName = "VMAF")] - [Parameter(Mandatory = $true, ParameterSetName = "Pass")] + [Parameter(Mandatory = $true, ParameterSetName = 'CRF')] + [Parameter(Mandatory = $true, ParameterSetName = 'VMAF')] + [Parameter(Mandatory = $true, ParameterSetName = 'PASS')] [ValidateNotNullOrEmpty()] - [Alias("O", "Encode", "Distorted")] + [Alias('O', 'Encode', 'Distorted')] [string]$OutputPath, ## VMAF-Specific Parameters - [Parameter(Mandatory = $true, ParameterSetName = "VMAF")] - [Alias("VMAF")] + [Parameter(Mandatory = $true, ParameterSetName = 'VMAF')] + [Alias('VMAF', 'EnableVMAF')] [switch]$CompareVMAF, - [Parameter(Mandatory = $false, ParameterSetName = "VMAF")] - [alias("SSIM")] + [Parameter(Mandatory = $false, ParameterSetName = 'VMAF')] + [alias('SSIM')] [switch]$EnableSSIM, - [Parameter(Mandatory = $false, ParameterSetName = "VMAF")] - [alias("PSNR")] + [Parameter(Mandatory = $false, ParameterSetName = 'VMAF')] + [alias('PSNR')] [switch]$EnablePSNR, - [Parameter(Mandatory = $false, ParameterSetName = "VMAF")] + [Parameter(Mandatory = $false, ParameterSetName = 'VMAF')] [ValidateSet('json', 'xml', 'csv', 'sub')] - [alias("LogType")] + [Alias('LogType', 'VMAFLog')] [string]$LogFormat - ) ######################################################### @@ -681,6 +718,12 @@ function Set-ScriptPaths ([hashtable]$OS) { HevcPath = $hevcPath OutputFile = $OutputPath } + if ($VapoursynthScript) { + $pathObject['VPY'] = $VapoursynthScript + } + + Write-Verbose "PATHS OBJECT:`n $($pathObject | Out-String)" + return $pathObject } @@ -726,11 +769,11 @@ $console.WindowTitle = 'FFEncoder' [console]::TreatControlCAsInput = $false # Import FFTools module -Import-Module -Name "$PSScriptRoot\modules\FFTools" +Import-Module -Name "$PSScriptRoot\modules\FFTools" -Force Write-Verbose "`n`n---------------------------------------" # Source version functions -. $([Path]::Join($ScriptsDirectory, 'VerifyVersions.ps1')).toString() +. $([Path]::Join($ScriptsDirectory, 'VerifyVersions.ps1')).ToString() # Verify the current version of pwsh & exit if version not satisfied $Global:psReq = Confirm-PoshVersion # Check for updates to FFencoder and prompt to download if git is available @@ -758,10 +801,10 @@ if ($PSBoundParameters['CompareVMAF']) { Write-Host "" $params = @{ - Source = $InputPath - Encode = $OutputPath - SSIM = $EnableSSIM - PSNR = $EnablePSNR + Source = $InputPath + Encode = $OutputPath + SSIM = $EnableSSIM + PSNR = $EnablePSNR } if ($PSBoundParameters['LogFormat']) { @@ -799,27 +842,27 @@ else { } <# - VALIDATE - Check: + VALIDATE: - Source resolution - Parameter combinations - - TODO: Too complicated for parameter sets...try dynamic params? - Primary audio type if transcoding was selected + Confirm test encode parameters + Confirm scaling parameters + Confirm unsharp parameters + Audio check - Warn if transcoding lossy -> lossy - x264 or x265 settings that use different value ranges -#> + - Disable audio params if source has no audio -# Check the source resolution -$sourceResolution = ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 $InputPath + If Vapoursynth is used, filtering-related checks are ignored - must be handled + in Vapoursynth +#> # Verify test parameters and prompt if one is missing (unless ExitOnError is present) if ($PSBoundParameters['TestStart'] -and !$PSBoundParameters['TestFrames']) { Write-Host 'The -TestStart parameter was passed without a frame count duration' @errColors $params = @{ - Prompt = 'Enter the number of test frames to use: ' - Timeout = 25000 - Mode = 'Integer' - Count = 3 + Prompt = 'Enter the number of test frames to use: ' + Timeout = 25000 + Mode = 'Integer' + Count = 3 } try { $TestFrames = Read-TimedInput @params -Verbose:$setVerbose @@ -830,53 +873,155 @@ if ($PSBoundParameters['TestStart'] -and !$PSBoundParameters['TestFrames']) { } } -# If scale is used, verify arguments and handle errors -if ($PSBoundParameters['Scale']) { - $isScale = $true - try { - $scaleType, $filter = Confirm-ScaleFilter -Filter $Scale -Verbose:$setVerbose - } - catch { - Write-Host "`u{203C} $($_.Exception.Message). The output will not be scaled`n" @errColors - $isScale = $false +# Check the source resolution +$sourceResolution = ffprobe -v error -select_streams v:0 -show_entries stream=width,height ` + -of csv=s=x:p=0 $InputPath + +# If VS is used, skip video filtering +if (!$PSBoundParameters['VapoursynthScript']) { + # If scale is used, verify arguments and handle errors + if ($PSBoundParameters['Scale']) { + $isScale = $true + try { + $scaleType, $filter = Confirm-ScaleFilter -Filter $Scale -Verbose:$setVerbose + } + catch { + Write-Host "`u{203C} $($_.Exception.Message). The output will not be scaled`n" @errColors + $isScale = $false + } } -} -else { $isScale = $false } - -# If scaling is used, check if Resolution was passed & set hashtable -if ($isScale) { - # Warn if no resolution was passed, and set to a default - if (!$PSBoundParameters['Resolution']) { - $defaultResolution = switch -Wildcard ($sourceResolution) { - '*3840x2160*' { '1080p' } - '*1920x1080*' { '2160p' } - '*1280x720*' { '1080p' } + else { $isScale = $false } + + # If scaling is used, check if Resolution was passed & set hashtable + if ($isScale) { + # Warn if no resolution was passed, and set to a default + if (!$PSBoundParameters['Resolution']) { + $defaultResolution = switch -Wildcard ($sourceResolution) { + '*3840x2160*' { '1080p' } + '*1920x1080*' { '2160p' } + '*1280x720*' { '1080p' } + } + Write-Warning "No resolution specified for scaling. Using a default based on source: $defaultResolution" + Write-Host "" + } + + # Collect the arguments into a hashtable + $scaleHash = @{ + Scale = $scaleType + ScaleFilter = $filter + Resolution = $Resolution ??= $defaultResolution } - Write-Warning "No resolution specified for scaling. Using a default based on source: $defaultResolution" - Write-Host "" } + # Verify unsharp params and prompt for new value if necessary + if ($PSBoundParameters['UnsharpStrength'] -and !$PSBoundParameters['Unsharp']) { + Write-Warning "No value was passed for -Unsharp. Using default: luma_small" + $Unsharp = 'luma_small' + } + elseif ($PSBoundParameters['Unsharp'] -and !$PSBoundParameters['UnsharpStrength']) { + if ($Unsharp -notlike 'custom=*') { + Write-Warning "No value was passed for -UnsharpStrength. Using default: sharpen_mild" + $UnsharpStrength = 'sharpen_mild' + } + } + elseif ($PSBoundParameters['Unsharp']) { + $unsharpSet = @('luma_small', 'luma_medium', 'luma_large', 'chroma_small', + 'chroma_medium', 'chroma_large', 'yuv_small', 'yuv_medium', + 'yuv_large') + + if ($Unsharp -notin $unsharpSet -and $Unsharp -notlike 'custom=*') { + $unsharpOptions = ($unsharpSet + 'custom=') | + Join-String -Separator "`r`n`t`u{2022} " ` + -OutputPrefix "$($boldOn) Valid options for Unsharp$($boldOff):`n`t`u{2022} " + Write-Host "Invalid option entered for Unsharp:`n$unsharpOptions" + $params = @{ + Prompt = 'Enter a valid option: ' + Timeout = 50000 + Mode = 'Select' + Count = 4 + InputObject = $unsharpSet + } + $Unsharp = Read-TimedInput @params + Write-Host "" + } + } + elseif ($PSBoundParameters['UnsharpStrength'] -and $Unsharp -notlike 'custom=*') { + $strengthSet = @('sharpen_mild', 'sharpen_medium', 'sharpen_strong', + 'blur_mild', 'blur_medium', 'blur_strong') + + if ($UnsharpStrength -notin $strengthSet) { + $strengthOptions = $strengthSet | + Join-String -Separator "`r`n`t`u{2022} " ` + -OutputPrefix "$($boldOn) Valid options for Unsharp Strength$($boldOff):`n`t`u{2022} " + Write-Host "Invalid option entered for Unsharp strength:`n$strengthOptions" + $params = @{ + Prompt = 'Enter a valid option: ' + Timeout = 50000 + Mode = 'Select' + Count = 4 + InputObject = $strengthSet + } + $UnsharpStrength = Read-TimedInput @params + Write-Host "" + } + } # Collect the arguments into a hashtable - $scaleHash = @{ - Scale = $scaleType - ScaleFilter = $filter - Resolution = $Resolution ??= $defaultResolution + if ($Unsharp -and $UnsharpStrength) { + $unsharpHash = @{ + Size = $Unsharp + Strength = $UnsharpStrength + } + } + else { $unsharpHash = $null } + + <# + CROP FILE GENERATION + HDR ELIGIBILITY VERIFICATION + + If crop arguments are passed via FFMpegExtra, don't generate crop file + If HDR metadata is present but x264 is selected, exit on error (not supported) + #> + + if ($PSBoundParameters['FFMpegExtra']) { + $custom = $FFMpegExtra.Where({ $_['-vf'] -like '*crop*' }) + $skipCropFile = $custom ? $true : $false + } + if ($skipCropFile) { + Write-Host "Crop override arguments detected. Skipping crop file generation" @warnColors + Write-Host "" + # Check if source is 4K for HDR metadata + $cropDim = ($sourceResolution -like '3840x2160*') ? ( @(-1, -1, $true) ) : ( @(-1, -1, $false) ) + } + else { + New-CropFile -InputPath $paths.InputFile -CropFilePath $paths.CropPath -Count 1 -Verbose:$setVerbose + # Calculating the crop values. Re-throw terminating error if one occurs + $cropDim = Measure-CropDimensions -CropFilePath $paths.CropPath -Resolution $Resolution -Verbose:$setVerbose } } +# Vapoursynth is used +else { + $format = "$($PSStyle.Foreground.Yellow)$($PSStyle.Bold)$($PSStyle.Italic)" + $msg = "Vapoursynth script detected - $format all filtering (cropping, resizing, etc.) must be done using Vapoursynth`n" + Write-Host $msg + + # Set dummy values for required parameters + $cropDim = ($sourceResolution -like '3840x2160*') ? @(-2, -2, $true) : @(-2, -2, $false) +} -# Validate input audio +# Validate audio in the input file $res = ffprobe -hide_banner -loglevel error -select_streams a:0 -of default=noprint_wrappers=1:nokey=1 ` -show_entries "stream=codec_name,profile" ` -i $Paths.InputFile - + +# If audio streams were found if ($res) { - $lossless = (($res[0] -like 'truehd') -xor ($res[1] -like 'DTS-HD MA') -xor ($res[0] -like 'flac')) ? - $true : $false - $test1 = @("^c[opy]*$", "c[opy]*a[ll]*", "^n[one]?").Where({ $Audio -match $_ }) - $test2 = @("^c[opy]*$", "c[opy]*a[ll]*", "^n[one]?").Where({ $Audio2 -match $_ }) + $lossless = (($res[0] -like 'truehd') -xor ($res[1] -like 'DTS-HD MA') -xor ($res[0] -like 'flac')) ? + $true : $false + $test1 = @('^c[opy]*$', 'c[opy]*a[ll]*', '^n[one]?').Where({ $Audio -match $_ }, 'SkipUntil', 1) + $test2 = @('^c[opy]*$', 'c[opy]*a[ll]*', '^n[one]?').Where({ $Audio2 -match $_ }, 'SkipUntil', 1) if (!$lossless -and (!$test1 -or !$test2)) { $msg = "Audio stream 0 is not lossless. Transcoding to another lossy codec is NOT recommended " + - "(If you're stream copying a codec by name, ignore this)" + "(If you're stream copying a codec by name, ignore this)" Write-Warning $msg } } @@ -886,36 +1031,6 @@ elseif (!$res) { $Audio2 = 'none' } -<# - CROP FILE GENERATION - HDR ELIGIBILITY VERIFICATION - - If crop arguments are passed via FFMpegExtra, don't generate crop file - If HDR metadata is present but x264 is selected, exit on error (not supported) -#> - -$skipCropFile = $false -if ($PSBoundParameters['FFMpegExtra']) { - foreach ($arg in $FFMpegExtra) { - if ($arg -is [hashtable]) { - foreach ($val in $arg.Values) { - if ($val -match "crop") { $skipCropFile = $true } - } - } - } -} -if ($skipCropFile) { - Write-Host "Crop override arguments detected. Skipping crop file generation" @warnColors - Write-Host "" - # Check if source is 4K for HDR metadata - $cropDim = ($sourceResolution -like '3840x2160?') ? ( @(-1, -1, $true) ) : ( @(-1, -1, $false) ) -} -else { - New-CropFile -InputPath $paths.InputFile -CropFilePath $paths.CropPath -Count 1 -Verbose:$setVerbose - # Calculating the crop values. Re-throw terminating error if one occurs - $cropDim = Measure-CropDimensions -CropFilePath $paths.CropPath -Resolution $Resolution -Verbose:$setVerbose -} - <# SET RATE CONTROL @@ -979,6 +1094,7 @@ $ffmpegParams = @{ PsyRdoq = $PsyRdoq NoiseReduction = $NoiseReduction NLMeans = $NLMeans + Unsharp = $unsharpHash TuDepth = $TuDepth LimitTu = $LimitTu Tree = $Tree @@ -1012,13 +1128,14 @@ catch { $params = @{ Message = "An error occurred during ffmpeg invocation. Exception:`n$($_.Exception)" RecommendedAction = 'Correct the Error Message' - Category = "InvalidArgument" - CategoryActivity = "FFmpeg Function Invocation" + Category = 'InvalidArgument' + CategoryActivity = 'FFmpeg Function Invocation' TargetObject = $ffmpegParams ErrorId = 55 } $console.WindowTitle = $currentTitle + [console]::TreatControlCAsInput = $false Get-Job | Stop-Job -PassThru | Remove-Job -Force Write-Error @params -ErrorAction Stop } @@ -1138,12 +1255,12 @@ if ([File]::Exists($Paths.StereoPath) -and !$skipBackgroundAudioMux) { # If mkvmerge is available, use it instead of ffmpeg if ((Get-Command 'mkvmerge') -and $OutputPath.EndsWith('mkv')) { $muxPaths = @{ - Input = $paths.OutputFile - Output = $output - ExternalAudio = $paths.StereoPath - Title = $paths.Title - Language = $paths.Language - LogPath = $paths.LogPath + Input = $paths.OutputFile + Output = $output + ExternalAudio = $paths.StereoPath + Title = $paths.Title + Language = $paths.Language + LogPath = $paths.LogPath } $mId = 1 Invoke-MkvMerge -Paths $muxPaths -Mode 'remux' -ModeID 1 -Verbose:$setVerbose @@ -1189,7 +1306,7 @@ if ($PSBoundParameters['GenerateMKVTagFile']) { Write-Host "The MKVToolnix suite is required to use the -GenerateMKVTagFile parameter" @errColors } else { - & $([Path]::Join($ScriptsDirectory, 'MatroskaTagGenerator.ps1')).toString() @GenerateMKVTagFile -Path $paths.OutputFile + & $([Path]::Join($ScriptsDirectory, 'MatroskaTagGenerator.ps1')).ToString() @GenerateMKVTagFile -Path $paths.OutputFile } } catch { @@ -1208,12 +1325,12 @@ $stopwatch.Stop() if ($PSBoundParameters['GenerateReport']) { $twoPass = ($PSBoundParameters['VideoBitrate'] -and $Pass -eq 2) ? $true : $false $params = @{ - DateTimes = @($startTime, $endTime) - Duration = $stopwatch - Paths = $paths - TwoPass = $twoPass - Encoder = $Encoder - Verbose = $setVerbose + DateTimes = @($startTime, $endTime) + Duration = $stopwatch + Paths = $paths + TwoPass = $twoPass + Encoder = $Encoder + Verbose = $setVerbose } Write-Report @params } From 82140327ff3c17d9f578e545ab964ce6196f5b83 Mon Sep 17 00:00:00 2001 From: patrickenfuego Date: Wed, 27 Jul 2022 22:23:43 -0400 Subject: [PATCH 07/10] fix bugs & increase thread usage Fixed autoformat bug that put a space between width,height. Replaced regex matching with .NET to reduce weird matching errors --- modules/FFTools/Public/Invoke-VMAF.ps1 | 123 ++++++++++++++----------- 1 file changed, 70 insertions(+), 53 deletions(-) diff --git a/modules/FFTools/Public/Invoke-VMAF.ps1 b/modules/FFTools/Public/Invoke-VMAF.ps1 index 61ce833..e43243a 100644 --- a/modules/FFTools/Public/Invoke-VMAF.ps1 +++ b/modules/FFTools/Public/Invoke-VMAF.ps1 @@ -41,11 +41,15 @@ function Invoke-VMAF { ) # Private internal function to parse dimensions from a string - function Get-Resolution ([string]$FilePath) { - $resolution = ffprobe -v error -select_streams v:0 -show_entries stream=width, height -of csv=s=x:p=0 $FilePath - - if ($resolution -match "(?\d+)x(?\d+)") { - [int]$width, [int]$height = $Matches.w, $Matches.h + function Get-Resolution ([string]$FilePath, [string]$Type) { + $resolution = ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 $FilePath + Write-Verbose "VMAF Resolution is: $resolution" + $match = [regex]::Matches($resolution, "(?\d+)x(?\d+).*") + if ($match) { + [int]$width = $match.Groups[1].Value + [int]$height = $match.Groups[2].Value + + Write-Verbose "VMAF Dimensions for $Type : $width`x$height" } else { Write-Error "VMAF: Regular expression failed to match dimensions. ffprobe may have returned a bad result" -ErrorAction Stop @@ -54,21 +58,18 @@ function Invoke-VMAF { return $width, $height } + Write-Verbose "Source Path: $Source" + Write-Verbose "Encode path: $Encode" + <# - SETUP NAMES + SETUP + Sanitize LogFormat if present Title - Current Directory #> - if ($Encode -match "(?.*(?:\\|\/)+)(?.*)\..*") { - $title = $Matches.oTitle -replace '\s', '_' - $currDirectory = $Matches.oRoot - } - else { - $title = 'title_vmaf' - $currDirectory = Split-Path $Encode -Parent - } + $LogFormat = $LogFormat.ToLower() + $title = Split-Path $Encode -LeafBase <# GATHER VIDEO INFORMATION @@ -79,12 +80,15 @@ function Invoke-VMAF { #> # Check if zscale is available with ffmpeg & set scaling args - $scale, $set = ($(ffmpeg 2>&1) -join ' ' -notmatch 'libzimg') ? 'scale', 'flags' : 'zscale', 'f' + $scale, $set = ($(ffmpeg 2>&1) -join ' ' -notmatch 'libzimg') ? + 'scale', 'flags' : + 'zscale', 'f' - # # Get the resolution of the encode (distorted) for cropping - $w, $h = Get-Resolution -FilePath $Encode # Get the resolution of the source (reference) file to verify if scaling was used - $sw, $sh = Get-Resolution -FilePath $Source + $sw, $sh = Get-Resolution -FilePath $Source -Type 'Source' + # # Get the resolution of the encode (distorted) for cropping + $w, $h = Get-Resolution -FilePath $Encode -Type 'Encode' + # Check for downscale - upscale encode back using lanczos if (($sw - $w) -gt 200) { $referenceString = "[0:v]crop=$($sw):$($sh)[reference]" @@ -112,10 +116,11 @@ function Invoke-VMAF { } <# - SET PATHS + SET PATHS & CPU COUNT Log path - model path + Model path + Collect number of CPUs to use as thread count (50% usage) #> # Get the parent directory of json model files @@ -130,30 +135,52 @@ function Invoke-VMAF { Write-Error "VMAF: Cannot locate json model file" -ErrorAction Stop } - $jsonLeaf = Split-Path $jsonFile -Leaf - # Perform all the path fuckery required by VMAF & ffmpeg on Windows - if ($env:OS -like "*Windows*") { - # Copy to temp for easier path manipulation - Copy-Item -Path $jsonFile -Destination 'C:\Temp' - # Set true paths in temp - $jsonTmpPath = [Path]::Join('C:\Temp', $jsonLeaf) - $logTmpPath = [Path]::Join('C:\Temp', "$title`_vmaf_log.json") - # Escape path in the special way required by libvmaf: C\\:/Temp/file.json - $tmpRoot = (Split-Path $jsonTmpPath -Qualifier) -replace ':', '\\:' - $tmpPath = (Split-Path $jsonTmpPath -NoQualifier) -replace '\\', '/' - $modelPath = "$tmpRoot$tmpPath" - $logPath = "$tmpRoot/Temp/$title`_vmaf_log.json" - } - elseif ($IsLinux -or $isMacOS) { - Copy-Item $jsonFile -Destination '/tmp' - $modelPath = [Path]::Join('/tmp', $jsonLeaf) - $logPath = [Path]::Join('/tmp', "$title`_vmaf_log.json") + if ($IsWindows) { + # Set the model path and format for libvmaf + $modelDrive = (Split-Path $jsonFile -Qualifier) -replace ':', '\\:' + $modelPath = (Split-Path $jsonFile -NoQualifier) -replace '\\', '/' + $modelPath = "$modelDrive$modelPath" + + $logDrive = (Split-Path $Encode -Qualifier) -replace ':', '\\:' + $logPath = (Split-Path $Encode -Parent) + $logPath = (Split-Path $logPath -NoQualifier) -replace '\\', '/' + $logPath = "$logDrive$logPath/$title`_vmaf_log.json" + + # Get CPU count. Use ~ 50% of system capability + $coreCount = Get-CimInstance Win32_Processor | Select-Object -ExpandProperty NumberOfCores + } + elseif ($IsLinux -or $IsMacOS) { + $modelPath = $jsonFile + $logPath = [Path]::Join($(Split-Path $Encode -Parent), "$title`_vmaf_log.json") + + # Get CPU count. Use ~ 50% of system capability + $cpuStr = $IsLinux ? + (grep 'cpu cores' /proc/cpuinfo | uniq) : + (sysctl -a | grep machdep.cpu.core_count) + + $countStr = [regex]::Match($cpuStr, '\d+') | + if ($countStr) { + if (($coreCount.Value -as [int]) -is [int]) { + $coreCount = $countStr.Value + } + else { + Write-Warning "CPU count did not return an integer. Defaulting to 4 threads" + $coreCount = 4 + } + } + else { + Write-Host "Could not detect the number of CPUs on the system. Defaulting to 4 threads" + $coreCount = 4 + } } else { Write-Error "Unknown Operating System detected. Exiting script" -ErrorAction Stop } + Write-Verbose "VMAF Model Path: $modelPath" + Write-Verbose "VMAF Log Path: $logPath" + <# Set VMAF string Add features @@ -161,7 +188,7 @@ function Invoke-VMAF { #> # Set the VMAF string with options and paths - $vmafStr = "log_fmt=json:log_path=$logPath`:model_path=$modelPath" + $vmafStr = "log_fmt=$LogFormat`:log_path=$logPath`:model_path=$modelPath`:n_threads=$coreCount" if ($SSIM) { $vmafStr += ":feature=name=ssim" } @@ -179,21 +206,11 @@ function Invoke-VMAF { -f null - Write-Verbose "Last Exit Code: $LASTEXITCODE" - if ($LASTEXITCODE) { - Write-Host "`nAnalysis complete! " -NoNewline @successColors + if ($LASTEXITCODE -ne 1) { + Write-Host "`nAnalysis complete!" -NoNewline @successColors + Write-Host "The log file can be found at: $($ul)$($aMagenta)$logPath" } else { Write-Host "The exit code for ffmpeg indicates that a problem occurred. " @warnColors -NoNewline } - # Copy log path back to main directory and remove temp paths - Copy-Item $logTmpPath -Destination $currDirectory -ErrorAction SilentlyContinue - $success = $? - if ($psReq) { - $success ? (Write-Host "VMAF log has been copied to $($italicOn+$aMagenta)`u{201C}$currDirectory`u{201D}$($reset)") : - (Write-Host "There was an issue copying the log file back from the temp directory" @warnColors) - } - else { - $success ? (Write-Host "VMAF log has been copied to `u{201C}$currDirectory`u{201D}" @progressColors) : - (Write-Host "There was an issue copying the log file back from the temp directory" @warnColors) - } } \ No newline at end of file From f19826c8c798fa7e556b9768c170622615ad0628 Mon Sep 17 00:00:00 2001 From: patrickenfuego Date: Wed, 27 Jul 2022 22:24:05 -0400 Subject: [PATCH 08/10] fix multiple ModeID line bug --- modules/FFTools/Utils/Invoke-MkvMerge.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/FFTools/Utils/Invoke-MkvMerge.ps1 b/modules/FFTools/Utils/Invoke-MkvMerge.ps1 index 22663e1..4e708b9 100644 --- a/modules/FFTools/Utils/Invoke-MkvMerge.ps1 +++ b/modules/FFTools/Utils/Invoke-MkvMerge.ps1 @@ -52,6 +52,7 @@ function Invoke-MkvMerge { 3 { '0:1,0:2,0:3' } 4 { '0:1,0:2,0:3,0:4' } 5 { '0:1,0:2,0:3,0:4,0:5' } + 6 { '0:1,0:2,0:3,0:4,0:5,0:6' } } $trackOrder = switch ($ModeID) { @@ -84,8 +85,6 @@ function Invoke-MkvMerge { "0:$($Paths.Language)" '--track-name' ($ModeID -in 2, 3) ? "0:$($trackTitle['DeeTitle'])" : "0:$($trackTitle['StereoTitle'])" - ($ModeID -in 2, 3) ? "0:$($trackTitle['DeeTitle'])" : "0:$($trackTitle['StereoTitle'])" - ($ModeID -in 2, 3) ? "0:$($trackTitle['DeeTitle'])" : "0:$($trackTitle['StereoTitle'])" '(' "$($Paths.Audio)" ')' From 90dc7ad9f63b866354c396727ff6bef94c9879f1 Mon Sep 17 00:00:00 2001 From: patrickenfuego Date: Thu, 28 Jul 2022 20:56:58 -0400 Subject: [PATCH 09/10] simplify unsharp strength validation --- FFEncoder.ps1 | 39 +++++++-------------------------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/FFEncoder.ps1 b/FFEncoder.ps1 index e31655e..cc1ce92 100644 --- a/FFEncoder.ps1 +++ b/FFEncoder.ps1 @@ -529,7 +529,7 @@ param ( [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] - [ArgumentCompletions( + [ValidateSet( 'sharpen_mild', 'sharpen_medium', 'sharpen_strong', 'blur_mild', 'blur_medium', 'blur_strong' )] @@ -914,23 +914,13 @@ if (!$PSBoundParameters['VapoursynthScript']) { } # Verify unsharp params and prompt for new value if necessary - if ($PSBoundParameters['UnsharpStrength'] -and !$PSBoundParameters['Unsharp']) { - Write-Warning "No value was passed for -Unsharp. Using default: luma_small" - $Unsharp = 'luma_small' - } - elseif ($PSBoundParameters['Unsharp'] -and !$PSBoundParameters['UnsharpStrength']) { - if ($Unsharp -notlike 'custom=*') { - Write-Warning "No value was passed for -UnsharpStrength. Using default: sharpen_mild" - $UnsharpStrength = 'sharpen_mild' - } - } - elseif ($PSBoundParameters['Unsharp']) { + if ($PSBoundParameters['Unsharp']) { $unsharpSet = @('luma_small', 'luma_medium', 'luma_large', 'chroma_small', 'chroma_medium', 'chroma_large', 'yuv_small', 'yuv_medium', 'yuv_large') if ($Unsharp -notin $unsharpSet -and $Unsharp -notlike 'custom=*') { - $unsharpOptions = ($unsharpSet + 'custom=') | + $unsharpOptions = ($unsharpSet + 'custom=') | Join-String -Separator "`r`n`t`u{2022} " ` -OutputPrefix "$($boldOn) Valid options for Unsharp$($boldOff):`n`t`u{2022} " Write-Host "Invalid option entered for Unsharp:`n$unsharpOptions" @@ -944,25 +934,10 @@ if (!$PSBoundParameters['VapoursynthScript']) { $Unsharp = Read-TimedInput @params Write-Host "" } - } - elseif ($PSBoundParameters['UnsharpStrength'] -and $Unsharp -notlike 'custom=*') { - $strengthSet = @('sharpen_mild', 'sharpen_medium', 'sharpen_strong', - 'blur_mild', 'blur_medium', 'blur_strong') - - if ($UnsharpStrength -notin $strengthSet) { - $strengthOptions = $strengthSet | - Join-String -Separator "`r`n`t`u{2022} " ` - -OutputPrefix "$($boldOn) Valid options for Unsharp Strength$($boldOff):`n`t`u{2022} " - Write-Host "Invalid option entered for Unsharp strength:`n$strengthOptions" - $params = @{ - Prompt = 'Enter a valid option: ' - Timeout = 50000 - Mode = 'Select' - Count = 4 - InputObject = $strengthSet - } - $UnsharpStrength = Read-TimedInput @params - Write-Host "" + + if (!$PSBoundParameters['UnsharpStrength'] -and $Unsharp -notlike 'custom=*') { + Write-Warning "No value was passed for -UnsharpStrength. Using default: sharpen_mild" + $UnsharpStrength = 'sharpen_mild' } } # Collect the arguments into a hashtable From a56ccb83532c7036ce6386acdb2857dc8077e7be Mon Sep 17 00:00:00 2001 From: patrickenfuego Date: Thu, 28 Jul 2022 21:45:29 -0400 Subject: [PATCH 10/10] Update documentation --- FFEncoder.ps1 | 11 ++++++++++- README.md | 54 ++++++++++++++++++++++++++++----------------------- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/FFEncoder.ps1 b/FFEncoder.ps1 index cc1ce92..30cae25 100644 --- a/FFEncoder.ps1 +++ b/FFEncoder.ps1 @@ -191,6 +191,15 @@ Filtering method used for rescaling input with the -Scale parameter. Compatible arguments: - scale: fast_bilinear, neighbor, area, gauss, sinc, spline, bilinear, bicubic, lanczos - zscale: point, spline16, spline36, bilinear, bicubic, lanczos + .PARAMETER Unsharp + Enable the unsharp filter and specify the search range. Use one of the presets specified in the project wiki, in the form: + _ + or pass a custom filter string as: + 'custom=' + Mandatory parameter for sharpening/blurring a video source. + + .PARAMETER UnsharpStrength + Sets the strength of the unsharp filter. Use one of the presets defined in the project wiki, in the form: _ .PARAMETER Resolution Upscale/downscale resolution used with the -Scale parameter. Default value is 1080p (1920 x 1080) .PARAMETER SkipDolbyVision @@ -252,7 +261,7 @@ param ( } )] [Alias('VSScript', 'VPY')] - [string]$VapoursynthScript, + [string]$VapourSynthScript, [Parameter(Mandatory = $false, ParameterSetName = 'CRF')] [Parameter(Mandatory = $false, ParameterSetName = 'PASS')] diff --git a/README.md b/README.md index e0f1f0f..7158dc2 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ # FFEncoder -FFEncoder is a cross-platform PowerShell script and module that is meant to make high definition video encoding workflows easier. FFEncoder uses [ffmpeg](https://ffmpeg.org/), [ffprobe](https://ffmpeg.org/ffprobe.html), the [x264 H.264 encoder](https://x264.org/en/), and the [x265 HEVC encoder](https://x265.readthedocs.io/en/master/index.html) to compress video files for streaming or archiving. +FFEncoder is a cross-platform PowerShell script and module that is meant to make high definition video encoding workflows easier. FFEncoder uses [ffmpeg](https://ffmpeg.org/), [VapourSynth](https://www.vapoursynth.com/doc/), [Mkvtoolnix](https://mkvtoolnix.download/), [ffprobe](https://ffmpeg.org/ffprobe.html), the [x264 H.264 encoder](https://x264.org/en/), and the [x265 HEVC encoder](https://x265.readthedocs.io/en/master/index.html) to compress, filter, and multiplex multimedia files for streaming or archiving. - [FFEncoder](#ffencoder) - [About](#about) @@ -48,7 +48,8 @@ Check out the [wiki](https://github.com/patrickenfuego/FFEncoder/wiki) for addit - ffmpeg / ffprobe - PowerShell v. 7.0 or newer -- [Mkvtoolnix](https://mkvtoolnix.download/) (optional, but highly recommended) +- Mkvtoolnix (optional, but highly recommended) +- VapourSynth (optional) The script requires PowerShell 7.0 or newer on all systems as it utilizes new parallel processing features introduced in this version. Multi-threading prior to PowerShell 7 was prone to memory leaks which persuaded me to make the change. @@ -167,14 +168,15 @@ FFEncoder can accept the following parameters from the command line: > An Asterisk \* denotes that the parameter is mandatory only for its given parameter set (for example, you can choose either `-CRF` or `-VideoBitrate` for rate control, but not both): -| Parameter Name | Default | Mandatory | Alias | Description | Mandatory For | -| ---------------- | ------- | ------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------- | -| **InputPath** | N/A | True | **I**, **Source**, **Reference** | The path to the source file, i.e. remux. Also acts as the reference path for VMAF comparisons | All | -| **OutputPath** | N/A | True | **O**, **Encode**, **Distorted** | The path of the the encoded output file, or the encoded (distorted) file path during VMAF comparisons | All | -| **CRF** | N/A | \*True | **C** | Rate control parameter that targets a specific quality level. Ranges from 0.0 to 51.0. Lower values result in higher quality | Rate Control | -| **VideoBitrate** | N/A | \*True | **VBitrate** | Rate control parameter that targets a specific bitrate. Can be used as an alternative to CRF when file size is a priority | Rate Control | -| **ScaleFilter** | None | \*True | **Resize**, **Resample** | Scaling filter to use. Scaling options are `scale` (ffmpeg default) and `zscale` (requires `libzimg`) | Resizing | -| **CompareVMAF** | N/A | \*True | None | Runs a VMAF comparison on two video files | VMAF | +| Parameter Name | Default | Mandatory | Alias | Description | Mandatory For | +| ---------------- | ------- | ------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| **InputPath** | N/A | True | **I**, **Source**, **Reference** | The path to the source file, i.e. remux. Also acts as the reference path for VMAF comparisons | All | +| **OutputPath** | N/A | True | **O**, **Encode**, **Distorted** | The path of the the encoded output file, or the encoded (distorted) file path during VMAF comparisons | All | +| **CRF** | N/A | \*True | **C** | Rate control parameter that targets a specific quality level. Ranges from 0.0 to 51.0. Lower values result in higher quality | Rate Control | +| **VideoBitrate** | N/A | \*True | **VBitrate** | Rate control parameter that targets a specific bitrate. Can be used as an alternative to CRF when file size is a priority | Rate Control | +| **Scale** | None | \*True | **ScaleType**, **SF** | Scaling/resizing filter to use. See [Rescaling Video](https://github.com/patrickenfuego/FFEncoder/wiki/Video-Options#rescaling-videos) for more info | Scaling | +| **Unsharp** | None | \*True | **U** | Enable unsharp filter and set search range, in the form `_` or `custom=` | Sharpen/Blur | +| **CompareVMAF** | N/A | \*True | None | Flag to enable a VMAF comparison on two video files | VMAF | ### Utility @@ -206,23 +208,27 @@ FFEncoder can accept the following parameters from the command line: ### Video Filtering -| Parameter Name | Default | Mandatory | Alias | Description | -| --------------- | ---------------- | --------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Deinterlace** | Disabled | False | **DI** | Switch to enable deinterlacing of interlaced content using yadif | -| **NLMeans** | Disabled | False | **NL** | High quality de-noising filter. Accepts a hashtable containing 5 values. See [here](https://ffmpeg.org/ffmpeg-filters.html#nlmeans-1) for more info | -| **Scale** | bilinear | False | **ScaleType**, **SF** | Scaling/resizing filter to use. See [Rescaling Video](https://github.com/patrickenfuego/FFEncoder/wiki/Video-Options#rescaling-videos) for more info | -| **Resolution** | Source Dependent | False | **Res**, **R** | Scaling resolution. See [Rescaling Video](https://github.com/patrickenfuego/FFEncoder/wiki/Video-Options#rescaling-videos) for more info | +> See the Mandatory section above for parameters needed to enable certain filters + +| Parameter Name | Default | Mandatory | Alias | Description | +| ------------------- | ---------------- | --------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Deinterlace** | Disabled | False | **DI** | Switch to enable deinterlacing of interlaced content using yadif | +| **NLMeans** | Disabled | False | **NL** | High quality de-noising filter. Accepts a hashtable containing 5 values. See [here](https://ffmpeg.org/ffmpeg-filters.html#nlmeans-1) for more info | +| **Scale** | bilinear | False | **ScaleType**, **SF** | Scaling/resizing filter to use. See [Rescaling Video](https://github.com/patrickenfuego/FFEncoder/wiki/Video-Options#rescaling-videos) for more info | +| **Resolution** | Source Dependent | False | **Res**, **R** | Scaling resolution. See [Rescaling Video](https://github.com/patrickenfuego/FFEncoder/wiki/Video-Options#rescaling-videos) for more info | +| **UnsharpStrength** | luma_mild | False | **UStrength** | Specify the unsharp filters strength, in the form `_` | ### Encoder Config -| Parameter Name | Default | Mandatory | Alias | Description | -| ------------------- | ------------ | --------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Encoder** | x265 | False | **Enc** | Specifies which encoder to use - x264 or x265 | -| **FirstPassType** | Default | False | **PassType**, **FTP** | Tuning option for two pass encoding. See [Two Pass Encoding Options](https://github.com/patrickenfuego/FFEncoder/wiki/Video-Options#two-pass-encoding-options) for more info | -| **SkipDolbyVision** | False | False | **NoDV**, **SDV** | Switch to disable Dolby Vision encoding, even if metadata is present | -| **SkipHDR10Plus** | False | False | **No10P**, **NTP** | Switch to disable HDR10+ encoding, even if metadata is present | -| **TestFrames** | 0 (Disabled) | False | **T**, **Test** | Integer value representing the number of test frames to encode. When `-TestStart` is not set, encoding starts at 00:01:30 so that title screens are skipped | -| **TestStart** | Disabled | False | **Start**, **TS** | Starting point for test encodes. Accepts formats `00:01:30` (sexagesimal time), `200f` (frame start), `200t` (decimal time in seconds) | +| Parameter Name | Default | Mandatory | Alias | Description | +| --------------------- | ------------ | --------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Encoder** | x265 | False | **Enc** | Specifies which encoder to use - x264 or x265 | +| **FirstPassType** | Default | False | **PassType**, **FTP** | Tuning option for two pass encoding. See [Two Pass Encoding Options](https://github.com/patrickenfuego/FFEncoder/wiki/Video-Options#two-pass-encoding-options) for more info | +| **SkipDolbyVision** | False | False | **NoDV**, **SDV** | Switch to disable Dolby Vision encoding, even if metadata is present | +| **SkipHDR10Plus** | False | False | **No10P**, **NTP** | Switch to disable HDR10+ encoding, even if metadata is present | +| **TestFrames** | 0 (Disabled) | False | **T**, **Test** | Integer value representing the number of test frames to encode. When `-TestStart` is not set, encoding starts at 00:01:30 so that title screens are skipped | +| **TestStart** | Disabled | False | **Start**, **TS** | Starting point for test encodes. Accepts formats `00:01:30` (sexagesimal time), `200f` (frame start), `200t` (decimal time in seconds) | +| **VapourSynthScript** | Disabled | False | **VSScript**, **VPY** | Path to VapourSynth script. Video filtering parameters are ignored when enabled, and must be done in the vpy script | ### Universal Encoder Settings