diff --git a/aomenc-3.0.0-335.exe b/aomenc-3.0.0-335.exe new file mode 100644 index 0000000..46c39dc Binary files /dev/null and b/aomenc-3.0.0-335.exe differ diff --git a/spav1an.ps1 b/spav1an.ps1 new file mode 100644 index 0000000..d79c475 --- /dev/null +++ b/spav1an.ps1 @@ -0,0 +1,348 @@ +param ( + [String] $EncoderPath = "aomenc-3.0.0-335.exe", + [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] $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] $AvisynthScript = "LSMASHVideoSource({0})", + [String] $EncoderOptions = "--cpu-used=8 --passes=1 --cq-level=10 --rt -o {0} -", + [parameter(Position=0)] [String] $InputFile = "" +) +$ErrorActionPreference = "Stop" + +Add-Type @" +using System; +public struct KeyFrameData { + public int start; + public int end; + public decimal startstamp; +} +"@ + +Add-Type @" +using System; +public class ProjectChunkData { + public int index; + public int start; + public int end; + public decimal startstamp; + public bool finished; + public bool muxed; + public Object job; +} +"@ + +Write-Host "" + +function Write-OurHelp { + Write-Host "" + Write-Host "S. Powershell variant of av1an" + 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 "" +} + +function Assert-OurParameters { + try { + $gobble = Start-ThreadJob -ScriptBlock {} + } catch { + Write-Host "ThreadJob was not found, automatically installing for scope local user" + Install-Module -Name ThreadJob -Scope CurrentUser + Write-Host "Install complete" + } + $writehelp = $false + + if ($inputfile -eq "") { + $writehelp = $true + } + if ($InputFile -notmatch '[^.]+\..+') { + Write-Host "Error: Inputfile must have an extension" + Write-Host "" + $writehelp = $true + } + if ($Framerate -notmatch '\d+/\d+') { + Write-Host "Error: -Framerate has to be in a format of /" + Write-Host " Example: -Framerate 24000/1001" + Write-Host " Example: -Framerate 25/1" + Write-Host "" + $writehelp = $true + } + if ($AnalyzeVideoSize -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) { + $writehelp = $true + } + + if ($writehelp) { + Write-OurHelp + exit + } +} + +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 + } + } + + 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 { + 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 + $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') { + $keyframes.Add((New-Object KeyFrameData -Property @{ start = $i; startstamp = [convert]::ToDecimal($split[0]) })) + } + } + + if ($keyframes.Count -lt 2) { + Write-Host "Too few keyframes found, exiting" + exit + } + + $start = New-Object KeyFrameData -Property @{ + start = $keyframes[0].start; + startstamp = $keyframes[0].startstamp; + end = 0; + } + for ($i = 1; $i -lt $keyframes.Count; $i++) { + $length = ($keyframes[$i].start - 1) - $start.start + if ($length -gt $maxkeyint) { + if ($keyframes[$i - 1].start - 1 -gt $start.start) { + $start.end = $keyframes[$i - 1].start - 1 + $next = $keyframes[$i - 1] + if ($start.end - $start.start -lt $maxkeyint / 2) { + $start.end = $keyframes[$i].start - 1 + $next = $keyframes[$i] + } + $project.Add($start) + $start = New-Object KeyFrameData -Property @{ + start = $next.start; + startstamp = $next.startstamp; + end = 0; + } + } + } + } + $start.end = $videodata.Length - 1 + $project.Add($start) + + $projectoutput = '[' + $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]" + Set-Content -Path $projectfile $projectoutput +} + +function Start-OurEncoding { + Write-Host "Starting our chunk encoder with maximum $Threads workers" + Write-Host "" + $encodefile = $workproject + '_output' + $projectraw = Get-Content -Path $projectfile | ConvertFrom-Json + $project = New-Object 'System.Collections.Generic.List[ProjectChunkData]' + for ($i = 0; $i -lt $projectraw.Count; $i++) { + $project.Add((New-Object ProjectChunkData -Property @{ + index = $i + 1; + start = $projectraw[$i][0]; + end = $projectraw[$i][1]; + startstamp = $projectraw[$i][2]; + finished = $false; + muxed = $false; + job = $null; + })) + } + + $chunkformat = "" + $lastChunkFrame = $project.Count.ToString() + for ($x = 0; $x -lt $lastChunkFrame.Length; $x++) { + $chunkformat += '0' + } + $chunkformat = "[{0:$chunkformat}/{1:$chunkformat}]" + + $activeworkers = New-Object 'System.Collections.Generic.List[ProjectChunkData]' + for ($i = 0; $i -lt $Threads; $i++) { + $activeworkers.Add([ProjectChunkData]@{}) + } + + $chunklength = $lastChunkFrame.Length * 2 + 3 + + for ($i = 0; $i -lt $project.Count) { + $startedchunk = $false + for ($x = 0; $x -lt $activeworkers.Count; $x++) { + 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) { + $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', '-') + #> + + $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) + $EncoderParametersSplitted = $EncoderParameters.Split(' ') + + for ($e = 0; $e -le $EncoderParametersSplitted.Length; $e++) { + if ($EncoderParametersSplitted[$e] -match $identifier1) { + $EncoderParametersSplitted[$e] = $EncoderParametersSplitted[$e] -replace $identifier1, $chunkfile + } + } + + "Encoding chunk " + ($chunkformat -f $project[$i].index, $project.Count) + " " + $project[$i].start + ' - ' + $project[$i].end + ' (' + ($project[$i].end - $project[$i].start + 1) + ')' + + $job = Start-ThreadJob -ArgumentList @($FFmpegPath, $ffmpegreadchunkparameters, $EncoderPath, $EncoderParametersSplitted, $i) -ScriptBlock { + $lecommand = $args[0] + ' ' + $args[1] + ' | ' + $args[2] + ' ' + $args[3] + $lecommand + cmd.exe /C $lecommand + } + $project[$i].job = $job + + $activeworkers[$x] = $project[$i] + $i++ + $startedchunk = $true + break + } + } + + if ($startedchunk -eq $false) { + $chunkfinished = $false + while ($chunkfinished -eq $false) { + $longest = 15 + $chunklength + for ($x = 0; $x -lt $activeworkers.Count; $x++) { + if ($null -ne $activeworkers[$x].job) { + $currentprogress = ($activeworkers[$x].job.Error | Select-Object -Last 1) + if ($null -ne $currentprogress) { + $longest = [Math]::Max($longest, $currentprogress.ToString().Length + 15 + $chunklength) + } + ($chunkformat -f $activeworkers[$x].index, $project.Count) + ' ' + $activeworkers[$x].job.State + ' ' + $currentprogress + if ($activeworkers[$x].job.State -eq 'Completed') { + # $activeworkers[$x].job + $activeworkers[$x].job = $null + $activeworkers[$x].finished = $true + $chunkfinished = $true + } + } + } + Start-Sleep 1 + [Console]::SetCursorPosition(0, $Host.UI.RawUI.CursorPosition.Y - $activeworkers.Count) + for ($x = 0; $x -lt $activeworkers.Count; $x++) { + [Console]::WriteLine(("{0,-" + ($longest) + "}") -f " ") + } + [Console]::SetCursorPosition(0, $Host.UI.RawUI.CursorPosition.Y - $activeworkers.Count) + } + } + } + Write-Host "Waiting for jobs to finish" + $jobsleft = 1 + while ($jobsleft -gt 0) { + $longest = 15 + $chunklength + $jobsleft = 0 + for ($x = 0; $x -lt $activeworkers.Count; $x++) { + if ($null -ne $activeworkers[$x].job) { + $jobsleft++ + $currentprogress = ($activeworkers[$x].job.Error | Select-Object -Last 1) + if ($null -ne $currentprogress) { + $longest = [Math]::Max($longest, $currentprogress.ToString().Length + 15 + $chunklength) + } + ($chunkformat -f $activeworkers[$x].index, $project.Count) + ' ' + $activeworkers[$x].job.State + ' ' + $currentprogress + if ($activeworkers[$x].job.State -eq 'Completed') { + $activeworkers[$x].job = $null + $activeworkers[$x].finished = $true + } + } + } + Start-Sleep 1 + [Console]::SetCursorPosition(0, $Host.UI.RawUI.CursorPosition.Y - $jobsleft) + for ($x = 0; $x -lt $jobsleft; $x++) { + [Console]::WriteLine(("{0,-" + ($longest) + "}") -f " ") + } + [Console]::SetCursorPosition(0, $Host.UI.RawUI.CursorPosition.Y - $jobsleft) + } + Write-Host "Finished" +} + +Assert-OurParameters + +if ($Threads -lt 1) { + $Threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors +} + +$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 + +# .\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 - + +# ./ffprobe.exe -hide_banner -show_entries packet=pts_time,flags -select_streams v:0 -of csv=print_section=0 gi_joe_720p_275.webm > test2.txt + +# .\ffmpeg.exe -i gi_joe_720p_275.webm -vf "zscale=w=848:h=480:f=spline36" -map 0:0 -vcodec libx264 -preset veryfast -crf 30 -x264-params keyint=240:min-keyint=5:scenecut=10 -y -pix_fmt yuv420p gi_joe_720p_275_x264.mp4 \ No newline at end of file