From 4dce237600cc1511677b498f77e969a85dc76145 Mon Sep 17 00:00:00 2001 From: Jonatan Nilsson Date: Mon, 27 Sep 2021 21:43:03 +0000 Subject: [PATCH] More development --- spav1an.ps1 | 236 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 160 insertions(+), 76 deletions(-) diff --git a/spav1an.ps1 b/spav1an.ps1 index d79c475..22bbe62 100644 --- a/spav1an.ps1 +++ b/spav1an.ps1 @@ -3,13 +3,12 @@ param ( [String] $FFmpegPath = "ffmpeg.exe", [String] $FFprobePath = "ffprobe.exe", [int] $EncoderType = 1, # Encoder type. 1 = aomenc, 2 = SVT-AV1 - [int] $Renderer = 1, # Frame type, 1 = avisynth + [int] $Renderer = 1, # Frame type, 1 = ffmpeg native, 2 = avisynth [int] $MinChunkSec = 10, [int] $Threads = 0, - [String] $Framerate = "24000/1001", - [String] $EncOptions = "", - [String] $AnalyzeVideoSize = "848x480", - [String] $AnalyzeVideoFFmpeg = "-hide_banner -loglevel info -i {0} -map 0:0 -vf zscale={1}:f=spline36 -vcodec libx264 -preset veryfast -crf 40 -x264-params keyint={2}:min-keyint=5:scenecut=40 -y -pix_fmt yuv420p {3}", + [String] $Framerate = "auto", + [String] $AnalyzeVidSize = "848x480", + [String] $AnalyzeVidEnc = "-hide_banner -loglevel info -i {0} -map 0:0 -vf zscale={1}:f=spline36 -vcodec libx264 -preset veryfast -crf 40 -x264-params keyint={2}:min-keyint=5:scenecut=40 -y -pix_fmt yuv420p {3}", [String] $AvisynthScript = "LSMASHVideoSource({0})", [String] $EncoderOptions = "--cpu-used=8 --passes=1 --cq-level=10 --rt -o {0} -", [parameter(Position=0)] [String] $InputFile = "" @@ -43,18 +42,39 @@ Write-Host "" function Write-OurHelp { Write-Host "" Write-Host "S. Powershell variant of av1an" - Write-Host "Usage: .\spav1an.ps1 [options] [INPUT_FILE]" + Write-Host "Usage: .\spav1an.ps1 [options] INPUT_FILE" Write-Host "" Write-Host "Main options:" - Write-Host " -EncoderType 1,2 Select which encoder type we are using: " - Write-Host " 1 = aomenc encoder" - Write-Host " 2 = SVT-AV1 encoder" - Write-Host " -Renderer 1 Select which frame renderer we are gonna be using." - Write-Host " 1 = avisynth" - Write-Host " -Framerate rate/scale Frame rate of the video" + Write-Host " -EncoderOptions The encoding options for the encoder" + Write-Host " -EncoderType 1,2 Select which encoder type we are using: " + Write-Host " 1 = aomenc encoder" + Write-Host " 2 = SVT-AV1 encoder" + Write-Host " -Renderer 1,2 Select which frame renderer we are gonna be" + Write-Host " using for splitting the file:" + Write-Host " 1 = ffmpeg (uses -ss)" + Write-Host " 2 = avisynth" + Write-Host " -MinChunkSec 10 The minimum amount of seconds each chunk" + Write-Host " encode shouldbe at least." + Write-Host " -Threads 0 Total amount of workers to use. Default 0" + Write-Host " will use the total of logical processors" + Write-Host " -Framerate auto Frame rate of the video. Auto will use ffprobe" + Write-Host " to autodect. Otherwise you can specify it using" + Write-Host " rate/scale" Write-Host "" + Write-Host "Analyze options:" + Write-Host "The analyze file will be used to generate scenecut changes as well as other" + Write-Host "keyframe data that will be used for splitting the video into chunks." + Write-Host " -AnalyzeVidSize widxhei The width and height of the analyze video" + Write-Host " Smaller size might be more innacurate but" + Write-Host " will be faster to encode" + Write-Host " -AnalyzeVidEnc The encoding options for the analyze video." + Write-Host " By default this is x264 veryfast crf 40" } - +<# +------------------------------------------------------------------- +--------------- Verify all the parameters look okay -------------- +------------------------------------------------------------------- +#> function Assert-OurParameters { try { $gobble = Start-ThreadJob -ScriptBlock {} @@ -66,6 +86,8 @@ function Assert-OurParameters { $writehelp = $false if ($inputfile -eq "") { + Write-Host "Error: Inputfile is missing" + Write-Host "" $writehelp = $true } if ($InputFile -notmatch '[^.]+\..+') { @@ -73,21 +95,31 @@ function Assert-OurParameters { Write-Host "" $writehelp = $true } - if ($Framerate -notmatch '\d+/\d+') { - Write-Host "Error: -Framerate has to be in a format of /" + if ($Framerate -notmatch '\d+/\d+' -and $Framerate -ne 'auto') { + Write-Host "Error: -Framerate has to be in a format of / or auto" Write-Host " Example: -Framerate 24000/1001" Write-Host " Example: -Framerate 25/1" + Write-Host " Example: -Framerate auto" Write-Host "" $writehelp = $true } - if ($AnalyzeVideoSize -notmatch '\d+x\d+') { + if ($AnalyzeVidSize -notmatch '\d+x\d+') { Write-Host "Error: -AnalyzeVideoSize has to be in a format of x" Write-Host " Example: -AnalyzeVideoSize 1280x720" Write-Host " Example: -AnalyzeVideoSize 848x480" Write-Host "" $writehelp = $true } - if (($EncoderType -ne 1 -and $EncoderType -ne 2) -or $Renderer -ne 1) { + if ($EncoderType -ne 1 -and $EncoderType -ne 2) { + Write-Host "Error: Unknown encoder type of $EncoderType." + Write-Host "Only values of 1 and 2 are supported yet." + Write-Host "" + $writehelp = $true + } + if ($Renderer -ne 1 -and $Renderer -ne 2) { + Write-Host "Error: Unknown renderer type of $Renderer." + Write-Host "Only values of 1 and 2 are supported yet." + Write-Host "" $writehelp = $true } @@ -97,40 +129,84 @@ function Assert-OurParameters { } } -function Write-AnalyzeFile { - $identifier1 = '2Nhd6VgWZr69B4kzAStydVGCcVugprCpJR' - $identifier2 = 'jh2Acz3nR3XjKrzqPiE3ZBANYYRtEHeqr4' - - $splitsize = $AnalyzeVideoSize.Split('x') - $vfsize = "w=" + $splitsize[0] + ":h=" + $splitsize[1] - $AnalyzeVideoFFmpeg = $AnalyzeVideoFFmpeg -f $identifier1, $vfsize, $maxkeyint, $identifier2 - $analyzeffmpegSplitted = $AnalyzeVideoFFmpeg.Split(' ') - - for ($i = 0; $i -le $analyzeffmpegSplitted.Length; $i++) { - if ($analyzeffmpegSplitted[$i] -match $identifier1) { - $analyzeffmpegSplitted[$i] = $analyzeffmpegSplitted[$i] -replace $identifier1, $InputFile - } elseif ($analyzeffmpegSplitted[$i] -match $identifier2) { - $analyzeffmpegSplitted[$i] = $analyzeffmpegSplitted[$i] -replace $identifier2, $analyzefilename +function Rename-ArrayIdentifiers([String[]] $InputArr, [String] $Identifier1, [String] $ReplaceWith1, [String] $Identifier2, [String] $ReplaceWith2) { + for ($i = 0; $i -le $InputArr.Length; $i++) { + if ($InputArr[$i] -match $Identifier1) { + $InputArr[$i] = $InputArr[$i] -replace $Identifier1, $ReplaceWith1 + } elseif ($Identifier2 -and $InputArr[$i] -match $Identifier2) { + $InputArr[$i] = $InputArr[$i] -replace $Identifier2, $ReplaceWith2 } } + return $InputArr +} + +<# +------------------------------------------------------------------- +------- Create our project files -------- +------------------------------------------------------------------- +#> +function Write-ProjectFile { + # identifier for text replacement 1 + $identifier1 = '2Nhd6VgWZr69B4kzAStydVGCcVugprCpJR' + # identifier for text replacement 2 + $identifier2 = 'jh2Acz3nR3XjKrzqPiE3ZBANYYRtEHeqr4' + + Write-Host "Project file was not found. Creating a project file." + $analyzefilename = $workproject + '_analyze.mkv' + + # Check if framerate is auto and grab it from the source file + if ($Framerate -eq 'auto') { + $fpsPropeArgs = "-v error -select_streams v -of default=noprint_wrappers=1:nokey=1 -show_entries stream=r_frame_rate $identifier1".Split(' ') + $fpsPropeArgs = Rename-ArrayIdentifiers $fpsPropeArgs $identifier1 $InputFile + $Framerate = Invoke-NativeCommand -FilePath $FFprobePath -ArgumentList $fpsPropeArgs | Receive-RawPipeline + } + + # Split the framerate (rate/scale) to their respective values and calculate + # the max keyint from that + $FramerateSplit = $Framerate.Split('/') + $fps = [convert]::ToDouble($FramerateSplit[0]) / [convert]::ToDouble($FramerateSplit[1]) + $maxkeyint = [int][Math]::Ceiling($fps * 10) + + # Split the video size string of 'widthXheight' + $splitsize = $AnalyzeVidSize.Split('x') + $vfsize = "w=" + $splitsize[0] + ":h=" + $splitsize[1] + + # Create our command line + # Reason to use identifier isntead of using filename directly is cause we + # wanna split by space to get array argument list and that might split + # the file pathname. + $AnalyzeVidEnc = $AnalyzeVidEnc -f $identifier1, $vfsize, $maxkeyint, $identifier2 + + $analyzeffmpegSplitted = Rename-ArrayIdentifiers $AnalyzeVidEnc.Split(' ') $identifier1 $InputFile $identifier2 $analyzefilename Write-Host "Creating $analyzefilename to generate keyframe split data from" Write-Host "" Write-Host "$FFmpegPath $analyzeffmpegSplitted" - - Invoke-NativeCommand -FilePath $FFmpegPath -ArgumentList $analyzeffmpegSplitted | Receive-RawPipeline Write-Host "" -} -function Write-ProjectFile { + # Create our analyze file + # Reason we use Invoke-NativeCommand is "ffmpeg.exe" if located in the same + # folder as script will make the Invoke-Expression fail as it requires .\ in + # front of it. Invoke-NativeCommand allows us to specify filepath and it will + # run regardless + # Invoke-NativeCommand -FilePath $FFmpegPath -ArgumentList $analyzeffmpegSplitted | Receive-RawPipeline + Write-Host "" + Write-Host "Reading keyframe data from $analyzefilename" Write-Host "" $analyzekeyframefilename = $workproject + '_kf.txt' - Invoke-Expression ".\$FFprobePath -hide_banner -show_entries packet=pts_time,flags -select_streams v:0 -of csv=print_section=0 $analyzefilename > $analyzekeyframefilename" - Write-Host "" - $videodata = Get-Content $analyzekeyframefilename + + # Start probing and analyzing our analyze file + $analyzePropeArgs = "-hide_banner -show_entries packet=pts_time,flags -select_streams v:0 -of csv=print_section=0 $identifier1".Split(' ') + $analyzePropeArgs = Rename-ArrayIdentifiers $analyzePropeArgs $identifier1 $InputFile + $videodata = Invoke-NativeCommand -FilePath $FFprobePath -ArgumentList $analyzePropeArgs | Receive-RawPipeline + + # Invoke-Expression ".\$FFprobePath -hide_banner -show_entries packet=pts_time,flags -select_streams v:0 -of csv=print_section=0 $analyzefilename > $analyzekeyframefilename" + # Write-Host "" + $project = New-Object 'System.Collections.Generic.List[KeyFrameData]' $keyframes = New-Object 'System.Collections.Generic.List[KeyFrameData]' + for ($i = 0; $i -lt $videodata.Length; $i++) { $split = $videodata[$i].Split(',') if ($split.Count -gt 1 -and $split[1][0] -eq 'K') { @@ -138,6 +214,7 @@ function Write-ProjectFile { } } + # If there are too few frames, the user might as well just encode directly if ($keyframes.Count -lt 2) { Write-Host "Too few keyframes found, exiting" exit @@ -170,15 +247,23 @@ function Write-ProjectFile { $start.end = $videodata.Length - 1 $project.Add($start) - $projectoutput = '[' - $projectoutput += "`n [" + $project[0].start + ", " + $project[0].end + ", " + $project[0].startstamp + "]" + $projectoutput = '{' + $projectoutput += "`n `"fps`":`"" + $Framerate + "`"," + $projectoutput += "`n `"chunks`": [" + $projectoutput += "`n [" + $project[0].start + ", " + $project[0].end + ", " + $project[0].startstamp + "]" for ($i = 1; $i -lt $project.Count; $i++) { - $projectoutput += ",`n [" + $project[$i].start + ", " + $project[$i].end + ", " + $project[$i].startstamp + "]" + $projectoutput += ",`n [" + $project[$i].start + ", " + $project[$i].end + ", " + $project[$i].startstamp + "]" } - $projectoutput += "`n]" + $projectoutput += "`n ]" + $projectoutput += "`n}" Set-Content -Path $projectfile $projectoutput } +<# +------------------------------------------------------------------- +----------------------- Our encoding worker ----------------------- +------------------------------------------------------------------- +#> function Start-OurEncoding { Write-Host "Starting our chunk encoder with maximum $Threads workers" Write-Host "" @@ -217,24 +302,21 @@ function Start-OurEncoding { if ($null -eq $activeworkers[$x].job) { $chunkfile = $encodefile + '_' + $project[$i].start + '-' + $project[$i].end + '.ivf' - # Old avisynth version - <# - $trimcontent = $AvisynthScript -f ('"' + $InputFile + '"') - if ($Renderer -eq 1) { + if ($Renderer -eq 2) { + $trimcontent = $AvisynthScript -f ('"' + $InputFile + '"') $trimcontent += "`nTrim(" + $project[$i].start + ", " + $project[$i].end + ")" + $chunkavs = $encodefile + '_' + $project[$i].start + '-' + $project[$i].end + '.avs' + Set-Content -Path $chunkavs $trimcontent + + $ffmpegreadchunkparameters = @('-hide_banner', '-loglevel', 'warning', '-i', $chunkavs, '-map', '0:0', '-strict', '-1', '-f', 'yuv4mpegpipe', '-') + } elseif ($Renderer -eq 1) { + $sshours = [Math]::Floor($project[$i].startstamp / 3600) + $ssminutes = [Math]::Floor(($project[$i].startstamp % 3600) / 60) + $ssseconds = $project[$i].startstamp % 60 + $ssstart = "{0:00}:{1:00}:{2:00.000}" -f $sshours, $ssminutes, $ssseconds + + $ffmpegreadchunkparameters = @('-hide_banner', '-loglevel', 'warning', '-ss', $ssstart, '-i', $InputFile, '-map', '0:0', '-vframes', ($project[$i].end - $project[$i].start + 1), '-strict', '-1', '-f', 'yuv4mpegpipe', '-') } - $chunkavs = $encodefile + '_' + $project[$i].start + '-' + $project[$i].end + '.avs' - Set-Content -Path $chunkavs $trimcontent - - $ffmpegreadchunkparameters = @('-hide_banner', '-loglevel', 'warning', '-i', $chunkavs, '-map', '0:0', '-strict', '-1', '-f', 'yuv4mpegpipe', '-') - #> - - $sshours = [Math]::Floor($project[$i].startstamp / 3600) - $ssminutes = [Math]::Floor(($project[$i].startstamp % 3600) / 60) - $ssseconds = $project[$i].startstamp % 60 - $ssstart = "{0:00}:{1:00}:{2:00.000}" -f $sshours, $ssminutes, $ssseconds - - $ffmpegreadchunkparameters = @('-hide_banner', '-loglevel', 'warning', '-ss', $ssstart, '-i', $InputFile, '-map', '0:0', '-vframes', ($project[$i].end - $project[$i].start + 1), '-strict', '-1', '-f', 'yuv4mpegpipe', '-') $identifier1 = '5BdMoB52CQyrNYEof3gARsbLgsTjZCdqF9' $EncoderParameters = $EncoderOptions -f $identifier1, ($project[$i].end - $project[$i].start + 1) @@ -319,27 +401,29 @@ function Start-OurEncoding { Write-Host "Finished" } -Assert-OurParameters +try { + Assert-OurParameters -if ($Threads -lt 1) { - $Threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors + if ($Threads -lt 1) { + $Threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors + } + + $workproject = [io.path]::GetFileNameWithoutExtension($InputFile) + $projectfile = $workproject + '_spav1an.json' + + if (!(Test-Path $projectfile)) { + Write-ProjectFile + } +} +catch { + Write-Host "" + Write-Host "FATAL EXCEPTION OCCURED:" + Write-Host "" + Write-Host $_ + Write-Host $_.ScriptStackTrace } -$FramerateSplit = $Framerate.Split('/') -$fps = [convert]::ToDouble($FramerateSplit[0]) / [convert]::ToDouble($FramerateSplit[1]) -$workproject = [io.path]::GetFileNameWithoutExtension($InputFile) -$projectfile = $workproject + '_spav1an.json' - -if (!(Test-Path $projectfile)) { - Write-Host "Project file was not found. Creating a project file." - $analyzefilename = $workproject + '_analyze.mkv' - $maxkeyint = [int][Math]::Ceiling($fps * 10) - - # Write-AnalyzeFile - Write-ProjectFile -} - -Start-OurEncoding +# Start-OurEncoding # .\ffmpeg.exe -hide_banner -i gi_joe_720p_275.webm -map 0:0 -vf "zscale=w=1280:h=720:f=spline36" -pix_fmt yuv420p -strict -1 -f yuv4mpegpipe - | .\aomenc-3.0.0-335.exe --cpu-used=6 --cq-level=20 --bit-depth=8 -o gi_joe_test.webm -