param ( [String] $EncoderPath = "aomenc-3.0.0-335.exe", [String] $FFmpegPath = "ffmpeg.exe", [String] $FFprobePath = "ffprobe.exe", [Switch] $PrintCommands, [int] $EncoderType = 1, # Encoder type. 1 = aomenc, 2 = SVT-AV1 [int] $Renderer = 1, # Frame type, 1 = ffmpeg native, 2 = avisynth [int] $Threads = 0, [int] $MaxKeyInt = 0, # [String] $Framerate = "auto", [String] $AnalyzeVidSize = "848x480", [String] $AnalyzeVidEnc = "-hide_banner -loglevel error -stats -i {0} -map 0:0 -vf zscale={1}:f=spline36 -vcodec libx264 -preset veryfast -crf 40 -x264-params keyint={2}:min-keyint=5:scenecut=60 -y -pix_fmt yuv420p {3}", [String] $AvisynthScript = "LSMASHVideoSource({0})", [String] $EncoderOptions = "--cpu-used=8 --passes=1 --target-bitrate=1000 --rt", [parameter(Position=0)] [String] $InputFile = "" ) $ErrorActionPreference = "Stop" Add-Type @" using System; public struct KeyFrameData { public int start; public int end; public decimal startstamp; public int length; } "@ Add-Type @" using System; public class ProjectChunkData { public string filename; public int index; public int start; public int end; public decimal startstamp; public bool finished; public bool muxed; public Object job; } "@ Add-Type @" using System; public class ProjectContext { public string fileprefix; public string fps; public string countformat; public string chunkformat; public int chunkstotal; public int nextchunkindex; public int muxerinqueue; public int muxerlastindex; public int muxernextindex; public Object muxerjob; public DateTime started; public DateTime finished; public DateTime eta; public int framestotal; public int framesstarted; public int framesfinished; } "@ 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 " -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 " -MaxKeyInt 0 Maximum distance between each keyint. This is" Write-Host " used to determine the maximum length of each segment." Write-Host " A value of 0 means it defaults to 10 seconds." Write-Host " -PrintCommands Print all ffmpeg/ffprobe commands run and arguments" 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 { Start-ThreadJob -ScriptBlock {} | Out-Null } catch { Write-Host "ThreadJob was not found, automatically installing for scope local user" Install-Module -Name ThreadJob -Scope CurrentUser Write-Host "Install complete" } try { Invoke-NativeCommand -FilePath 'cmd' -ArgumentList ('/c', 'echo test') | Receive-RawPipeline | Out-Null } catch { Write-Host "Use-RawPipeline was not found, automatically installing for scope local user" Install-Module -Name Use-RawPipeline -Scope CurrentUser Write-Host "Install complete" } try { Invoke-NativeCommand -FilePath $FFprobePath -ArgumentList ('-loglevel', 'quiet', '-version') | Receive-RawPipeline | Out-Null } catch { Write-Error ("Error, unable to run or find $FFprobePath, make sure path or program exists. Error: " + $_.Exception.Message) exit 1 } try { Invoke-NativeCommand -FilePath $FFmpegPath -ArgumentList ('-loglevel', 'quiet', '-version') | Receive-RawPipeline | Out-Null } catch { Write-Error ("Error, unable to run or find $FFmpegPath, make sure path or program exists. Error: " + $_.Exception.Message) exit 1 } try { Invoke-NativeCommand -FilePath $EncoderPath -ArgumentList ('--help') | Receive-RawPipeline | Out-Null } catch { Write-Error ("Error, unable to run or find $EncoderPath, make sure path or program exists. Error: " + $_.Exception.Message) exit 1 } $writehelp = $false if ($inputfile -eq "") { Write-Host "Error: Inputfile is missing" Write-Host "" $writehelp = $true } if ($InputFile -notmatch '[^.]+\..+') { Write-Host "Error: Inputfile must have an extension" Write-Host "" $writehelp = $true } 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 ($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) { 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 } if ($MaxKeyInt -lt 0) { Write-Host "Error: MaxKeyInt must be larger than or equal to 0." Write-Host "" $writehelp = $true } if ($writehelp) { Write-OurHelp exit } } # Helper function to replace identifiers with the replaced string. This allows us to do a simple # arguments.split(' ') without having to worry about it accidentally splitting a filename in half as # we give the filename a random text string that we later replace inside the split array with the correct path. 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') { Write-Host "Framerate is auto, checking framerate from source" $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 if ($PrintCommands) { Write-Host "[Running] $FFprobePath $fpsPropeArgs" } $Framerate = Invoke-NativeCommand -FilePath $FFprobePath -ArgumentList $fpsPropeArgs | Receive-RawPipeline Write-Host "Framerate is: $Framerate" } # 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]) if ($MaxKeyInt -lt 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" # 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 if ($PrintCommands) { Write-Host "[Running] $FFmpegPath $analyzeffmpegSplitted" } Invoke-NativeCommand -FilePath $FFmpegPath -ArgumentList $analyzeffmpegSplitted | Receive-RawPipeline Write-Host "Reading keyframe data from $analyzefilename" # Start probing and analyzing our analyze file $analyzePropeArgs = "-loglevel error -hide_banner -show_entries packet=pts_time,flags -select_streams v:0 -of csv=print_section=0 $identifier1".Split(' ') $analyzePropeArgs = Rename-ArrayIdentifiers $analyzePropeArgs $identifier1 $analyzefilename if ($PrintCommands) { Write-Host "[Running] $FFprobePath $analyzePropeArgs" } $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]' # Parse the output of the ffprobe. The results is a csv containing ":" # What we are looking for is flags of "K" at the start meaning keyframe. Since the csv # contains each frame information, we can use the line index as an identifier for the frame number. for ($i = 0; $i -lt $videodata.Length; $i++) { $split = $videodata[$i].Split(',') # Check if the flag starts with 'K' which means it's a keyframe and add it to our list 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 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 1 } # Start grouping keyframes. This is our helper object we will be using to count # up our keyframes together until we get close to the max key int. $start = New-Object KeyFrameData -Property @{ start = $keyframes[0].start; startstamp = $keyframes[0].startstamp; end = 0; } # Loop over each keyframe for ($i = 1; $i -lt $keyframes.Count; $i++) { # Continue going through keyframes until we've collected enough that # we go over our $MaxKeyInt. Some videos have a loooooot of keyframes # and for our encoder, we would rather group all those up together than # have it process a bunch of sub one-second segments $length = ($keyframes[$i].start - 1) - $start.start if ($length -gt $MaxKeyInt) { # Since we went over the $MaxKeyInt we wanna mark the end on the keyframe # before this one that overflowed. However we make sure that we don't end # up with a 0 frame segment if ($keyframes[$i - 1].start - 1 -gt $start.start) { # We have enough keyframes to batch up into a single segment. So grab # the one keyframe before this one and continue on from that one. $start.end = $keyframes[$i - 1].start - 1 $start.length = $start.end - $start.start # Make the next segment start with the previous keyframe that didn't overflow # our $MaxKeyInt threshold. $next = $keyframes[$i - 1] # In case that segment was shorter than half of $MaxKeyInt, we would rather # make it longer and include the keyframe that made it overflow. What can happen # is that a $MaxKeyInt of 240 and keyframe length of 20 and 210, we would rather # the segment be total of both, 230, over the $MaxKeyInt than to have it create a # tiny 20 frame segment for example. if ($start.end - $start.start -lt $MaxKeyInt / 2) { $start.end = $keyframes[$i].start - 1 $start.length = $start.end - $start.start $next = $keyframes[$i] } # Add the segment to our project and start from the next keyframe. $project.Add($start) $start = New-Object KeyFrameData -Property @{ start = $next.start; startstamp = $next.startstamp; end = 0; } } } } # Wrap up the last segment and add it to our project. $start.end = $videodata.Length - 1 $start.length = $start.end - $start.start $project.Add($start) $projectoutput = '{' $projectoutput += "`n `"fps`":`"" + $Framerate + "`"," $projectoutput += "`n `"chunks`": [" $projectoutput += "`n [" + $project[0].start + ", " + $project[0].end + ", " + $project[0].startstamp + ", " + $project[0].length + "]" for ($i = 1; $i -lt $project.Count; $i++) { $projectoutput += ",`n [" + $project[$i].start + ", " + $project[$i].end + ", " + $project[$i].startstamp + ", " + $project[$i].length + "]" } $projectoutput += "`n ]" $projectoutput += "`n}" Set-Content -Path $projectfile $projectoutput # Cleanup temp files Remove-Item $analyzefilename } <# ------------------------------------------------------------------- ----------------------- Our encoding worker ----------------------- ------------------------------------------------------------------- #> # Start-ProcessJobItem $context $project[$context.nextchunkindex] function Start-ProcessJobItem([ProjectContext] $context, [ProjectChunkData] $projectitem) { if ($Renderer -eq 2) { $trimcontent = $AvisynthScript -f ('"' + $InputFile + '"') $trimcontent += "`nTrim(" + $projectitem.start + ", " + $projectitem.end + ")" $chunkavs = $context.fileprefix + '_' + $projectitem.start + '-' + $projectitem.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($projectitem.startstamp / 3600) $ssminutes = [Math]::Floor(($projectitem.startstamp % 3600) / 60) $ssseconds = $projectitem.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', ($projectitem.end - $projectitem.start + 1), '-strict', '-1', '-f', 'yuv4mpegpipe', '-') } $identifier1 = '5BdMoB52CQyrNYEof3gARsbLgsTjZCdqF9' $encoderparameters = ($EncoderOptions + " --limit={1} --fps={2} -o {0} -") -f $identifier1, ($projectitem.end - $projectitem.start + 1), $context.fps $encoderargs = Rename-ArrayIdentifiers $encoderparameters.Split(' ') $identifier1 $projectitem.filename # Write-Host ("Encoding chunk " + ($chunkformat -f $projectitem.index, $project.Count) + " " + $projectitem.start + ' - ' + $projectitem.end + ' (' + ($projectitem.end - $projectitem.start + 1) + ')') if ($PrintCommands) { Write-Host "[Running] $FFmpegPath $ffmpegreadchunkparameters | $EncoderPath $encoderargs" } $job = Start-ThreadJob -ArgumentList @($FFmpegPath, $ffmpegreadchunkparameters, $EncoderPath, $encoderargs, $i) -ScriptBlock { $lecommand = $args[0] + ' ' + $args[1] + ' | ' + $args[2] + ' ' + $args[3] cmd.exe /C $lecommand if ($lastExitCode -gt 0) { throw } } $projectitem.job = $job } function Watch-JobsUntilFinishes([ProjectContext] $context, [System.Collections.Generic.List[ProjectChunkData]] $activeworkers) { $framechunk = '0' * $context.framestotal.ToString().Length $workerFinished = $false $seconds = ($context.finished - $context.started).TotalSeconds $fps = ($context.framesfinished - $context.framesstarted) / $seconds $etatotal = ($context.framestotal / $fps) - $seconds $finishedold = $context.framesfinished while ($workerFinished -ne $true) { if ($context.muxerjob.State -eq 'Failed') { Write-Host "------------------------------" Write-Host "Error encoding segment ${$context.muxerjob.Name}:" $context.muxerjob.Error | Write-Host $context.muxerjob.Output | Write-Host throw "Error during muxing" } $consolewidth = [Math]::Max($Host.UI.RawUI.WindowSize.Width, 0) [Console]::SetCursorPosition(0, [Math]::Max($Host.UI.RawUI.CursorPosition.Y - $activeworkers.Count - 2, 0)) for ($x = 0; $x -lt $activeworkers.Count; $x++) { if ($null -eq $activeworkers[$x].job) { Write-Host (("{0,-" + [Math]::Max($Host.UI.RawUI.WindowSize.Width - 1, 0) + "}") -f ("Worker {0} done" -f ($x + 1))) continue } if ($activeworkers[$x].job.State -eq 'Failed') { Write-Host "------------------------------" Write-Host "Error encoding segment ${$activeworkers[0].index}:" $activeworkers[$x].job.Error | Write-Host throw "Error encoding segment" } if ($activeworkers[$x].job.State -eq 'Completed') { if ($null -ne $activeworkers[$x].job) { $activeworkers[$x].job = $null $activeworkers[$x].finished = $true $activeworkers[$x].muxed = $false $context.framesfinished += ($activeworkers[$x].end - $activeworkers[$x].start) + 1 $context.finished = Get-Date $context.muxerinqueue++ } $workerFinished = $true $currentprogress = 'Completed' } else { $currentprogress = ($activeworkers[$x].job.Error | Select-Object -Last 1) } if ($null -eq $currentprogress) { $currentprogress = 'Starting...' } else { $currentprogress = $currentprogress.ToString() $indexOfEta = $currentprogress.IndexOf('[ETA') if ($indexOfEta -gt 0) { $currentprogress = $currentprogress.Substring(0, $indexOfEta) } } $prefix = $context.chunkformat -f $activeworkers[$x].index, $context.chunkstotal $message = "$prefix $currentprogress" if ($message.Length -gt ($consolewidth - 1)) { $message = $message.Substring(0, $consolewidth - 1) } Write-Host (("{0,-" + [Math]::Max($Host.UI.RawUI.WindowSize.Width - 1, 0) + "}") -f $message) } $now = Get-Date $howisitgoing = ("{0:" + $framechunk + "}/{1:" + $framechunk + "} ({2:" + $context.countformat + "}/{3:" + $context.countformat + "})") -f $context.framesfinished, $context.framestotal, $context.muxernextindex, $context.chunkstotal $howisitgoing += " [{0,-20}]" -f ('#' * [Math]::Floor(($context.framesfinished/$context.framestotal) * 20)) # $howisitgoing += (" Muxed {0:" + $context.countformat + "}/{1:" + $context.countformat + "}") -f $context.muxernextindex, $context.chunkstotal if ($finishedold -gt $context.framesstarted) { $howisitgoing += " fps {0:0.00}" -f $fps $until = $context.started.AddSeconds($etatotal) - $now if ($until.TotalSeconds -gt 0) { if ($until.Days -gt 0) { $howisitgoing += (" eta {0}d {1:00}:{2:00}:{3:00}" -f $until.Days, $until.Hours, $until.Minutes, $until.Seconds) } else { $howisitgoing += (" eta {1:00}:{2:00}:{3:00}" -f $until.Days, $until.Hours, $until.Minutes, $until.Seconds) } } } else { $howisitgoing += ' eta ' } if ($context.muxerjob.State -ne 'Completed') { $lastmessage = $context.muxerjob.Output | Select-Object -Last 1 if ($lastmessage) { $howisitgoing += " Muxing " + $lastmessage } } if ($howisitgoing.Length -gt ($consolewidth - 1)) { $howisitgoing = $howisitgoing.Substring(0, $consolewidth - 1) } Write-Host (("{0,-" + $consolewidth + "}") -f " ") Write-Host (("{0,-" + $consolewidth + "}") -f $howisitgoing) if ($workerFinished) { break } if ($context.muxerjob.State -eq 'Completed' -and $context.muxernextindex -ge $project.Count) { break } Start-Sleep 1 } return $finishedForMuxing } function Start-Muxing([ProjectContext] $context, [System.Collections.Generic.List[ProjectChunkData]] $project) { [System.Collections.ArrayList]$segmentstomux = @() $lastmuxed = $null if ($context.muxernextindex -gt 0) { $segmentstomux.Add((("{0}({1:" + $context.countformat + "}).webm") -f $context.fileprefix, $context.muxernextindex)) | Out-Null $lastmuxed = $segmentstomux[0] } for ($i = $context.muxernextindex; $i -lt $project.Count; $i++) { if ($project[$i].finished -and $project[$i].muxed -eq $false ) { $context.muxerinqueue-- $context.muxernextindex++ $project[$i].muxed = $true $segmentstomux.Add($project[$i].filename) | Out-Null } else { break } } $targetfile = ("{0}({1:" + $context.countformat + "}).webm") -f $context.fileprefix, $context.muxernextindex $context.muxerjob = Start-ThreadJob -ArgumentList @($targetfile, $segmentstomux, $lastmuxed) -ScriptBlock { cmd.exe /C ("mkvmerge.exe -w --flush-on-close -o `"" + $args[0] + "`" [ " + $args[1] + " ]") if ($lastExitCode -gt 0) { throw } if ($args[2]) { Remove-Item -Force $args[2] } for ($x = 0; $x -lt $args[1].Count; $x++) { Remove-Item -Force $args[1][$x] } } } function Start-OurEncoding { if ($Threads -lt 1) { $Threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors } Write-Host "Read our project file" $context = New-Object 'ProjectContext' $context.fileprefix = $workproject + '_output' $projectraw = Get-Content -Path $projectfile | ConvertFrom-Json $project = New-Object 'System.Collections.Generic.List[ProjectChunkData]' # Create a format string so we can display a nice zero-padded number of the amount # of segments left, example: [180-320] # $countFormat = '0' * $project.Count.ToString().Length $context.countformat = '0' * $projectraw.chunks.Count.ToString().Length $context.chunkformat = "[{0:${$context.countformat}}/{1:$('0' * $projectraw.chunks.Count.ToString().Length)}]" # Parse through the entire project file and add all the data to our encoding # segment array. for ($i = 0; $i -lt $projectraw.chunks.Count; $i++) { $project.Add((New-Object ProjectChunkData -Property @{ index = $i + 1; start = $projectraw.chunks[$i][0]; end = $projectraw.chunks[$i][1]; startstamp = $projectraw.chunks[$i][2]; finished = $false; muxed = $false; job = $null; filename = ("{0}_{1:${$context.countformat}}.ivf" -f $context.fileprefix, ($i + 1)); })) } $context.framestotal = $project[$project.Count - 1].end + 1 $context.framesstarted = 0 $context.fps = $projectraw.fps $context.nextchunkindex = 0 $context.muxernextindex = 0 $context.muxerinqueue = 0 $context.chunkstotal = $project.Count $context.muxerjob = Start-ThreadJob -ScriptBlock {} $activeworkers = New-Object 'System.Collections.Generic.List[ProjectChunkData]' for ($i = 0; $i -lt $Threads; $i++) { $activeworkers.Add([ProjectChunkData]@{}) } Write-Host "Checking for existing files" $removed = 0 $files = Get-ChildItem -Path './' for ($i = 0; $i -lt $files.Count; $i++) { if ($files[$i].Name.StartsWith($context.fileprefix + '_')) { $removed++ Remove-Item -Force $files[$i] } elseif ($files[$i].Name.StartsWith($context.fileprefix + '(')) { try { $mymatches = $files[$i].Name | Select-String -Pattern '\((\d+)\)' $checkmuxerprevious = [int]$mymatches.Matches[0].Groups[1].Value if ($context.muxernextindex -eq 0 -or $checkmuxerprevious -lt $context.muxernextindex) { $context.muxernextindex = [int]$mymatches.Matches[0].Groups[1].Value $context.nextchunkindex = $context.muxernextindex Write-Host ("Continuing from previous session from {0}" -f $context.muxernextindex) } else { Write-Host ("Removing {0} as we found existing smaller index to continue from of {1}" -f $files[$i].Name, $context.muxernextindex) $removed++ Remove-Item -Force $files[$i].Name } } catch { throw ("Unable to continue from previous run, unable to read " + $files[$i].Name) } } } if ($removed -gt 0) { Write-Host ("Removed {0} existing files" -f $removed) } for ($i = 0; $i -lt $context.nextchunkindex; $i++) { $context.framesfinished += ($project[$i].end - $project[$i].start) + 1 } $context.framesstarted = $context.framesfinished Write-Host "Starting our chunk encoder with maximum $Threads workers" for ($x = 0; $x -lt $activeworkers.Count; $x++) { Write-Host "Warming up worker $x" } Write-Host "" Write-Host "" $context.started = Get-Date # repeat until done while ($true) { # If we still have some unproccessed segments left if ($context.nextchunkindex -lt $project.Count) { for ($x = 0; $x -lt $activeworkers.Count; $x++) { if ($null -ne $activeworkers[$x].job) { continue } Start-ProcessJobItem $context $project[$context.nextchunkindex] $activeworkers[$x] = $project[$context.nextchunkindex] $context.nextchunkindex++ } } # Mux if appropriate if ($context.muxerinqueue -ge $activeworkers.Count -and $context.muxerjob.State -eq 'Completed') { Start-Muxing $context $project } # print progress until an item finishes Watch-JobsUntilFinishes $context $activeworkers if ($context.muxerjob.State -eq 'Completed' -and $context.muxernextindex -ge $project.Count) { break } } Move-Item (("{0}({1:" + $context.countformat + "}).webm") -f $context.fileprefix, $context.muxernextindex) ("{0}_finished.webm" -f $context.fileprefix) Write-Host "Finished" } try { Assert-OurParameters $workproject = [io.path]::GetFileNameWithoutExtension($InputFile) $projectfile = $workproject + '_spav1an.json' if (!(Test-Path $projectfile)) { Write-ProjectFile } Start-OurEncoding } catch { Write-Host "" Write-Host "FATAL EXCEPTION OCCURED:" Write-Host "" Write-Host $_ Write-Host $_.ScriptStackTrace } # .\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