More development, added basic muxing support
This commit is contained in:
parent
81ed27cd05
commit
0d6f7444db
2 changed files with 289 additions and 133 deletions
BIN
mkvmerge.exe
Normal file
BIN
mkvmerge.exe
Normal file
Binary file not shown.
402
spav1an.ps1
402
spav1an.ps1
|
@ -7,11 +7,12 @@ param (
|
||||||
[int] $Renderer = 1, # Frame type, 1 = ffmpeg native, 2 = avisynth
|
[int] $Renderer = 1, # Frame type, 1 = ffmpeg native, 2 = avisynth
|
||||||
[int] $MinChunkSec = 10,
|
[int] $MinChunkSec = 10,
|
||||||
[int] $Threads = 0,
|
[int] $Threads = 0,
|
||||||
|
[int] $MaxKeyInt = 0, #
|
||||||
[String] $Framerate = "auto",
|
[String] $Framerate = "auto",
|
||||||
[String] $AnalyzeVidSize = "848x480",
|
[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] $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 = ""
|
[parameter(Position=0)] [String] $InputFile = ""
|
||||||
)
|
)
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
@ -29,6 +30,7 @@ public struct KeyFrameData {
|
||||||
Add-Type @"
|
Add-Type @"
|
||||||
using System;
|
using System;
|
||||||
public class ProjectChunkData {
|
public class ProjectChunkData {
|
||||||
|
public string filename;
|
||||||
public int index;
|
public int index;
|
||||||
public int start;
|
public int start;
|
||||||
public int end;
|
public int end;
|
||||||
|
@ -39,6 +41,27 @@ public class ProjectChunkData {
|
||||||
}
|
}
|
||||||
"@
|
"@
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
|
||||||
function Write-OurHelp {
|
function Write-OurHelp {
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "S. Powershell variant of av1an"
|
Write-Host "S. Powershell variant of av1an"
|
||||||
|
@ -60,6 +83,9 @@ function Write-OurHelp {
|
||||||
Write-Host " -Framerate auto Frame rate of the video. Auto will use ffprobe"
|
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 " to autodect. Otherwise you can specify it using"
|
||||||
Write-Host " rate/scale"
|
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 " -PrintCommands Print all ffmpeg/ffprobe commands run and arguments"
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "Analyze options:"
|
Write-Host "Analyze options:"
|
||||||
|
@ -78,33 +104,33 @@ function Write-OurHelp {
|
||||||
#>
|
#>
|
||||||
function Assert-OurParameters {
|
function Assert-OurParameters {
|
||||||
try {
|
try {
|
||||||
$gobble = Start-ThreadJob -ScriptBlock {}
|
Start-ThreadJob -ScriptBlock {} | Out-Null
|
||||||
} catch {
|
} catch {
|
||||||
Write-Host "ThreadJob was not found, automatically installing for scope local user"
|
Write-Host "ThreadJob was not found, automatically installing for scope local user"
|
||||||
Install-Module -Name ThreadJob -Scope CurrentUser
|
Install-Module -Name ThreadJob -Scope CurrentUser
|
||||||
Write-Host "Install complete"
|
Write-Host "Install complete"
|
||||||
}
|
}
|
||||||
try {
|
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 {
|
} catch {
|
||||||
Write-Host "Use-RawPipeline was not found, automatically installing for scope local user"
|
Write-Host "Use-RawPipeline was not found, automatically installing for scope local user"
|
||||||
Install-Module -Name Use-RawPipeline -Scope CurrentUser
|
Install-Module -Name Use-RawPipeline -Scope CurrentUser
|
||||||
Write-Host "Install complete"
|
Write-Host "Install complete"
|
||||||
}
|
}
|
||||||
try {
|
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 {
|
} catch {
|
||||||
Write-Error ("Error, unable to run or find $FFprobePath, make sure path or program exists. Error: " + $_.Exception.Message)
|
Write-Error ("Error, unable to run or find $FFprobePath, make sure path or program exists. Error: " + $_.Exception.Message)
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
try {
|
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 {
|
} catch {
|
||||||
Write-Error ("Error, unable to run or find $FFmpegPath, make sure path or program exists. Error: " + $_.Exception.Message)
|
Write-Error ("Error, unable to run or find $FFmpegPath, make sure path or program exists. Error: " + $_.Exception.Message)
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$gobble = Invoke-NativeCommand -FilePath $EncoderPath -ArgumentList ('--help') | Receive-RawPipeline
|
Invoke-NativeCommand -FilePath $EncoderPath -ArgumentList ('--help') | Receive-RawPipeline | Out-Null
|
||||||
} catch {
|
} catch {
|
||||||
Write-Error ("Error, unable to run or find $EncoderPath, make sure path or program exists. Error: " + $_.Exception.Message)
|
Write-Error ("Error, unable to run or find $EncoderPath, make sure path or program exists. Error: " + $_.Exception.Message)
|
||||||
exit 1
|
exit 1
|
||||||
|
@ -148,6 +174,11 @@ function Assert-OurParameters {
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
$writehelp = $true
|
$writehelp = $true
|
||||||
}
|
}
|
||||||
|
if ($MaxKeyInt -lt 0) {
|
||||||
|
Write-Host "Error: MaxKeyInt must be larger than or equal to 0."
|
||||||
|
Write-Host ""
|
||||||
|
$writehelp = $true
|
||||||
|
}
|
||||||
|
|
||||||
if ($writehelp) {
|
if ($writehelp) {
|
||||||
Write-OurHelp
|
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) {
|
function Rename-ArrayIdentifiers([String[]] $InputArr, [String] $Identifier1, [String] $ReplaceWith1, [String] $Identifier2, [String] $ReplaceWith2) {
|
||||||
for ($i = 0; $i -le $InputArr.Length; $i++) {
|
for ($i = 0; $i -le $InputArr.Length; $i++) {
|
||||||
if ($InputArr[$i] -match $Identifier1) {
|
if ($InputArr[$i] -match $Identifier1) {
|
||||||
|
@ -194,7 +228,10 @@ function Write-ProjectFile {
|
||||||
# the max keyint from that
|
# the max keyint from that
|
||||||
$FramerateSplit = $Framerate.Split('/')
|
$FramerateSplit = $Framerate.Split('/')
|
||||||
$fps = [convert]::ToDouble($FramerateSplit[0]) / [convert]::ToDouble($FramerateSplit[1])
|
$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'
|
# Split the video size string of 'widthXheight'
|
||||||
$splitsize = $AnalyzeVidSize.Split('x')
|
$splitsize = $AnalyzeVidSize.Split('x')
|
||||||
|
@ -204,7 +241,7 @@ function Write-ProjectFile {
|
||||||
# Reason to use identifier isntead of using filename directly is cause we
|
# 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
|
# wanna split by space to get array argument list and that might split
|
||||||
# the file pathname.
|
# 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
|
$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
|
# front of it. Invoke-NativeCommand allows us to specify filepath and it will
|
||||||
# run regardless
|
# run regardless
|
||||||
if ($PrintCommands) { Write-Host "[Running] $FFmpegPath $analyzeffmpegSplitted" }
|
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"
|
Write-Host "Reading keyframe data from $analyzefilename"
|
||||||
$analyzekeyframefilename = $workproject + '_kf.txt'
|
|
||||||
|
|
||||||
# Start probing and analyzing our analyze file
|
# 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 = "-loglevel error -hide_banner -show_entries packet=pts_time,flags -select_streams v:0 -of csv=print_section=0 $identifier1".Split(' ')
|
||||||
|
@ -233,8 +269,12 @@ function Write-ProjectFile {
|
||||||
$project = New-Object 'System.Collections.Generic.List[KeyFrameData]'
|
$project = New-Object 'System.Collections.Generic.List[KeyFrameData]'
|
||||||
$keyframes = 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 "<timestamp>:<flags>"
|
||||||
|
# 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++) {
|
for ($i = 0; $i -lt $videodata.Length; $i++) {
|
||||||
$split = $videodata[$i].Split(',')
|
$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') {
|
if ($split.Count -gt 1 -and $split[1][0] -eq 'K') {
|
||||||
$keyframes.Add((New-Object KeyFrameData -Property @{
|
$keyframes.Add((New-Object KeyFrameData -Property @{
|
||||||
start = $i;
|
start = $i;
|
||||||
|
@ -245,26 +285,50 @@ function Write-ProjectFile {
|
||||||
# If there are too few frames, the user might as well just encode directly
|
# If there are too few frames, the user might as well just encode directly
|
||||||
if ($keyframes.Count -lt 2) {
|
if ($keyframes.Count -lt 2) {
|
||||||
Write-Host "Too few keyframes found, exiting"
|
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 = New-Object KeyFrameData -Property @{
|
||||||
start = $keyframes[0].start;
|
start = $keyframes[0].start;
|
||||||
startstamp = $keyframes[0].startstamp;
|
startstamp = $keyframes[0].startstamp;
|
||||||
end = 0;
|
end = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Loop over each keyframe
|
||||||
for ($i = 1; $i -lt $keyframes.Count; $i++) {
|
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
|
$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) {
|
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.end = $keyframes[$i - 1].start - 1
|
||||||
$start.length = $start.end - $start.start
|
$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]
|
$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.end = $keyframes[$i].start - 1
|
||||||
$start.length = $start.end - $start.start
|
$start.length = $start.end - $start.start
|
||||||
$next = $keyframes[$i]
|
$next = $keyframes[$i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Add the segment to our project and start from the next keyframe.
|
||||||
$project.Add($start)
|
$project.Add($start)
|
||||||
$start = New-Object KeyFrameData -Property @{
|
$start = New-Object KeyFrameData -Property @{
|
||||||
start = $next.start;
|
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.end = $videodata.Length - 1
|
||||||
$start.length = $start.end - $start.start
|
$start.length = $start.end - $start.start
|
||||||
$project.Add($start)
|
$project.Add($start)
|
||||||
|
@ -288,6 +354,9 @@ function Write-ProjectFile {
|
||||||
$projectoutput += "`n ]"
|
$projectoutput += "`n ]"
|
||||||
$projectoutput += "`n}"
|
$projectoutput += "`n}"
|
||||||
Set-Content -Path $projectfile $projectoutput
|
Set-Content -Path $projectfile $projectoutput
|
||||||
|
|
||||||
|
# Cleanup temp files
|
||||||
|
Remove-Item $analyzefilename
|
||||||
}
|
}
|
||||||
|
|
||||||
<#
|
<#
|
||||||
|
@ -295,144 +364,231 @@ function Write-ProjectFile {
|
||||||
----------------------- Our encoding worker -----------------------
|
----------------------- 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 {
|
function Start-OurEncoding {
|
||||||
if ($Threads -lt 1) {
|
if ($Threads -lt 1) {
|
||||||
$Threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
|
$Threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "Starting our chunk encoder with maximum $Threads workers"
|
Write-Host "Read our project file"
|
||||||
Write-Host ""
|
$context = New-Object 'ProjectContext'
|
||||||
$encodefile = $workproject + '_output'
|
$context.fileprefix = $workproject + '_output'
|
||||||
$projectraw = Get-Content -Path $projectfile | ConvertFrom-Json
|
$projectraw = Get-Content -Path $projectfile | ConvertFrom-Json
|
||||||
$project = New-Object 'System.Collections.Generic.List[ProjectChunkData]'
|
$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 @{
|
$project.Add((New-Object ProjectChunkData -Property @{
|
||||||
index = $i + 1;
|
index = $i + 1;
|
||||||
start = $projectraw[$i][0];
|
start = $projectraw.chunks[$i][0];
|
||||||
end = $projectraw[$i][1];
|
end = $projectraw.chunks[$i][1];
|
||||||
startstamp = $projectraw[$i][2];
|
startstamp = $projectraw.chunks[$i][2];
|
||||||
finished = $false;
|
finished = $false;
|
||||||
muxed = $false;
|
muxed = $false;
|
||||||
job = $null;
|
job = $null;
|
||||||
|
filename = ("{0}_{1:${$context.countformat}}.ivf" -f $context.fileprefix, ($i + 1));
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
$chunkformat = ""
|
$context.framestotal = $project[$project.Count - 1].end + 1
|
||||||
$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]'
|
$activeworkers = New-Object 'System.Collections.Generic.List[ProjectChunkData]'
|
||||||
for ($i = 0; $i -lt $Threads; $i++) {
|
for ($i = 0; $i -lt $Threads; $i++) {
|
||||||
$activeworkers.Add([ProjectChunkData]@{})
|
$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) {
|
Write-Host "Starting our chunk encoder with maximum $Threads workers"
|
||||||
$startedchunk = $false
|
|
||||||
for ($x = 0; $x -lt $activeworkers.Count; $x++) {
|
for ($x = 0; $x -lt $activeworkers.Count; $x++) {
|
||||||
if ($null -eq $activeworkers[$x].job) {
|
Write-Host "Warming up worker $x"
|
||||||
$chunkfile = $encodefile + '_' + $project[$i].start + '-' + $project[$i].end + '.ivf'
|
|
||||||
|
|
||||||
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', '-')
|
|
||||||
}
|
}
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
$identifier1 = '5BdMoB52CQyrNYEof3gARsbLgsTjZCdqF9'
|
$context.chunkstotal = $project.Count
|
||||||
$EncoderParameters = $EncoderOptions -f $identifier1, ($project[$i].end - $project[$i].start + 1)
|
$context.fps = $projectraw.fps
|
||||||
$EncoderParametersSplitted = $EncoderParameters.Split(' ')
|
$context.nextchunkindex = 0
|
||||||
|
$context.muxernextindex = 0
|
||||||
|
$context.muxerinqueue = 0
|
||||||
|
$context.muxerjob = Start-ThreadJob -ScriptBlock {}
|
||||||
|
|
||||||
for ($e = 0; $e -le $EncoderParametersSplitted.Length; $e++) {
|
# repeat until done
|
||||||
if ($EncoderParametersSplitted[$e] -match $identifier1) {
|
while ($true) {
|
||||||
$EncoderParametersSplitted[$e] = $EncoderParametersSplitted[$e] -replace $identifier1, $chunkfile
|
# If we still have some unproccessed segments left
|
||||||
}
|
if ($context.nextchunkindex -lt $project.Count) {
|
||||||
}
|
|
||||||
|
|
||||||
"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++) {
|
for ($x = 0; $x -lt $activeworkers.Count; $x++) {
|
||||||
if ($null -ne $activeworkers[$x].job) {
|
if ($null -ne $activeworkers[$x].job) { continue }
|
||||||
$currentprogress = ($activeworkers[$x].job.Error | Select-Object -Last 1)
|
|
||||||
if ($null -ne $currentprogress) {
|
Start-ProcessJobItem $context $project[$context.nextchunkindex]
|
||||||
$longest = [Math]::Max($longest, $currentprogress.ToString().Length + 15 + $chunklength)
|
$activeworkers[$x] = $project[$context.nextchunkindex]
|
||||||
}
|
$context.nextchunkindex++
|
||||||
($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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Mux if appropriate
|
||||||
|
if ($context.muxerinqueue -ge $activeworkers.Count -and $context.muxerjob.State -eq 'Completed') {
|
||||||
|
Start-Muxing $context $project
|
||||||
}
|
}
|
||||||
Start-Sleep 1
|
|
||||||
[Console]::SetCursorPosition(0, $Host.UI.RawUI.CursorPosition.Y - $activeworkers.Count)
|
# print progress until an item finishes
|
||||||
for ($x = 0; $x -lt $activeworkers.Count; $x++) {
|
Watch-JobsUntilFinishes $context $activeworkers
|
||||||
[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"
|
Write-Host "Finished"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,6 +601,8 @@ try {
|
||||||
if (!(Test-Path $projectfile)) {
|
if (!(Test-Path $projectfile)) {
|
||||||
Write-ProjectFile
|
Write-ProjectFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Start-OurEncoding
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
|
@ -454,8 +612,6 @@ catch {
|
||||||
Write-Host $_.ScriptStackTrace
|
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 -
|
# .\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
|
# ./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
|
||||||
|
|
Loading…
Reference in a new issue