Files
staxrip/Source/General/GlobalClass.vb
T
2024-06-07 09:32:22 +02:00

1832 lines
69 KiB
VB.net

Imports System.Collections.ObjectModel
Imports System.Drawing.Imaging
Imports System.Globalization
Imports System.Management.Automation
Imports System.Management.Automation.Language
Imports System.Runtime.ExceptionServices
Imports System.Security.Principal
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Windows.Forms.VisualStyles
Imports Microsoft.VisualBasic.FileIO
Imports Microsoft.Win32
Imports StaxRip.UI
Imports VB6 = Microsoft.VisualBasic
Public Class GlobalClass
Property DefaultCommands As New GlobalCommands
Property DPI As Integer
Property IsAdmin As Boolean = New WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)
Property IsJobProcessing As Boolean
Property MainForm As MainForm
Shared Property MAX_PATH As Integer = 260
Property MinimizedWindows As Boolean
Property ProcForm As ProcessingForm
Property ProjectPath As String
Property SavedProject As New Project
Property StopAfterCurrentJob As Boolean
Property ActiveForm As Form
Event AfterJobAdded()
Event AfterJobFailed()
Event AfterJobMuxed()
Event AfterJobProcessed()
Event AfterJobsProcessed()
Event AfterProjectLoaded()
Event AfterProjectOrSourceLoaded()
Event AfterSourceLoaded()
Event AfterVideoEncoded()
Event ApplicationExit()
Event BeforeJobAdding()
Event BeforeJobProcessed()
Event BeforeProcessing()
Event WhileProcessing(commandLine As String, percentage As Single, lastProgress As String)
Sub WriteDebugLog(value As String)
If s?.WriteDebugLog Then
Trace.TraceInformation(value)
End If
End Sub
Function InvokePowerShellCode(code As String, Optional args As String() = Nothing) As Collection(Of PSObject)
Return InvokePowerShellCode(code, Nothing, Nothing, args)
End Function
Function InvokePowerShellCode(
code As String,
variableName As String,
variableValue As Object) As Collection(Of PSObject)
InvokePowerShellCode(code, variableName, variableValue, Nothing)
End Function
Function InvokePowerShellCode(
code As String,
variableName As String,
variableValue As Object,
args As String()) As Collection(Of PSObject)
Try
Return PowerShell.Invoke(code, variableName, variableValue, args)
Catch ex As RuntimeException
g.ShowException(ex, "PowerShell Scipt Exception",
ex.ErrorRecord.ScriptStackTrace.Replace(" <ScriptBlock>, <No file>", ""))
Catch ex As Exception
ShowException(ex)
End Try
End Function
Sub LoadPowerShellScripts()
For Each dirPath In {Path.Combine(Folder.Apps, "Scripts"), Folder.Scripts + "Auto Load"}
If dirPath.DirExists Then
For Each fp In Directory.GetFiles(dirPath, "*.ps1")
g.DefaultCommands.ExecutePowerShellFile(fp)
Next
End If
Next
End Sub
Function ConvertToCSV(delimiter As String, objects As IEnumerable(Of Object)) As String
Dim code = $"$inputVar | ConvertTo-Csv -Delimiter '{delimiter}' -NoTypeInformation"
Return "sep=" + delimiter + BR + PowerShell.InvokeAndConvert(code, "inputVar", objects)
End Function
Function IsWindowsTerminalAvailable() As Boolean
Return File.Exists(Path.Combine(Folder.AppDataLocal, "Microsoft", "WindowsApps", "wt.exe"))
End Function
Sub RunCodeInTerminal(code As String)
Dim base64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(code)) 'UTF16LE
If s.PreferWindowsTerminal AndAlso g.IsWindowsTerminalAvailable Then
RunCommandInTerminal("wt.exe", $"powershell.exe -NoLogo -NoExit -NoProfile -EncodedCommand ""{base64}""")
Else
RunCommandInTerminal("powershell.exe", $"-NoLogo -NoExit -NoProfile -EncodedCommand ""{base64}""")
End If
End Sub
Sub RunCommandInTerminal(fileName As String, Optional arguments As String = Nothing)
Using pr As New Process
pr.StartInfo.FileName = fileName
pr.StartInfo.Arguments = arguments
pr.StartInfo.UseShellExecute = False
pr.StartInfo.WorkingDirectory = Folder.Desktop
Proc.SetEnvironmentVariables(pr)
pr.Start()
End Using
End Sub
Sub Execute(fileName As String, Optional arguments As String = Nothing)
Dim info As New ProcessStartInfo With {
.UseShellExecute = False,
.FileName = fileName
}
If arguments <> "" Then
info.Arguments = arguments
End If
Using pr As New Process
pr.StartInfo = info
Proc.SetEnvironmentVariables(pr)
pr.Start()
End Using
End Sub
Sub ShellExecute(fileName As String, Optional arguments As String = Nothing)
Try
Dim psi = New ProcessStartInfo(fileName.Escape) With {
.UseShellExecute = True,
.Arguments = arguments
}
Process.Start(psi)?.Dispose()
Catch ex As Exception
g.ShowException(ex, "Failed to start process", "Filename:" + BR2 + fileName + BR2 + "Arguments:" + BR2 + arguments)
End Try
End Sub
Sub SelectFileWithExplorer(filepath As String)
g.ShellExecute("explorer.exe", "/n, /select, " + filepath.Escape)
End Sub
Sub AddToPath(ParamArray dirs As String())
Dim path = Environment.GetEnvironmentVariable("path")
Dim newPath = path
For Each d In dirs
If d.DirExists AndAlso Not d.StartsWith(Folder.System) AndAlso Not path.Contains(d + ";") Then
newPath = d + ";" + newPath
End If
Next
If newPath <> path Then
Environment.SetEnvironmentVariable("path", newPath)
End If
End Sub
Function IsFixedDrive(path As String) As Boolean
Try
If path <> "" Then
Return New DriveInfo(path).DriveType = DriveType.Fixed
End If
Catch
End Try
End Function
Sub ProcessJobs()
For Each form In PreviewForm.Instances.ToArray
form.Close()
Next
Dim tempProject = ObjectHelp.GetCopy(p)
Dim projectPath = g.ProjectPath
Dim mainFormText = g.MainForm.Text
Dim targetFile = p.TargetFile
FrameServerHelp.AviSynthToolPath()
g.StopAfterCurrentJob = False
ProcessJobsRecursive()
Dim restore = String.IsNullOrWhiteSpace(tempProject.SourceFile) OrElse (File.Exists(tempProject.SourceFile) AndAlso Directory.Exists(tempProject.TempDir) AndAlso File.Exists(projectPath))
If restore Then
g.MainForm.OpenProject(tempProject, projectPath)
g.MainForm.tbTargetFile.Text = targetFile
g.MainForm.Text = mainFormText
g.ProjectPath = projectPath
g.UpdateTrim(p.Script)
g.MainForm.UpdateFilters()
Else
g.MainForm.OpenProject("", False)
End If
End Sub
Sub ProcessJobsRecursive()
Dim jobs = JobManager.ActiveJobs
If jobs.Count = 0 Then
Exit Sub
End If
g.IsJobProcessing = True
Dim jobPath = jobs(0).Path
Try
JobManager.ActivateJob(jobPath, False)
g.MainForm.OpenProject(jobPath, False)
If s.PreventStandby Then
PowerRequest.SuppressStandby()
End If
ProcessJob(jobPath)
jobs = JobManager.GetJobs
If jobs.Count = 0 Then
g.RaiseAppEvent(ApplicationEvent.AfterJobsProcessed)
g.ShutdownPC()
ElseIf JobManager.ActiveJobs.Count = 0 OrElse g.StopAfterCurrentJob Then
g.RaiseAppEvent(ApplicationEvent.AfterJobsProcessed)
If Process.GetProcessesByName("StaxRip").Count = 1 Then
g.ShutdownPC()
End If
Else
If Process.GetCurrentProcess.PrivateMemorySize64 / 1024 ^ 2 > 1500 Then
g.ShellExecute(Application.ExecutablePath, "-StartJobs")
g.MainForm.SetSavedProject()
g.MainForm.Close()
Else
ProcessJobsRecursive()
End If
End If
Catch ex As AbortException
Log.Save()
g.MainForm.OpenProject(g.ProjectPath, False)
Catch ex As Exception
Log.Save()
g.OnException(ex)
Finally
If s.PreventStandby Then
PowerRequest.EnableStandby()
End If
g.IsJobProcessing = False
ThemeManager.SetCurrentTheme(s.ThemeName)
g.MainForm.OpenProject(jobPath, False)
ProcController.Finished()
End Try
End Sub
Sub ProcessJob(jobPath As String)
Try
If Not File.Exists(jobPath) Then
g.RunTask(Sub() MsgError("Project file not found!", $"'{jobPath}'{BR}could not be found and got skipped!"))
Exit Sub
End If
g.RaiseAppEvent(ApplicationEvent.BeforeJobProcessed)
Dim startTime = DateTime.Now
If p.BatchMode Then
Dim missingFiles = p.SourceFiles.Where(Function(srcFile) Not srcFile.FileExists)
If missingFiles.Count > 0 Then
g.RunTask(Sub() MsgError("Source file not found!", $"{missingFiles.Join(BR)}{BR}could not be found and got skipped!"))
Exit Sub
End If
g.MainForm.OpenVideoSourceFiles(p.SourceFiles, True, s.ErrorMessageTimeout)
g.ProjectPath = Path.Combine(p.TempDir, p.TargetFile.Base + ".srip")
p.BatchMode = False
g.MainForm.SaveProjectPath(g.ProjectPath)
Else
If Not File.Exists(p.SourceFile) Then
g.RunTask(Sub() MsgError("Source file not found!", $"'{p.SourceFile}'{BR}could not be found and got skipped!"))
Exit Sub
End If
End If
g.RaiseAppEvent(ApplicationEvent.BeforeProcessing)
Log.WriteEnvironment()
Log.WriteConfiguration()
Dim summary = MediaInfo.GetSummary(p.SourceFile)
If Not Log.ToString().Contains(summary) Then
Log.WriteHeader("Media Info Source File")
For Each i In p.SourceFiles
Log.WriteLine(i)
Next
Log.WriteLine(BR + MediaInfo.GetSummary(p.SourceFile))
End If
Log.WriteHeader($"{p.Script.Engine} Script")
Log.WriteLine(p.Script.GetFullScript)
p.Script.Synchronize(False, False)
If p.Script.Error <> "" Then
Throw New ErrorAbortException($"{p.Script.Engine} Script Error", p.Script.Error)
End If
Log.WriteHeader("Source Script Info")
Log.WriteLine(p.SourceScript.GetInfo().GetInfoText(-1))
Log.WriteHeader("Target Script Info")
Log.WriteLine(p.Script.Info.GetInfoText(-1))
g.MainForm.Hide()
Dim actions As New List(Of Action)
If p.SkipAudioEncoding AndAlso File.Exists(p.Audio0.GetOutputFile) Then
p.Audio0.File = p.Audio0.GetOutputFile()
Else
actions.Add(Sub()
Audio.Process(p.Audio0)
p.Audio0.Encode()
End Sub)
End If
If p.SkipAudioEncoding AndAlso File.Exists(p.Audio1.GetOutputFile) Then
p.Audio1.File = p.Audio1.GetOutputFile()
Else
actions.Add(Sub()
Audio.Process(p.Audio1)
p.Audio1.Encode()
End Sub)
End If
For Each track In p.AudioTracks
Dim temp = track
If p.SkipAudioEncoding AndAlso File.Exists(track.GetOutputFile) Then
track.File = track.GetOutputFile()
Else
actions.Add(Sub()
Audio.Process(temp)
temp.Encode()
End Sub)
End If
Next
actions.Add(Sub() Subtitle.Cut(p.VideoEncoder.Muxer.Subtitles))
If CanEncodeVideo() Then
If p.VideoEncoder.CanChunkEncode Then
p.Script.Synchronize()
If p.VideoEncoder.BeforeEncoding() Then
For Each i In p.VideoEncoder.GetChunkEncodeActions()
actions.Add(i)
Next
End If
Else
actions.Add(Sub()
If p.VideoEncoder.BeforeEncoding() Then
p.VideoEncoder.Encode()
End If
End Sub)
End If
End If
Try
Parallel.Invoke(New ParallelOptions With {.MaxDegreeOfParallelism = s.ParallelProcsNum}, actions.ToArray)
Catch ex As AggregateException
ExceptionDispatchInfo.Capture(ex.InnerExceptions(0)).Throw()
End Try
Log.Save()
p.VideoEncoder.AfterEncoding()
g.RaiseAppEvent(ApplicationEvent.AfterVideoEncoded)
p.VideoEncoder.Muxer.Mux()
If p.Thumbnailer Then
Dim cts = New CancellationTokenSource()
Dim closingTD As TaskDialog(Of DialogResult) = Nothing
Dim mainFormClosingHandler As FormClosingEventHandler = Nothing
mainFormClosingHandler = Sub(sender As Object, e As FormClosingEventArgs)
If Not e.Cancel Then
Using closingTD
closingTD = New TaskDialog(Of DialogResult)(DialogResult.Yes)
closingTD.Icon = TaskIcon.Warning
closingTD.Title = "Close while Thumbnailer is running?"
closingTD.Content = "StaxRip shall be closed while Thumbnailer is still running."
closingTD.Content += $"{BR2}These are your options:"
closingTD.Content += $"{BR}(1) Leave this dialog open and StaxRip will continue the closing procedure as soon as Thumbnailer finishes."
closingTD.Content += $" If you have modified the project, you may be asked to save it first."
closingTD.Content += $"{BR}(2) YES - Forces Thumbnailer to stop immediately and StaxRip will continue closing."
closingTD.Content += $"{BR}(3) NO - Cancels the closing procedure and StaxRip keeps running."
closingTD.AddButton(DialogResult.Yes)
closingTD.AddButton(DialogResult.No)
If closingTD.Show() = DialogResult.No Then
e.Cancel = True
End If
End Using
End If
End Sub
Dim thumbnailerTask = Task.Run(
Async Function()
AddHandler g.MainForm.FormClosing, mainFormClosingHandler
Dim proceededSources = Await Thumbnailer.RunAsync(cts.Token, p, p.TargetFile)
If closingTD?.IsDisposingOrDisposed Then
closingTD.Close()
End If
RemoveHandler g.MainForm.FormClosing, mainFormClosingHandler
Return proceededSources
End Function,
cts.Token)
End If
g.RaiseAppEvent(ApplicationEvent.AfterJobMuxed)
Log.WriteHeader("Job Complete")
Log.WriteStats(startTime)
Dim deleteTempFiles = True
If FileTypes.Video.Contains(p.TargetFile.Ext()) Then
Dim isCuttedRemux = TypeOf p.VideoEncoder Is NullEncoder AndAlso p.Ranges?.Any()
Dim hasFrames = MediaInfo.GetVideo(p.TargetFile, "FrameCount").ToInt(-1)
Dim shouldFrames = p.TargetFrames
If hasFrames <> shouldFrames Then
g.MainForm.UpdateTargetParameters(p)
shouldFrames = p.TargetFrames
End If
If hasFrames <> shouldFrames Then
deleteTempFiles = p.DeleteTempFilesOnFrameMismatch
Log.WriteHeader("Frame Mismatch")
Log.WriteLine($"WARNING: Target file has {hasFrames} frames, but should have {shouldFrames} frames!")
If isCuttedRemux Then
Log.WriteLine($"There is a mismatch of {shouldFrames - hasFrames} frames!")
Else
Log.WriteLine($"Encoding was probably terminated at {hasFrames / shouldFrames * 100:0.0}% with a mismatch of {shouldFrames - hasFrames} frames!")
End If
If hasFrames > -1 AndAlso p.AbortOnFrameMismatch AndAlso Not isCuttedRemux Then
Throw New ErrorAbortException("Frame Mismatch", $"Target file has {hasFrames} frames, but should have {shouldFrames} frames!", p)
End If
End If
End If
Log.Save()
g.ArchiveLogFile(Log.GetPath())
g.RaiseAppEvent(ApplicationEvent.AfterJobProcessed)
JobManager.RemoveJob(jobPath)
If deleteTempFiles Then g.DeleteTempFiles()
If jobPath.StartsWith(Path.Combine(Folder.Settings, "Batch Projects")) Then File.Delete(jobPath)
Catch ex As SkipException
Log.Save()
ProcController.Aborted = False
Catch ex As ErrorAbortException
Log.Save()
g.RaiseAppEvent(ApplicationEvent.AfterJobFailed)
g.ShowException(ex, Nothing, Nothing, s.ErrorMessageTimeout)
g.ShellExecute(g.GetTextEditorPath(), """" + p.Log.GetPath() + """")
ProcController.Aborted = False
End Try
End Sub
Function CanEncodeVideo() As Boolean
Return Not (p.SkipVideoEncoding AndAlso TypeOf p.VideoEncoder IsNot NullEncoder AndAlso File.Exists(p.VideoEncoder.OutputPath))
End Function
Sub DeleteTempFiles(Optional proj As Project = Nothing)
If proj Is Nothing Then proj = p
If proj.DeleteTempFilesMode = DeleteMode.Disabled Then Return
If String.IsNullOrWhiteSpace(proj.TempDir) OrElse Not proj.TempDir.DirExists() OrElse Not New DirectoryInfo(proj.TempDir).Name.EndsWith("_temp") Then Return
If JobManager.GetJobs()?.Where(Function(a) a.Active AndAlso a.Path.Contains(proj.TempDir))?.Any() Then Return
Dim excludeSourcefile = True
Dim extensions As IEnumerable(Of String) = {}
Dim howToDelete = If(proj.DeleteTempFilesMode = DeleteMode.RecycleBin, RecycleOption.SendToRecycleBin, RecycleOption.DeletePermanently)
Select Case proj.DeleteTempFilesSelection
Case DeleteSelection.Everything
FolderHelp.Delete(proj.TempDir, howToDelete)
Exit Sub
Case DeleteSelection.Custom
excludeSourcefile = False
extensions = proj.DeleteTempFilesCustomSelection
Case DeleteSelection.Selective
If proj.DeleteTempFilesSelectiveSelection.HasFlag(DeleteSelectiveSelection.Projects) Then extensions = extensions.Union(FileTypes.Projects)
If proj.DeleteTempFilesSelectiveSelection.HasFlag(DeleteSelectiveSelection.Logs) Then extensions = extensions.Union(FileTypes.Logs)
If proj.DeleteTempFilesSelectiveSelection.HasFlag(DeleteSelectiveSelection.Scripts) Then extensions = extensions.Union(FileTypes.Scripts)
If proj.DeleteTempFilesSelectiveSelection.HasFlag(DeleteSelectiveSelection.Indexes) Then extensions = extensions.Union(FileTypes.Indexes)
If proj.DeleteTempFilesSelectiveSelection.HasFlag(DeleteSelectiveSelection.Videos) Then extensions = extensions.Union(FileTypes.Video)
If proj.DeleteTempFilesSelectiveSelection.HasFlag(DeleteSelectiveSelection.Audios) Then extensions = extensions.Union(FileTypes.Audio)
If proj.DeleteTempFilesSelectiveSelection.HasFlag(DeleteSelectiveSelection.Subtitles) Then extensions = extensions.Union(FileTypes.SubtitleExludingContainers)
End Select
Dim filesInTemp = Directory.GetFiles(proj.TempDir).AsEnumerable()
If excludeSourcefile Then filesInTemp = filesInTemp.Where(Function(x) x <> proj.SourceFile)
Dim filesToDelete As IEnumerable(Of String)
Select Case proj.DeleteTempFilesSelectionMode
Case SelectionMode.Exclude
filesToDelete = filesInTemp.Where(Function(x) Not extensions.Contains(x.Ext(), StringComparer.InvariantCultureIgnoreCase))
Case SelectionMode.Include
filesToDelete = filesInTemp.Where(Function(x) extensions.Contains(x.Ext(), StringComparer.InvariantCultureIgnoreCase))
Case Else
End Select
FileHelp.Delete(filesToDelete, howToDelete)
End Sub
ReadOnly Property StartupTemplatePath As String
Get
Dim ret = Path.Combine(Folder.Template, s.StartupTemplate + ".srip")
If Not File.Exists(ret) Then
ret = Path.Combine(Folder.Template, "Automatic Workflow.srip")
s.StartupTemplate = "Automatic Workflow"
End If
Return ret
End Get
End Property
ReadOnly Property SettingsFile As String
Get
Return Path.Combine(Folder.Settings, "Settings.dat")
End Get
End Property
Function BrowseFolder(defaultFolder As String) As String
Using dialog As New FolderBrowserDialog
dialog.SetSelectedPath(defaultFolder)
If dialog.ShowDialog = DialogResult.OK Then
Return dialog.SelectedPath
End If
End Using
End Function
Function VerifyRequirements() As Boolean
If s.VerifyToolStatus AndAlso Environment.Is64BitProcess Then
SyncLock Package.ConfLock
For Each pack In Package.Items.Values
If Not pack.VerifyOK Then
Return False
End If
Next
End SyncLock
If Not p.Script.IsFilterActive("Source") Then
MsgWarn("No active filter of category 'Source' found.")
Return False
End If
End If
Return True
End Function
Function VerifySource(files As IEnumerable(Of String)) As Boolean
For Each file In files
If Not file.IsProcessEncodingCompatible AndAlso p.Script.IsAviSynth Then
ShowAviSynthUnicodeError()
Return False
End If
Next
Return True
End Function
Sub ShowAviSynthUnicodeError()
MsgError($"Unicode filenames are not supported by AviSynth unless Windows 10 is used." + BR2 +
$"Rename the file or enable VapourSynth:{BR2}Filters > Filter Setup > VapourSynth")
End Sub
Sub PlayAudio(ap As AudioProfile)
If FileTypes.AudioRaw.Contains(ap.File.Ext) Then
g.ShellExecute(Package.mpvnet.Path, ap.File.Escape)
ElseIf ap.File = p.FirstOriginalSourceFile AndAlso ap.Streams.Count > 0 Then
g.ShellExecute(Package.mpvnet.Path, "--audio=" & (ap.Stream.Index + 1) & " " + p.FirstOriginalSourceFile.Escape)
ElseIf FileTypes.Audio.Contains(ap.File.Ext) Then
g.ShellExecute(Package.mpvnet.Path, "--audio-delay=" + (g.ExtractDelay(ap.File) / 1000).ToInvariantString.Shorten(9) + " --audio-files=" + ap.File.Escape + " " + p.FirstOriginalSourceFile.Escape)
Else
MsgError("Unable to play audio.")
End If
End Sub
Sub PlayScript(script As VideoScript)
PlayScriptWithMPV(script)
End Sub
Function GetAudioProfileForScriptPlayback() As AudioProfile
If File.Exists(p.Audio0.File) AndAlso FileTypes.Audio.Contains(p.Audio0.File.Ext) Then
Return p.Audio0
ElseIf File.Exists(p.Audio1.File) AndAlso FileTypes.Audio.Contains(p.Audio1.File.Ext) Then
Return p.Audio1
End If
End Function
Sub PlayScriptWithMPC(script As VideoScript, Optional cliArgs As String = Nothing)
If script Is Nothing Then
Exit Sub
End If
script.Synchronize()
Dim playerPath = Package.MPC.Path
If Not playerPath.FileExists AndAlso Package.MPC.VerifyOK(True) Then
playerPath = Package.MPC.Path
End If
If Not playerPath.FileExists Then
Exit Sub
End If
Dim args = ""
If cliArgs <> "" Then
args += " " + cliArgs
End If
Dim ap = GetAudioProfileForScriptPlayback()
If ap IsNot Nothing AndAlso FileTypes.Audio.Contains(ap.File.Ext) AndAlso
p.Ranges.Count = 0 Then
args += " /dub " + ap.File.Escape
End If
args += " " + script.Path.Escape
g.ShellExecute(playerPath, args.Trim)
End Sub
Sub PlayScriptWithMPV(script As VideoScript, Optional cliArgs As String = Nothing)
If script Is Nothing Then
Exit Sub
End If
script.Synchronize()
Dim args = ""
If script.Engine = ScriptEngine.VapourSynth Then
args += " --demuxer-lavf-format=vapoursynth"
End If
If cliArgs <> "" Then
args += " " + cliArgs
End If
If Calc.IsARSignalingRequired Then
args += " --video-aspect=" + Calc.GetTargetDAR.ToInvariantString.Shorten(8)
End If
Dim ap = GetAudioProfileForScriptPlayback()
If ap IsNot Nothing AndAlso FileTypes.Audio.Contains(ap.File.Ext) AndAlso p.Ranges.Count = 0 Then
args += " --audio-files=" + ap.File.Escape
End If
args += " " + script.Path.Escape
g.Execute(Package.mpvnet.Path, args.Trim)
End Sub
Function ExtractDelay(value As String) As Integer
Dim match = Regex.Match(value, " (-?\d+) ?ms")
If match.Success Then
Return CInt(match.Groups(1).Value)
End If
End Function
Function ExtractTrackNameFromFilename(filename As String) As String
Return If(filename.Base().Contains("{"), filename.Base().Right("{").LeftLast("}").UnescapeIllegalFileSysChars, Nothing)
End Function
Function ExtractLanguageFromPath(path As String) As Language
If String.IsNullOrWhiteSpace(path) Then Return Nothing
Dim ret As New Language(CultureInfo.InvariantCulture)
Dim filename = path.FileName.LeftLast(".")
For Each extracted In {filename.Right("_[").Left("]").Trim(), filename.Right("[").Left("]").Trim(), filename.Left(".").Trim(), filename.Right(".").Left(".").Trim(), Regex.Replace(filename, "{.*?}", "").Trim()}
If String.IsNullOrWhiteSpace(extracted) Then Continue For
For Each lng In Language.Languages.OrderBy(Function(x) x.Name.Length)
If extracted = lng.Name Then
ret = lng
Exit For
ElseIf extracted = lng.EnglishName Then
ret = lng
Exit For
ElseIf extracted = lng.ThreeLetterCode Then
ret = lng
Exit For
ElseIf extracted = lng.TwoLetterCode Then
ret = lng
Exit For
End If
Next
Next
If ret Is Nothing OrElse Not ret.IsDetermined Then
For Each lng In Language.Languages.OrderByDescending(Function(x) x.Name.Length)
If filename.Contains(lng.TwoLetterCode) Then
ret = lng
End If
If filename.Contains(lng.ThreeLetterCode) Then
ret = lng
End If
If path.Contains(lng.EnglishName.Left(" (")) Then
ret = lng
End If
If path.Contains(lng.EnglishName) Then
ret = lng
If filename.Contains(lng.EnglishName) Then
ret = lng
Exit For
End If
End If
If path.Contains(lng.Name) Then
ret = lng
If filename.Contains(lng.Name) Then
ret = lng
Exit For
End If
End If
Next
End If
Return ret
End Function
Function GetSourceBase() As String
Return If(p.TempDir <> "" AndAlso New DirectoryInfo(p.TempDir).Name.EndsWithEx("_temp"), "temp", p.SourceFile.Base)
End Function
Sub ShowCode(title As String, content As String, Optional find As String = Nothing, Optional wordwrap As Boolean = False)
Dim form As New CodeForm(content, find, wordwrap) With {
.Text = $"{title} - {g.DefaultCommands.GetApplicationDetails()}"
}
form.Show()
End Sub
Sub ShowHelp(title As String, content As String)
If title <> "" Then
title = title.TrimEnd("."c, ":"c)
End If
MsgInfo(title, content)
End Sub
Sub PopulateProfileMenu(
ic As ToolStripItemCollection,
profiles As IList,
dialogAction As Action,
loadAction As Action(Of Profile))
For Each iProfile As Profile In profiles
Dim a = iProfile.Name.SplitNoEmpty("|")
Dim l = ic
For i = 0 To a.Length - 1
Dim found = False
For Each iItem As ToolStripItem In l
If i < a.Length - 1 Then
If iItem.Text = a(i) + " " Then
found = True
l = DirectCast(iItem, ToolStripMenuItem).DropDownItems
End If
End If
Next
If Not found Then
If i = a.Length - 1 Then
Dim item As New MenuItemEx(a(i) + " ", Sub() loadAction(iProfile))
l.Add(item)
l = item.DropDownItems
Else
Dim item As New MenuItemEx(a(i) + " ")
l.Add(item)
l = item.DropDownItems
End If
End If
Next
Next
If dialogAction IsNot Nothing Then
ic.Add(New ToolStripSeparator)
ic.Add(New MenuItemEx("Edit Profiles...", dialogAction, "Opens the profiles editor"))
End If
End Sub
Function GetAutoSize(percentage As Integer) As Integer
Dim ret As Integer
Dim size = p.TargetSize
Dim bitrate = p.VideoBitrate
For i = 1 To 100000
p.TargetSize = i
p.VideoBitrate = CInt(Calc.GetVideoBitrate)
If CInt(Calc.GetPercent) >= percentage Then
ret = i
Exit For
End If
Next
p.TargetSize = size
p.VideoBitrate = bitrate
If ret = 0 Then ret = size
Return ret
End Function
Function GetPreviewPosMS() As Integer
Return CInt((s.LastPosition / p.Script.GetCachedFrameRate) * 1000)
End Function
Function GetTextEditorPath() As String
Dim ret = GetAppPathForExtension("txt")
Return If(ret <> "", ret, "notepad.exe")
End Function
Function GetAppPathForExtension(ParamArray extensions As String()) As String
For Each extension In extensions
If Not extension.StartsWith(".") Then
extension = "." + extension
End If
Dim c = 0UI
If Native.AssocQueryString(&H40, 2, extension, Nothing, Nothing, c) = 1 Then
If c > 0 Then
Dim sb As New StringBuilder(CInt(c))
If 0 = Native.AssocQueryString(&H40, 2, extension, Nothing, sb, c) Then
Dim ret = sb.ToString
If File.Exists(ret) Then
Return ret
End If
End If
End If
End If
Next
End Function
Sub SaveSettings()
Try
Using mutex As New Mutex(False, "staxrip settings file")
mutex.WaitOne()
SafeSerialization.Serialize(s, g.SettingsFile)
mutex.ReleaseMutex()
End Using
Dim backupPath = Path.Combine(Folder.Settings, "Backup")
If Not Directory.Exists(backupPath) Then
Directory.CreateDirectory(backupPath)
End If
FileHelp.Copy(g.SettingsFile, Path.Combine(backupPath, "Settings(v" + Application.ProductVersion + ").dat"))
Catch ex As Exception
g.ShowException(ex)
End Try
End Sub
Sub LoadVideoEncoder(profile As Profile)
Dim currentMuxer = p.VideoEncoder.Muxer
p.VideoEncoder = DirectCast(ObjectHelp.GetCopy(profile), VideoEncoder)
If currentMuxer.IsSupported(p.VideoEncoder.OutputExt) Then
p.VideoEncoder.Muxer = currentMuxer
Else
p.VideoEncoder.Muxer.Init()
End If
MainForm.tbTargetFile.Text = p.TargetFile.ChangeExt(p.VideoEncoder.Muxer.OutputExt)
p.VideoEncoder.OnStateChange()
p.VideoEncoder.SetMetaData(p.LastOriginalSourceFile)
MainForm.RecalcBitrate()
MainForm.Assistant()
End Sub
Sub LoadAudioProfile0(profile As Profile)
Dim file = p.Audio0.File
Dim delay = p.Audio0.Delay
Dim language = p.Audio0.Language
Dim stream = p.Audio0.Stream
Dim streams = p.Audio0.Streams
p.Audio0 = DirectCast(ObjectHelp.GetCopy(profile), AudioProfile)
p.Audio0.File = file
p.Audio0.Language = language
p.Audio0.Stream = stream
p.Audio0.Streams = streams
p.Audio0.Delay = delay
g.MainForm.llAudioProfile0.Text = g.ConvertPath(p.Audio0.Name)
g.MainForm.UpdateSizeOrBitrate()
g.MainForm.Assistant()
End Sub
Sub LoadAudioProfile1(profile As Profile)
Dim file = p.Audio1.File
Dim delay = p.Audio1.Delay
Dim language = p.Audio1.Language
Dim stream = p.Audio1.Stream
Dim streams = p.Audio1.Streams
p.Audio1 = DirectCast(ObjectHelp.GetCopy(profile), AudioProfile)
p.Audio1.File = file
p.Audio1.Language = language
p.Audio1.Stream = stream
p.Audio1.Streams = streams
p.Audio1.Delay = delay
g.MainForm.llAudioProfile1.Text = g.ConvertPath(p.Audio1.Name)
g.MainForm.UpdateSizeOrBitrate()
g.MainForm.Assistant()
End Sub
Sub RaiseAppEvent(ae As ApplicationEvent, Optional commandline As String = Nothing, Optional progress As Single = -1.0F, Optional progressline As String = Nothing)
Select Case ae
Case ApplicationEvent.AfterJobAdded
RaiseEvent AfterJobAdded()
Case ApplicationEvent.AfterJobFailed
RaiseEvent AfterJobFailed()
Case ApplicationEvent.AfterJobMuxed
RaiseEvent AfterJobMuxed()
Case ApplicationEvent.AfterJobProcessed
RaiseEvent AfterJobProcessed()
Case ApplicationEvent.AfterJobsProcessed
RaiseEvent AfterJobsProcessed()
Case ApplicationEvent.AfterProjectLoaded
RaiseEvent AfterProjectLoaded()
Case ApplicationEvent.AfterProjectOrSourceLoaded
RaiseEvent AfterProjectOrSourceLoaded()
Case ApplicationEvent.AfterSourceLoaded
RaiseEvent AfterSourceLoaded()
Case ApplicationEvent.AfterVideoEncoded
RaiseEvent AfterVideoEncoded()
Case ApplicationEvent.ApplicationExit
RaiseEvent ApplicationExit()
Case ApplicationEvent.BeforeJobAdding
RaiseEvent BeforeJobAdding()
Case ApplicationEvent.BeforeJobProcessed
RaiseEvent BeforeJobProcessed()
Case ApplicationEvent.BeforeProcessing
RaiseEvent BeforeProcessing()
Case ApplicationEvent.WhileProcessing
RaiseEvent WhileProcessing(commandline, progress, progressline)
End Select
Dim scriptPath = Path.Combine(Folder.Scripts, ae.ToString + ".ps1")
If File.Exists(scriptPath) Then
g.DefaultCommands.ExecutePowerShellFile(scriptPath)
End If
For Each ec In s.EventCommands
If ec.Enabled AndAlso ec.Event = ae Then
Dim matches = 0
For Each criteria In ec.CriteriaList
criteria.PropertyString = Macro.ExpandWhileProcessing(criteria.Macro, p, commandline, progress, progressline)
If criteria.Eval Then
matches += 1
End If
Next
If (ec.CriteriaList.Count = 0 OrElse (ec.OrOnly AndAlso matches > 0) OrElse
(Not ec.OrOnly AndAlso matches = ec.CriteriaList.Count)) AndAlso
ec.CommandParameters IsNot Nothing Then
Dim command = g.MainForm.CustomMainMenu.CommandManager.GetCommand(ec.CommandParameters.MethodName)
Dim params = command.FixParameters(ec.CommandParameters.Parameters)
If s.LogEventCommand AndAlso p.SourceFile <> "" Then
Log.WriteHeader("Event Command: " + ec.Name)
Log.WriteLine("Event: " + DispNameAttribute.GetValueForEnum(ec.Event))
Log.WriteLine("Command: " + command.MethodInfo.Name)
Log.WriteLine(command.GetParameterHelp(ec.CommandParameters.Parameters))
End If
For i = 0 To params.Count - 1
If TypeOf params(i) Is String Then
params(i) = Macro.ExpandWhileProcessing(DirectCast(params(i), String), p, commandline, progress, progressline)
End If
Next
g.MainForm.CustomMainMenu.CommandManager.ProcessPlusFixParams(command, params)
End If
End If
Next
End Sub
Sub SetTempDir()
If p.SourceFile <> "" Then
If p.NoTempDir Then
p.TempDir = p.SourceFile.Dir
Else
p.TempDir = Macro.Expand(p.TempDir)
If p.TempDir = "" Then
p.TempDir = If(New DirectoryInfo(p.SourceFile.Dir).Name.EndsWithEx("_temp"), p.SourceFile.Dir, Path.Combine(p.SourceFile.Dir, p.SourceFile.Base + "_temp"))
End If
p.TempDir = p.TempDir.FixDir
If Not Directory.Exists(p.TempDir) Then
Try
Directory.CreateDirectory(p.TempDir)
Catch
Try
p.TempDir = p.SourceFile.DirAndBase + "_temp"
If Not Directory.Exists(p.TempDir) Then
Directory.CreateDirectory(p.TempDir)
End If
Catch
MsgWarn("Failed to create a temp directory. By default it's created " +
"in the directory of the source file so it's not possible " +
"to open files directly from an optical drive unless a temp directory " +
"is defined in the options. Usually discs are copied to the hard drive " +
"first using an application like MakeMKV, DVDFab or AnyDVD.")
Throw New AbortException
End Try
End Try
End If
End If
End If
End Sub
Sub ShowCommandLinePreview(title As String, value As String, lineNumbers As Boolean)
If lineNumbers Then
Dim sb = New StringBuilder()
Dim lines = Regex.Split(value, "\r\n|\r|\n")
For i = 0 To lines.Length - 1
sb.AppendLine($"{i + 1,3}: {lines(i)}")
Next
value = sb.ToString()
End If
Select Case s.CommandLinePreview
Case CommandLinePreview.CodePreview
ShowCodePreview(value, Nothing, True)
Case CommandLinePreview.Powershell
Environment.SetEnvironmentVariable("CommandLineToShow", BR + value + BR)
g.Execute("powershell.exe", "-NoLogo -NoExit -NoProfile -Command $env:CommandLineToShow")
Case CommandLinePreview.WindowsTerminal
Environment.SetEnvironmentVariable("CommandLineToShow", BR + value + BR)
If g.IsWindowsTerminalAvailable Then
g.Execute("wt.exe", "powershell.exe -NoLogo -NoExit -NoProfile -Command $env:CommandLineToShow")
Else
MsgWarn("Windows Terminal not found!", "Windows Terminal could not be found and thus Powershell will be used to show the command line preview!")
g.Execute("powershell.exe", "-NoLogo -NoExit -NoProfile -Command $env:CommandLineToShow")
End If
End Select
End Sub
Sub ShowCodePreview(code As String, Optional find As String = Nothing, Optional wordwrap As Boolean = False)
ShowCode("Code Preview", code, find, wordwrap)
End Sub
Sub ffmsindex(sourcePath As String, cachePath As String, Optional indexAudio As Boolean = False, Optional proj As Project = Nothing)
If File.Exists(sourcePath) AndAlso Not File.Exists(cachePath) AndAlso Not FileTypes.VideoText.Contains(sourcePath.Ext) Then
Using proc As New Proc
proc.Header = "Indexing using ffmsindex"
proc.SkipString = "Indexing, please wait..."
proc.Project = proj
proc.Priority = ProcessPriorityClass.Normal
proc.File = Path.Combine(Package.ffms2.Directory, "ffmsindex.exe")
proc.Arguments = If(indexAudio, "-t -1 ", "") + sourcePath.LongPathPrefix.Escape + " " + cachePath.LongPathPrefix.Escape
proc.Start()
End Using
End If
End Sub
Function IsValidSource(Optional warn As Boolean = True) As Boolean
p.SourceScript.Synchronize(False, False)
If p.SourceScript.Info.FrameCount = 0 Then
If warn Then
MsgWarn("Failed to load source.")
End If
Return False
End If
If p.SourceScript.Error <> "" Then
MsgError("Script Error", p.SourceScript.Error)
Return False
End If
Return True
End Function
Function IsSourceSameOrSimilar(path As String) As Boolean
Return IsSourceSame(path) OrElse IsSourceSimilar(path)
End Function
Function IsCOMObjectRegistered(guid As String) As Boolean
Return File.Exists(Registry.ClassesRoot.GetString("CLSID\" + guid + "\InprocServer32", Nothing))
End Function
Function IsSourceSame(path As String) As Boolean
Return path.Base.StartsWith(p.SourceFile.Base) OrElse path.StartsWithEx(p.TempDir)
End Function
Function GetFilesInTempDirAndParent() As List(Of String)
Dim ret As New List(Of String)
Dim dirs As New HashSet(Of String)
If p.TempDir <> "" Then
dirs.Add(p.TempDir)
End If
If Not String.IsNullOrWhiteSpace(p.TempDir) AndAlso New DirectoryInfo(p.TempDir).Name.EndsWithEx("_temp") Then
dirs.Add(p.TempDir.Parent)
End If
dirs.Add(p.FirstOriginalSourceFile.Dir)
For Each i In dirs
ret.AddRange(Directory.GetFiles(i))
Next
Return ret
End Function
Function IsSourceSimilar(path As String) As Boolean
If p.SourceFile.Contains("_") Then
Dim sourceBase = p.SourceFile.Base
While sourceBase.Length > 2 AndAlso sourceBase.ToCharArray.Last.IsDigit
sourceBase = sourceBase.DeleteRight(1)
End While
If sourceBase.EndsWith("_") AndAlso path.Base.StartsWith(sourceBase.TrimEnd("_"c)) Then
Return True
End If
End If
End Function
Function IsCulture(twoLetterCode As String) As Boolean
Return CultureInfo.CurrentCulture.TwoLetterISOLanguageName = twoLetterCode
End Function
Private ExceptionHandled As Boolean
Sub OnException(ex As Exception)
If ExceptionHandled Then Exit Sub
ExceptionHandled = True
If TypeOf ex IsNot AbortException Then
Try
If File.Exists(p.SourceFile) Then
Dim name = p.TargetFile.Base
If name = "" Then
name = p.SourceFile.Base
End If
g.MainForm.SaveProjectPath(Path.Combine(p.SourceFile.Dir, "recovery.srip"))
End If
g.SaveSettings()
Catch
End Try
Try
ShowException(ex)
MakeBugReport(ex)
Catch
End Try
Process.GetCurrentProcess.Kill()
End If
End Sub
Sub ShowException(ex As Exception, Optional title As String = Nothing, Optional content As String = Nothing, Optional timeout As Integer = 0)
If timeout < 0 Then Exit Sub
Try
Using td As New TaskDialog(Of String)
td.Title = If(title = "",
If(TypeOf ex Is ErrorAbortException,
DirectCast(ex, ErrorAbortException).Title + $" (v{Application.ProductVersion})",
ex.GetType.Name + $" (v{Application.ProductVersion})"),
title)
td.Timeout = timeout
td.Content = (ex.Message + BR2 + content).Trim()
td.Icon = TaskIcon.Error
td.ExpandedContent = ex.ToString().Trim()
td.ShowCopyButton = True
td.AddButton("OK")
td.Show()
End Using
Catch
Dim msg = If(TypeOf ex Is ErrorAbortException, DirectCast(ex, ErrorAbortException).Title, ex.GetType.Name)
VB6.MsgBox(msg + BR2 + ex.Message + BR2 + ex.ToString, VB6.MsgBoxStyle.Critical)
End Try
End Sub
Sub ArchiveLogFile(path As String)
Try
Dim logFolder = IO.Path.Combine(Folder.Settings, "Log Files")
If Not Directory.Exists(logFolder) Then
Directory.CreateDirectory(logFolder)
End If
FileHelp.Copy(path, IO.Path.Combine(logFolder, Date.Now.ToString("yyyy-MM-dd - HH.mm.ss") + " - " + path.FileName))
Dim di As New DirectoryInfo(logFolder)
While di.GetFiles("*.log").Length > s.LogFileNum
FileHelp.Delete(di.GetFiles("*.log").OrderBy(Function(val) val.LastWriteTime).First.FullName)
End While
Catch ex As Exception
ShowException(ex, "Failed to archive log file")
End Try
End Sub
Sub SetRenderer(ms As ToolStrip)
ms.Renderer = If(VisualStyleInformation.IsEnabledByUser, New ToolStripRendererEx(), DirectCast(New ToolStripSystemRenderer(), ToolStripRenderer))
End Sub
Sub Play(file As String)
g.ShellExecute(Package.mpvnet.Path.Escape, file.Escape)
End Sub
Sub ShowWikiPage(title As String)
ShellExecute($"https://github.com/staxrip/staxrip/wiki/{title}")
End Sub
Sub OnUnhandledException(sender As Object, e As ThreadExceptionEventArgs)
OnException(e.Exception)
End Sub
Sub OnUnhandledException(sender As Object, e As UnhandledExceptionEventArgs)
OnException(DirectCast(e.ExceptionObject, Exception))
End Sub
Private IconValue As Icon
Private LastIconFile As String
Public Property Icon As Icon
Get
If IconValue Is Nothing OrElse s.IconFile <> LastIconFile Then
If File.Exists(s.IconFile) Then
IconValue = New Icon(s.IconFile)
LastIconFile = s.IconFile
Else
IconValue = My.Resources.Black
End If
End If
Return IconValue
End Get
Set(value As Icon)
IconValue = value
End Set
End Property
Sub MakeBugReport(e As Exception)
If e Is Nothing AndAlso Not g.IsValidSource(False) Then
Exit Sub
End If
If e IsNot Nothing Then
If Log.IsEmpty Then Log.WriteEnvironment()
Log.WriteHeader("Exception")
Log.WriteLine(e.ToString)
End If
Log.Save(p)
Dim logfileOpened = False
Dim fp = Log.GetPath
If MsgQuestion("An error occured", "Do you want to open the log file?",
TaskButton.YesNo) = DialogResult.Yes Then
g.ShellExecute(g.GetTextEditorPath(), fp.Escape)
logfileOpened = True
End If
If MsgQuestion("Bug Report", "Do you want to report an issue or bug?",
TaskButton.YesNo) = DialogResult.Yes Then
If Not logfileOpened Then
g.ShellExecute(g.GetTextEditorPath(), fp.Escape)
End If
g.SelectFileWithExplorer(fp)
g.ShellExecute("https://github.com/staxrip/staxrip/issues")
End If
End Sub
Function FileExists(path As String) As Boolean
For x = 1 To 6
If File.Exists(path) Then
Return True
End If
Thread.Sleep(500)
Next
End Function
Sub ShutdownPC()
ShutdownPC(CType(Registry.CurrentUser.GetInt("Software\" + Application.ProductName, "ShutdownMode"), ShutdownMode))
End Sub
Sub ShutdownPC(mode As ShutdownMode)
If mode <> ShutdownMode.Nothing Then
g.SavedProject = p
g.MainForm.Close()
Registry.CurrentUser.Write("Software\" + Application.ProductName, "ShutdownMode", 0)
Shutdown.Commit(mode)
End If
End Sub
Function EnableFilter(search As String) As Boolean
For Each Filter In p.Script.Filters
If Filter.Category = search Then
If Not Filter.Active Then
Filter.Active = True
g.MainForm.FiltersListView.Load()
End If
Return True
End If
Next
End Function
Function BrowseFile(filter As String) As String
Using dialog As New OpenFileDialog
dialog.Filter = filter
dialog.SetInitDir(p.TempDir)
If dialog.ShowDialog = DialogResult.OK Then
Return dialog.FileName
End If
End Using
End Function
Sub ShowScriptInfo(script As VideoScript)
script.Synchronize()
If Not script.Path.FileExists Then
Exit Sub
End If
Dim text = If(script.Error = "", script.Info.GetInfoText(-1), script.Error)
text = BR + " " + text.FixBreak.Replace(BR, BR + " ") + BR
Using form As New StringEditorForm
form.ScaleClientSize(25, 15)
form.MaximizeBox = False
form.rtb.ReadOnly = True
form.cbWrap.Checked = False
form.cbWrap.Visible = False
form.rtb.Text = text
form.Text = $"{script.Path} - {g.DefaultCommands.GetApplicationDetails()}"
form.bnOK.Visible = False
form.bnCancel.Text = "Close"
form.ShowDialog()
End Using
End Sub
Sub AddHardcodedSubtitle()
Dim validSubtitles = p.VideoEncoder?.Muxer?.Subtitles?.Where(Function(x) x.Path.Ext().ToLowerEx().EqualsAny("srt", "ass", "idx"))
Dim enabledValidSubtitles = validSubtitles?.Where(Function(x) x.Enabled)
Dim selectedSubtitle As Subtitle = If(enabledValidSubtitles?.Any(), enabledValidSubtitles?.FirstOrDefault(), validSubtitles?.FirstOrDefault())
If selectedSubtitle IsNot Nothing Then
selectedSubtitle.Enabled = False
p.AddHardcodedSubtitleFilter(selectedSubtitle.Path, False)
End If
End Sub
Sub CheckForModifiedDolbyVisionLevel5Data()
If p.HdrDolbyVisionMetadataFile IsNot Nothing Then
If p.HdrDolbyVisionMetadataFile.HasLevel5Changed Then
p.HdrDolbyVisionMetadataFile.RefreshLevel5Data()
'MainForm.AutoCrop()
'Dim newCrop = p.HdrDolbyVisionMetadataFile.Crop
'g.SetCrop(newCrop.Left, newCrop.Top, newCrop.Right, newCrop.Bottom, True, False)
End If
End If
End Sub
Sub SetCrop(left As Integer, top As Integer, right As Integer, bottom As Integer, direction As ForceOutputModDirection, Optional force As Boolean = False)
p.CropLeft = Math.Max(0, left)
p.CropTop = Math.Max(0, top)
p.CropRight = Math.Max(0, right)
p.CropBottom = Math.Max(0, bottom)
CorrectCropMod(direction, force)
MainForm.SetCropFilter()
MainForm.DisableCropFilter()
MainForm.Assistant()
End Sub
Sub RunAutoCrop(progressAction As Action(Of Double))
If p.SourceScript Is Nothing Then Return
If String.IsNullOrWhiteSpace(p.SourceScript.Path) Then Return
If Not p.SourceScript.Path.FileExists() Then Return
p.SourceScript.Synchronize(True, True, True)
If p.HdrDolbyVisionMetadataFile IsNot Nothing Then
Dim c = p.HdrDolbyVisionMetadataFile.Crop
SetCrop(c.Left, c.Top, c.Right, c.Bottom, ForceOutputModDirection.Decrease, True)
Else
Using server = FrameServerFactory.Create(p.SourceScript.Path)
Dim info = server.Info
Dim frameCount = info.FrameCount
Dim frameRate = info.FrameRate
Dim startFrame = 0
Dim endFrame = frameCount
Dim consideredFrames = endFrame - startFrame
Dim minFrames = 5
Dim interval = CInt(VB6.Conversion.Fix(frameRate * 60))
If p.AutoCropFrameSelectionMode = AutoCropFrameSelectionMode.FixedNumber Then
interval = consideredFrames \ (p.AutoCropFixedNumberFrameSelection - 1)
ElseIf p.AutoCropFrameSelectionMode = AutoCropFrameSelectionMode.TimeInterval Then
interval = CInt(VB6.Conversion.Fix(frameRate * p.AutoCropTimeIntervalFrameSelection))
If interval * minFrames > consideredFrames Then
interval = CInt(VB6.Conversion.Fix(consideredFrames / minFrames))
End If
End If
Dim analyzeCount = (consideredFrames \ interval) + 1
Dim analyzeFrames(analyzeCount - 1) As Integer
Dim crops(analyzeCount - 1) As AutoCrop
For i = 0 To analyzeCount - 1
analyzeFrames(i) = Math.Min(startFrame + i * interval, endFrame)
Next
For i = 0 To analyzeCount - 1
progressAction?.Invoke(i / analyzeCount * 100)
Dim frame = analyzeFrames(i)
Using bmp = BitmapUtil.CreateBitmap(server, frame)
crops(i) = AutoCrop.Start(bmp.Clone(New Rectangle(0, 0, bmp.Width, bmp.Height), PixelFormat.Format32bppRgb), frame)
End Using
Next
Dim left = crops.SelectMany(Function(arg) arg.Left).Min()
Dim top = crops.SelectMany(Function(arg) arg.Top).Min()
Dim right = crops.SelectMany(Function(arg) arg.Right).Min()
Dim bottom = crops.SelectMany(Function(arg) arg.Bottom).Min()
SetCrop(left, top, right, bottom, p.ForcedOutputModDirection, False)
End Using
End If
End Sub
Sub SmartCrop()
If Not p.Script.IsFilterActive("Resize") Then
Exit Sub
End If
Dim tempLeft = p.CropLeft
Dim tempRight = p.CropRight
Dim ae = Math.Abs(Calc.GetAspectRatioError)
While (p.SourceWidth - p.CropLeft - p.CropRight) > 64
p.CropLeft += 2
p.CropRight += 2
If Math.Abs(Calc.GetAspectRatioError()) < ae Then
tempLeft = p.CropLeft
tempRight = p.CropRight
ae = Math.Abs(Calc.GetAspectRatioError())
End If
End While
p.CropLeft = tempLeft
p.CropRight = tempRight
Dim tempTop = p.CropTop
Dim tempBottom = p.CropBottom
While (p.SourceHeight - p.CropTop - p.CropBottom) > 64
p.CropTop += 2
p.CropBottom += 2
If Math.Abs(Calc.GetAspectRatioError()) < ae Then
tempTop = p.CropTop
tempBottom = p.CropBottom
ae = Math.Abs(Calc.GetAspectRatioError())
End If
End While
p.CropTop = tempTop
p.CropBottom = tempBottom
End Sub
Sub OvercropWidth()
If p.AutoSmartOvercrop > 0 AndAlso
p.AutoSmartOvercrop < Calc.GetTargetDAR Then
Dim newar = p.AutoSmartOvercrop
Dim croph = p.SourceHeight - p.CropTop - p.CropBottom
g.MainForm.tbTargetHeight.Text = Calc.FixMod16(CInt(p.TargetWidth / newar)).ToString
newar = CSng(p.TargetWidth / p.TargetHeight)
Dim cropw = (newar / Calc.GetSourceDAR * (croph / p.SourceHeight)) * p.SourceWidth
p.CropLeft = CInt((p.SourceWidth - cropw) / 2)
p.CropRight = p.CropLeft
If p.CropLeft < 0 Then
p.CropLeft = 0
p.CropRight = 0
End If
CorrectCropMod(p.ForcedOutputModDirection, False)
End If
End Sub
Function ConvertPath(value As String) As String
If value = "" Then
Return ""
End If
If value.Length > 30 AndAlso value.Contains("|") Then
value = value.RightLast("|")
End If
If value.Contains("Constant Quality") Then
value = value.Replace("Constant Quality", "CQ")
End If
If value.Contains(" | ") Then
value = value.Replace(" | ", " - ")
End If
If value.Contains(" ") Then
value = value.Replace(" ", " ")
End If
Return value.Trim
End Function
Sub ForceCropMod()
If Not g.EnableFilter("Crop") Then
p.Script.InsertAfter("Source", New VideoFilter("Crop", "Crop", "Crop(%crop_left%, %crop_top%, -%crop_right%, -%crop_bottom%)"))
End If
CorrectCropMod(p.ForcedOutputModDirection, True)
End Sub
Sub CorrectCropMod(direction As ForceOutputModDirection, force As Boolean)
If p.AutoCorrectCropValues OrElse force Then
If direction = ForceOutputModDirection.Increase Then
p.CropLeft += p.CropLeft Mod 2
p.CropRight += p.CropRight Mod 2
p.CropTop += p.CropTop Mod 2
p.CropBottom += p.CropBottom Mod 2
Else
p.CropLeft -= p.CropLeft Mod 2
p.CropRight -= p.CropRight Mod 2
p.CropTop -= p.CropTop Mod 2
p.CropBottom -= p.CropBottom Mod 2
End If
Dim modValue = 4
If Not p.Script.IsFilterActive("Resize") Then
modValue = p.ForcedOutputMod
End If
Dim whalf = ((p.SourceWidth - p.CropLeft - p.CropRight) Mod modValue) \ 2
Dim hhalf = ((p.SourceHeight - p.CropTop - p.CropBottom) Mod modValue) \ 2
If direction = ForceOutputModDirection.Increase Then
If p.CropLeft > p.CropRight Then
p.CropLeft += whalf - whalf Mod 2
p.CropRight += whalf + whalf Mod 2
Else
p.CropRight += whalf - whalf Mod 2
p.CropLeft += whalf + whalf Mod 2
End If
If p.CropTop > p.CropBottom Then
p.CropTop += hhalf - hhalf Mod 2
p.CropBottom += hhalf + hhalf Mod 2
Else
p.CropBottom += hhalf - hhalf Mod 2
p.CropTop += hhalf + hhalf Mod 2
End If
Else
If p.CropLeft > p.CropRight Then
p.CropLeft -= whalf - whalf Mod 2
p.CropRight -= whalf + whalf Mod 2
Else
p.CropRight -= whalf - whalf Mod 2
p.CropLeft -= whalf + whalf Mod 2
End If
If p.CropTop > p.CropBottom Then
p.CropTop -= hhalf - hhalf Mod 2
p.CropBottom -= hhalf + hhalf Mod 2
Else
p.CropBottom -= hhalf - hhalf Mod 2
p.CropTop -= hhalf + hhalf Mod 2
End If
End If
g.MainForm.FiltersListView.Load()
End If
End Sub
Function GetTimeString(sec As Double) As String
Dim ts = TimeSpan.FromSeconds(sec)
Return CInt(Math.Floor(ts.TotalMinutes)).ToString("00") + ":" +
CInt(Math.Floor(ts.Seconds)).ToString("00")
End Function
Sub ShowAdvancedScriptInfo(script As VideoScript)
script.Synchronize()
If Not script.Path.FileExists Then
Exit Sub
End If
If script.Engine = ScriptEngine.AviSynth Then
Using td As New TaskDialog(Of String)
td.Title = "Choose below"
td.AddCommand("Info()")
td.AddCommand("avsmeter benchmark")
td.AddCommand("avsmeter info")
td.AddCommand("avs2pipemod info")
Select Case td.Show()
Case "avs2pipemod info"
g.RunCodeInTerminal($"""`n{Package.avs2pipemod.Name} {Package.avs2pipemod.Version}""; & '{Package.avs2pipemod.Path}' -dll=""{Package.AviSynth.Path.Escape}"" -info '{script.Path}'")
Case "avsmeter benchmark"
g.RunCodeInTerminal($"& '{Package.AVSMeter.Path}' '{script.Path.ToShortFilePath}'")
Case "avsmeter info"
g.RunCodeInTerminal($"& '{Package.AVSMeter.Path}' -info '{script.Path.ToShortFilePath}';""""")
Case "Info()"
Dim infoScript = New VideoScript
infoScript.AddFilter(New VideoFilter($"Import(""{script.Path}"")"))
Dim infoCode = $"Info(size={(script.GetInfo().Height * 0.05).ToInvariantString()})"
infoScript.AddFilter(New VideoFilter(infoCode))
infoScript.Path = Path.Combine(p.TempDir, p.TargetFile.Base + $"_info." + script.FileType)
Dim errorMsg = infoScript.GetError()
If errorMsg <> "" Then
MsgError("Script Error", errorMsg)
Exit Sub
End If
g.PlayScriptWithMPV(infoScript, "--pause=yes --osc=no --osd-level=0")
End Select
End Using
Else
Using td As New TaskDialog(Of String)
td.Title = "Choose below"
td.AddCommand("ClipInfo()")
td.AddCommand("vspipe info")
Select Case td.Show()
Case "vspipe info"
g.RunCodeInTerminal($"""`n{Package.vspipe.Name} {Package.vspipe.Version}`n""; & '{Package.vspipe.Path}' --info '{script.Path}' -;""""")
Case "ClipInfo()"
Dim infoScript = script.GetNewScript
infoScript.Path = Path.Combine(p.TempDir, p.TargetFile.Base + "_info." + script.FileType)
infoScript.AddFilter(New VideoFilter("clip = clip.resize.Bicubic(720, (720 / clip.width * clip.height) // 8 * 8)"))
infoScript.AddFilter(New VideoFilter("clip = core.text.ClipInfo(clip)"))
Dim errorMsg = infoScript.GetError()
If errorMsg <> "" Then
MsgError("Script Error", errorMsg)
Exit Sub
End If
g.PlayScriptWithMPV(infoScript, "--pause=yes --osc=no --osd-level=0")
End Select
End Using
End If
End Sub
Function IsDevelopmentPC() As Boolean
Return New DirectoryInfo(Application.StartupPath).Name.EndsWithEx("bin") OrElse New DirectoryInfo(Application.StartupPath).Name.EndsWithEx("bin-x86")
End Function
Sub RunTask(action As Action)
Task.Run(Sub()
Try
action.Invoke
Catch ex As Exception
OnException(ex)
End Try
End Sub)
End Sub
Function RunSTATask(action As Action) As Task
Dim tcs = New TaskCompletionSource(Of Object)()
Dim thread = New Thread(Sub()
Try
action?.Invoke()
tcs.SetResult(Nothing)
Catch ex As Exception
tcs.SetException(ex)
End Try
End Sub)
thread.SetApartmentState(ApartmentState.STA)
thread.Start()
Return tcs.Task
End Function
Sub UpdateTrim(script As VideoScript, Optional moveToTheEnd As Boolean = False)
If script Is Nothing Then Exit Sub
If moveToTheEnd OrElse Not p.Ranges.Any() Then script.RemoveFilter("Cutting")
p.RangesBasedOnFPS = script.OriginalInfo.FrameRate
Dim filter As VideoFilter = script.GetFilter("Cutting")
If p.Ranges.Any() Then
If filter Is Nothing Then
filter = New VideoFilter()
script.Filters.Add(filter)
End If
filter.Path = "Cutting"
filter.Category = "Cutting"
filter.Script = GetTrim(script)
filter.Active = True
End If
End Sub
Function GetTrim(script As VideoScript) As String
Dim ret = ""
For Each i In p.Ranges
If ret <> "" Then
ret += " + "
End If
If script.Engine = ScriptEngine.AviSynth Then
ret += "Trim(" & i.Start & ", " & i.End & ")"
If p.TrimCode <> "" Then
ret += "." + p.TrimCode.TrimStart("."c)
End If
Else
ret += "clip[" & i.Start & ":" & (i.End + 1) & "]"
End If
Next
Return If(script.Engine = ScriptEngine.AviSynth, ret, "clip = " + ret)
End Function
Function ContainsPipeTool(value As String) As Boolean
If value <> "" Then
Return value.Contains("ffmpeg") OrElse value.Contains("avs2pipemod") OrElse value.Contains("vspipe")
End If
End Function
Function GetFilterProfilesText(categories As List(Of FilterCategory)) As String
Dim ret = ""
Dim wasMultiline As Boolean
For Each i In categories
ret += "[" + i.Name + "]" + BR
For Each filter In i.Filters
If filter.Script.Contains(BR) Then
Dim lines = filter.Script.SplitLinesNoEmpty
For x = 0 To lines.Length - 1
lines(x) = " " + lines(x)
Next
ret += BR + filter.Path + " =" + BR + lines.Join(BR) + BR
wasMultiline = True
Else
If wasMultiline Then
ret += BR
End If
ret += filter.Path + " = " + filter.Script + BR
wasMultiline = False
End If
Next
If Not ret.EndsWith(BR2) Then
ret += BR
End If
Next
Return ret
End Function
Function GetCodeFont(Optional size As Single = 10.0) As Font
Return New Font(If(s.CodeFont = "", "Consolas", s.CodeFont), size * s.UIScaleFactor)
End Function
End Class