diff --git a/mkvmerge.exe b/mkvmerge.exe new file mode 100644 index 0000000..4038be9 Binary files /dev/null and b/mkvmerge.exe differ diff --git a/spav1an.ps1 b/spav1an.ps1 index e0b6e7f..dadddac 100644 --- a/spav1an.ps1 +++ b/spav1an.ps1 @@ -7,11 +7,12 @@ param ( [int] $Renderer = 1, # Frame type, 1 = ffmpeg native, 2 = avisynth [int] $MinChunkSec = 10, [int] $Threads = 0, + [int] $MaxKeyInt = 0, # [String] $Framerate = "auto", [String] $AnalyzeVidSize = "848x480", - [String] $AnalyzeVidEnc = "-hide_banner -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] $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 --cq-level=10 --rt -o {0} -", + [String] $EncoderOptions = "--cpu-used=8 --passes=1 --target-bitrate=20000 --rt --limit={1} --fps={2} -o {0} -", [parameter(Position=0)] [String] $InputFile = "" ) $ErrorActionPreference = "Stop" @@ -29,13 +30,35 @@ public struct KeyFrameData { 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; + 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 framesfinished; } "@ @@ -60,6 +83,9 @@ function Write-OurHelp { 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:" @@ -78,33 +104,33 @@ function Write-OurHelp { #> function Assert-OurParameters { try { - $gobble = Start-ThreadJob -ScriptBlock {} + 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 { - $gobble = Invoke-NativeCommand -FilePath 'cmd' -ArgumentList ('/c', 'echo test') | Receive-RawPipeline + 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 { - $gobble = Invoke-NativeCommand -FilePath $FFprobePath -ArgumentList ('-loglevel', 'quiet', '-version') | Receive-RawPipeline + 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 { - $gobble = Invoke-NativeCommand -FilePath $FFmpegPath -ArgumentList ('-loglevel', 'quiet', '-version') | Receive-RawPipeline + 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 { - $gobble = Invoke-NativeCommand -FilePath $EncoderPath -ArgumentList ('--help') | Receive-RawPipeline + 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 @@ -148,6 +174,11 @@ function Assert-OurParameters { 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 @@ -155,6 +186,9 @@ function Assert-OurParameters { } } +# 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) { @@ -194,7 +228,10 @@ function Write-ProjectFile { # the max keyint from that $FramerateSplit = $Framerate.Split('/') $fps = [convert]::ToDouble($FramerateSplit[0]) / [convert]::ToDouble($FramerateSplit[1]) - $maxkeyint = [int][Math]::Ceiling($fps * 10) + + if ($MaxKeyInt -lt 1) { + $MaxKeyInt = [int][Math]::Ceiling($fps * 10) + } # Split the video size string of 'widthXheight' $splitsize = $AnalyzeVidSize.Split('x') @@ -204,7 +241,7 @@ function Write-ProjectFile { # 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 + $AnalyzeVidEnc = $AnalyzeVidEnc -f $identifier1, $vfsize, $MaxKeyInt, $identifier2 $analyzeffmpegSplitted = Rename-ArrayIdentifiers $AnalyzeVidEnc.Split(' ') $identifier1 $InputFile $identifier2 $analyzefilename @@ -216,10 +253,9 @@ function Write-ProjectFile { # 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 + Invoke-NativeCommand -FilePath $FFmpegPath -ArgumentList $analyzeffmpegSplitted | Receive-RawPipeline Write-Host "Reading keyframe data from $analyzefilename" - $analyzekeyframefilename = $workproject + '_kf.txt' # 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(' ') @@ -228,13 +264,17 @@ function Write-ProjectFile { $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 "" + # 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; @@ -245,26 +285,50 @@ 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 + 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) { + 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] - if ($start.end - $start.start -lt $maxkeyint / 2) { + + # 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; @@ -274,6 +338,8 @@ function Write-ProjectFile { } } } + + # 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) @@ -288,6 +354,9 @@ function Write-ProjectFile { $projectoutput += "`n ]" $projectoutput += "`n}" Set-Content -Path $projectfile $projectoutput + + # Cleanup temp files + Remove-Item $analyzefilename } <# @@ -295,144 +364,231 @@ function Write-ProjectFile { ----------------------- 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 -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 + + 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" + } + + [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) { + 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 + Write-Host (("{0,-" + [Math]::Max($Host.UI.RawUI.WindowSize.Width - 1, 0) + "}") -f "$prefix $currentprogress") + } + $now = Get-Date + + $howisitgoing = ("{0:" + $framechunk + "}/{1:" + $framechunk + "}") -f $context.framesfinished, $context.framestotal + + $howisitgoing += " [{0,-30}]" -f ('#' * [Math]::Floor(($context.framesfinished/$context.framestotal) * 30)) + + $howisitgoing += (" Muxed {0:" + $context.countformat + "}/{1:" + $context.countformat + "}") -f $context.muxernextindex, $context.chunkstotal + + if ($context.muxerjob.State -ne 'Completed') { + $howisitgoing += " Muxing " + $context.muxerjob.State + ": " + ($context.muxerjob.Output | Select-Object -Last 3) + } + + + Write-Host (("{0,-" + [Math]::Max($Host.UI.RawUI.WindowSize.Width - 1, 0) + "}") -f " ") + Write-Host (("{0,-" + [Math]::Max($Host.UI.RawUI.WindowSize.Width - 1, 0) + "}") -f $howisitgoing) + + if ($workerFinished) { break } + Start-Sleep 1 + } + return $finishedForMuxing +} + +function Start-Muxing([ProjectContext] $context, [System.Collections.Generic.List[ProjectChunkData]] $project) { + [System.Collections.ArrayList]$segmentstomux = @() + + if ($context.muxernextindex -gt 0) { + $segmentstomux.Add(("{0}[{1}].webm" -f $context.fileprefix, $context.muxernextindex)) | Out-Null + } + + 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}].webm" -f $context.fileprefix, $context.muxernextindex + + $context.muxerjob = Start-ThreadJob -ArgumentList @($targetfile, $segmentstomux) -ScriptBlock { + cmd.exe /C ("mkvmerge.exe -w --flush-on-close -o `"" + $args[0] + "`" [ " + $args[1] + " ]") + if ($lastExitCode -gt 0) { + throw + } + Start-Sleep 5 + } +} + function Start-OurEncoding { if ($Threads -lt 1) { $Threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors } - Write-Host "Starting our chunk encoder with maximum $Threads workers" - Write-Host "" - $encodefile = $workproject + '_output' + 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]' - for ($i = 0; $i -lt $projectraw.Count; $i++) { + + # 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[$i][0]; - end = $projectraw[$i][1]; - startstamp = $projectraw[$i][2]; + 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)); })) } - $chunkformat = "" - $lastChunkFrame = $project.Count.ToString() - for ($x = 0; $x -lt $lastChunkFrame.Length; $x++) { - $chunkformat += '0' - } - $chunkformat = "[{0:$chunkformat}/{1:$chunkformat}]" + $context.framestotal = $project[$project.Count - 1].end + 1 $activeworkers = New-Object 'System.Collections.Generic.List[ProjectChunkData]' for ($i = 0; $i -lt $Threads; $i++) { $activeworkers.Add([ProjectChunkData]@{}) } - $chunklength = $lastChunkFrame.Length * 2 + 3 + Write-Host "Checking for existing files" + $files = Get-ChildItem -Path './' + for ($i = 0; $i -lt $files.Count; $i++) { + if ($files[$i].Name.StartsWith($context.fileprefix + '_')) { + Write-Host "Removing:", $files[$i].Name + Remove-Item $files[$i] + } + } - 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' + 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 "" - 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 + $context.chunkstotal = $project.Count + $context.fps = $projectraw.fps + $context.nextchunkindex = 0 + $context.muxernextindex = 0 + $context.muxerinqueue = 0 + $context.muxerjob = Start-ThreadJob -ScriptBlock {} - $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 + # 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 } - $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 + Start-ProcessJobItem $context $project[$context.nextchunkindex] + $activeworkers[$x] = $project[$context.nextchunkindex] + $context.nextchunkindex++ } } - - 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) - } + + # 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 } - 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" } @@ -445,6 +601,8 @@ try { if (!(Test-Path $projectfile)) { Write-ProjectFile } + + Start-OurEncoding } catch { Write-Host "" @@ -454,8 +612,6 @@ catch { Write-Host $_.ScriptStackTrace } -# 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