Imports System.Text Imports StaxRip.UI Imports StaxRip.CommandLine Public MustInherit Class VideoEncoder Inherits Profile Implements IComparable(Of VideoEncoder) MustOverride Sub Encode() MustOverride ReadOnly Property OutputFileType As String Overridable Property Passes As Integer Overridable Property QualityMode As Boolean Property AutoCompCheckValue As Integer = 70 Property Muxer As Muxer = New MkvMuxer Public MustOverride Sub ShowConfigDialog() Sub New() CanEditValue = True End Sub Private OutputPathValue As String Overridable ReadOnly Property OutputPath() As String Get If TypeOf Muxer Is EncoderMuxerBase OrElse TypeOf Muxer Is NullMuxer Then Return p.TargetFile Else Return p.TempDir + p.Name + "_out." + OutputFileType End If End Get End Property Overridable Function GetMenu() As MenuList End Function Overridable Sub ImportCommandLine(commandLine As String) Throw New NotImplementedException("import is not implemented for this encoder") End Sub Sub AfterEncoding() If Not g.WasFileJustWritten(OutputPath) Then Throw New ErrorAbortException("Encoder output file is missing", OutputPath) Else Log.WriteLine(MediaInfo.GetSummary(OutputPath)) End If End Sub Overrides Function CreateEditControl() As Control Dim ret As New ToolStripEx ret.ShowItemToolTips = False ret.GripStyle = ToolStripGripStyle.Hidden ret.BackColor = System.Drawing.SystemColors.Window ret.Dock = DockStyle.Fill ret.BackColor = System.Drawing.SystemColors.Window ret.LayoutStyle = System.Windows.Forms.ToolStripLayoutStyle.VerticalStackWithOverflow ret.Padding = New Padding(1, 1, 0, 0) ret.Font = New Font("Segoe UI", 9 * s.UIScaleFactor) For Each i In GetMenu() Dim b As New ToolStripButton b.Margin = New Padding(0) b.Text = i.Key b.Padding = New Padding(4) Dim happy = i AddHandler b.Click, Sub() happy.Value.Invoke() b.TextAlign = ContentAlignment.MiddleLeft ret.Items.Add(b) Next Return ret End Function Overridable ReadOnly Property IsCompCheckEnabled() As Boolean Get Return Not QualityMode End Get End Property Protected Sub OnAfterCompCheck() If p.CompCheckAction = CompCheckAction.AdjustFileSize Then Dim oldSize = g.MainForm.tbTargetSize.Text g.MainForm.tbTargetSize.Text = g.GetAutoSize(AutoCompCheckValue).ToString Log.WriteLine("Target size: " & oldSize & " MB -> " + g.MainForm.tbTargetSize.Text + " MB") ElseIf p.CompCheckAction = CompCheckAction.AdjustImageSize Then AutoSetImageSize() End If End Sub Sub AutoSetImageSize() If p.VideoEncoder.AutoCompCheckValue > 0 AndAlso Calc.GetPercent <> 0 AndAlso p.Script.IsFilterActive("Resize") Then Dim oldWidth = p.TargetWidth Dim oldHeight = p.TargetHeight p.TargetWidth = Calc.FixMod16(CInt((p.SourceHeight - p.CropTop - p.CropBottom) * Calc.GetTargetDAR())) Dim cropw = p.SourceWidth - p.CropLeft - p.CropRight If p.TargetWidth > cropw Then p.TargetWidth = cropw End If p.TargetHeight = Calc.FixMod16(CInt(p.TargetWidth / Calc.GetTargetDAR())) While Calc.GetPercent < p.VideoEncoder.AutoCompCheckValue If p.TargetWidth - 16 >= 320 Then p.TargetWidth -= 16 p.TargetHeight = Calc.FixMod16(CInt(p.TargetWidth / Calc.GetTargetDAR())) Else Exit While End If End While g.MainForm.tbTargetWidth.Text = p.TargetWidth.ToString g.MainForm.tbTargetHeight.Text = p.TargetHeight.ToString Log.WriteLine("Target image size: " & oldWidth.ToString & "x" & oldHeight.ToString & " -> " & p.TargetWidth.ToString & "x" & p.TargetHeight.ToString) If p.AutoSmartCrop Then g.MainForm.StartSmartCrop() End If End If End Sub Overrides Sub Clean() Muxer.Clean() End Sub Overridable Function GetError() As String Return Nothing End Function Sub OnStateChange() g.MainForm.UpdateEncoderStateRelatedControls() g.MainForm.SetEncoderControl(p.VideoEncoder.CreateEditControl) g.MainForm.lgbEncoder.Text = g.ConvertPath(p.VideoEncoder.Name).Shorten(38) g.MainForm.llMuxer.Text = p.VideoEncoder.Muxer.OutputType.ToUpper g.MainForm.tbSize_TextChanged() End Sub Public Enum Modes First = 1 Second = 2 CompCheck = 4 End Enum Sub OpenMuxerConfigDialog() Dim m = ObjectHelp.GetCopy(Of Muxer)(Muxer) If m.Edit = DialogResult.OK Then Muxer = m g.MainForm.llMuxer.Text = Muxer.OutputType.ToUpper g.MainForm.Refresh() g.MainForm.tbSize_TextChanged() g.MainForm.Assistant() End If End Sub Function OpenMuxerProfilesDialog() As DialogResult Using f As New ProfilesForm("Muxer Profiles", s.MuxerProfiles, AddressOf LoadMuxer, AddressOf GetMuxerProfile, AddressOf Muxer.GetDefaults) Return f.ShowDialog() End Using End Function Sub LoadMuxer(profile As Profile) Muxer = DirectCast(ObjectHelp.GetCopy(profile), Muxer) Muxer.Init() g.MainForm.llMuxer.Text = Muxer.OutputType.ToUpper g.MainForm.tbTargetFile.Text = p.TargetFile.ChangeExt(Muxer.GetExtension) g.MainForm.RecalcBitrate() g.MainForm.Assistant() End Sub Private Function GetMuxerProfile() As Profile Dim sb As New SelectionBox(Of Muxer) sb.Title = "New Profile" sb.Text = "Please choose a profile." sb.AddItem("Current Project", Muxer) For Each i In StaxRip.Muxer.GetDefaults() sb.AddItem(i) Next If sb.Show = DialogResult.OK Then Return sb.SelectedItem Return Nothing End Function Shared Function Getx264Encoder( name As String, device As x264DeviceMode, Optional mode As x264Mode = x264Mode.SingleCRF) As x264Encoder Return Getx264Encoder(name, mode, x264PresetMode.Medium, x264TuneMode.Disabled, device) End Function Shared Function Getx264Encoder(name As String, mode As x264Mode, preset As x264PresetMode, tuning As x264TuneMode, device As x264DeviceMode) As x264Encoder Dim r As New x264Encoder() r.Name = name r.Params.Mode.Value = mode r.Params.Preset.Value = preset r.Params.Tune.Value = tuning r.Params.Device.Value = device r.Params.ApplyDeviceSettings() r.Params.ApplyDefaults(r.Params) r.Params.ApplyDeviceSettings() r.SetMuxer() Return r End Function Shared Function GetDefaults() As List(Of VideoEncoder) Dim ret As New List(Of VideoEncoder) ret.Add(Getx264Encoder("x264", x264Mode.SingleCRF, x264PresetMode.Medium, x264TuneMode.Disabled, x264DeviceMode.Disabled)) Dim x265crf = New x265Encoder x265crf.Params.ApplyPresetDefaultValues() x265crf.Params.ApplyPresetValues() ret.Add(x265crf) ret.Add(New IntelEncoder()) Dim intel265 As New IntelEncoder() intel265.Params.Codec.Value = 1 ret.Add(intel265) Dim nvidia264 As New NVIDIAEncoder() nvidia264.Params.Mode.Value = 3 ret.Add(nvidia264) Dim nvidia265 As New NVIDIAEncoder() nvidia265.Params.Mode.Value = 3 nvidia265.Params.Codec.Value = 1 ret.Add(nvidia265) Dim amd264 As New AMDEncoder() amd264.Params.Mode.Value = 2 ret.Add(amd264) Dim xvid As New BatchEncoder() xvid.OutputFileTypeValue = "avi" xvid.Name = "XviD" xvid.Muxer = New ffmpegMuxer("AVI") xvid.QualityMode = True xvid.CommandLines = """%app:xvid_encraw%"" -cq 2 -smoother 0 -max_key_interval 250 -nopacked -vhqmode 4 -qpel -notrellis -max_bframes 1 -bvhq -bquant_ratio 162 -bquant_offset 0 -threads 1 -i ""%script_file%"" -avi ""%encoder_out_file%"" -par %target_sar%" ret.Add(xvid) ret.Add(New NullEncoder()) ret.Add(Getx264Encoder("2 pass | x264", x264Mode.TwoPass, x264PresetMode.Medium, x264TuneMode.Disabled, x264DeviceMode.Disabled)) Dim x265_2pass = New x265Encoder x265_2pass.Name = "2 pass | x265" x265_2pass.Params.Mode.Value = x265RateMode.TwoPass x265_2pass.Params.ApplyPresetDefaultValues() x265_2pass.Params.ApplyPresetValues() ret.Add(x265_2pass) Dim xvid2pass As New BatchEncoder() xvid2pass.OutputFileTypeValue = "avi" xvid2pass.Name = "2 pass | XviD" xvid2pass.Muxer = New ffmpegMuxer("AVI") xvid2pass.CommandLines = """%app:xvid_encraw%"" -smoother 0 -max_key_interval 250 -nopacked -vhqmode 4 -qpel -notrellis -max_bframes 1 -bvhq -bquant_ratio 162 -bquant_offset 0 -threads 1 -bitrate %video_bitrate% -par %target_sar% -turbo -pass1 ""%temp_file%.stats"" -i ""%script_file%"" || exit" + BR + """%app:xvid_encraw%"" -smoother 0 -max_key_interval 250 -nopacked -vhqmode 4 -qpel -notrellis -max_bframes 1 -bvhq -bquant_ratio 162 -bquant_offset 0 -threads 1 -bitrate %video_bitrate% -par %target_sar% -pass2 ""%temp_file%.stats"" -i ""%script_file%"" -avi ""%encoder_out_file%""" xvid2pass.CompCheckCommandLines = """%app:xvid_encraw%"" -cq 2 -smoother 0 -max_key_interval 250 -nopacked -vhqmode 4 -qpel -notrellis -max_bframes 1 -bvhq -bquant_ratio 162 -bquant_offset 0 -threads 1 -par %target_sar% -i ""%temp_file%_CompCheck.%script_ext%"" -avi ""%temp_file%_CompCheck.avi""" ret.Add(xvid2pass) Dim ffmpeg = New ffmpegEncoder() For x = 0 To ffmpeg.Params.Codec.Options.Length - 1 Dim ffmpeg2 = New ffmpegEncoder() ffmpeg2.Params.Codec.Value = x ret.Add(ffmpeg2) Next Dim x264cli As New BatchEncoder() x264cli.OutputFileTypeValue = "h264" x264cli.Name = "Command Line | x264" x264cli.Muxer = New MkvMuxer() x264cli.AutoCompCheckValue = 50 x264cli.CommandLines = """%app:x264%"" --pass 1 --bitrate %video_bitrate% --stats ""%temp_file%.stats"" --output NUL ""%script_file%"" || exit" + BR + """%app:x264%"" --pass 2 --bitrate %video_bitrate% --stats ""%temp_file%.stats"" --output ""%encoder_out_file%"" ""%script_file%""" x264cli.CompCheckCommandLines = """%app:x264%"" --crf 18 --output ""%temp_file%_CompCheck%encoder_ext%"" ""%temp_file%_CompCheck.%script_ext%""" ret.Add(x264cli) Dim divx As New BatchEncoder() divx.OutputFileTypeValue = "h265" divx.Name = "Command Line | DivX H.265" divx.Muxer = New MkvMuxer() divx.CommandLines = """%app:DivX265%"" -i ""%script_file%"" -o ""%encoder_out_file%"" -br %video_bitrate% -10" ret.Add(divx) Dim nvencH265 As New BatchEncoder() nvencH265.OutputFileTypeValue = "h265" nvencH265.Name = "Command Line | NVIDIA H.265" nvencH265.Muxer = New MkvMuxer() nvencH265.QualityMode = True nvencH265.CommandLines = """%app:ffmpeg%"" -i ""%script_file%"" -f yuv4mpegpipe -pix_fmt yuv420p -loglevel error - | ""%app:NVEncC%"" --sar %target_sar% --codec h265 --y4m --cqp 20 -i - -o ""%encoder_out_file%""" ret.Add(nvencH265) ret.Add(Getx264Encoder("Devices | DivX Plus", x264DeviceMode.DivXPlus)) ret.Add(Getx264Encoder("Devices | Blu-ray", x264DeviceMode.BluRay)) ret.Add(Getx264Encoder("Devices | iPhone", x264DeviceMode.iPhone)) ret.Add(Getx264Encoder("Devices | PlayStation", x264DeviceMode.PlayStation)) ret.Add(Getx264Encoder("Devices | Xbox", x264DeviceMode.Xbox)) Return ret End Function Function CompareToVideoEncoder(other As VideoEncoder) As Integer Implements System.IComparable(Of VideoEncoder).CompareTo Return Name.CompareTo(other.Name) End Function Overridable Sub RunCompCheck() End Sub Shared Sub SaveProfile(encoder As VideoEncoder) Dim name = InputBox.Show("Please enter a profile name.", "Profile Name", encoder.Name) If name <> "" Then encoder.Name = name For Each i In From prof In s.VideoEncoderProfiles.ToArray Where prof.GetType Is encoder.GetType If i.Name = name Then s.VideoEncoderProfiles(s.VideoEncoderProfiles.IndexOf(i)) = encoder Exit Sub End If Next s.VideoEncoderProfiles.Insert(0, encoder) End If End Sub Overrides Function Edit() As DialogResult Using f As New ControlHostForm(Name) f.AddControl(CreateEditControl, Nothing) f.ShowDialog() End Using Return DialogResult.OK End Function Class MenuList Inherits List(Of KeyValuePair(Of String, Action)) Overloads Sub Add(text As String, action As Action) Add(New KeyValuePair(Of String, Action)(text, action)) End Sub End Class End Class Public MustInherit Class BasicVideoEncoder Inherits VideoEncoder MustOverride ReadOnly Property CommandLineParams As CommandLineParams Public Overrides Sub ImportCommandLine(commandLine As String) If commandLine = "" Then Exit Sub Dim a = commandLine.SplitNoEmptyAndWhiteSpace(" ") For x = 0 To a.Length - 1 For Each param In CommandLineParams.Items If Not param.ImportAction Is Nothing AndAlso param.GetSwitches.Contains(a(x)) AndAlso a.Length - 1 > x Then param.ImportAction.Invoke(a(x + 1)) Exit For End If If TypeOf param Is BoolParam Then Dim boolParam = DirectCast(param, BoolParam) If boolParam.GetSwitches.Contains(a(x)) Then boolParam.Value = True Exit For End If ElseIf TypeOf param Is NumParam Then Dim numParam = DirectCast(param, NumParam) If numParam.GetSwitches.Contains(a(x)) AndAlso a.Length - 1 > x AndAlso a(x + 1).IsSingle Then numParam.Value = a(x + 1).ToSingle Exit For End If ElseIf TypeOf param Is OptionParam Then Dim optionParam = DirectCast(param, OptionParam) If optionParam.GetSwitches.Contains(a(x)) AndAlso a.Length - 1 > x Then Dim exitFor As Boolean For xOpt = 0 To optionParam.Options.Length - 1 If a(x + 1).Trim(""""c) = optionParam.Options(xOpt) Then optionParam.Value = xOpt exitFor = True Exit For End If Next If exitFor Then Exit For End If ElseIf TypeOf param Is StringParam Then Dim stringParam = DirectCast(param, StringParam) If stringParam.GetSwitches.Contains(a(x)) AndAlso a.Length - 1 > x Then stringParam.Value = a(x + 1).Trim(""""c) Exit For End If End If Next Next End Sub End Class Class BatchEncoder Inherits VideoEncoder Sub New() Name = "Command Line" Muxer = New MkvMuxer() End Sub Property CommandLines As String = "" Property CompCheckCommandLines As String = "" Property OutputFileTypeValue As String Overrides ReadOnly Property OutputfileType As String Get If OutputFileTypeValue = "" Then OutputFileTypeValue = "h264" Return OutputFileTypeValue End Get End Property Overrides Sub ShowConfigDialog() Using f As New BatchEncoderForm(Me) If f.ShowDialog() = DialogResult.OK Then OnStateChange() End If End Using End Sub Overrides Function GetMenu() As MenuList Dim ret As New MenuList ret.Add("Codec Configuration", AddressOf ShowConfigDialog) If IsCompCheckEnabled Then ret.Add("Run Compressibility Check", AddressOf RunCompCheck) End If ret.Add("Container Configuration", AddressOf OpenMuxerConfigDialog) Return ret End Function Function GetSkipStrings(commands As String) As String() If commands.Contains("DivX265") Then Return {"encoded @"} ElseIf commands.Contains("xvid_encraw") Then Return {"key="} ElseIf commands.Contains("x264") Then Return {"frames,"} ElseIf commands.Contains("NVEncC") Then Return {"frames: "} Else Return {" [ETA ", ", eta ", "frames: ", "frame= "} End If End Function Overrides Sub Encode() p.Script.Synchronize() Dim commands = Macro.Solve(CommandLines).Trim If commands.Contains("|") OrElse commands.Contains(BR) Then Dim batchPath = p.TempDir + Filepath.GetBase(p.TargetFile) + "_encode.bat" File.WriteAllText(batchPath, commands, Encoding.GetEncoding(850)) Using proc As New Proc proc.Init("Encoding video command line encoder: " + Name) proc.SkipStrings = GetSkipStrings(commands) proc.WriteLine(commands + BR2) proc.File = "cmd.exe" proc.Arguments = "/C call """ + batchPath + """" Try proc.Start() Catch ex As AbortException Throw ex Catch ex As Exception ProcessForm.CloseProcessForm() g.ShowException(ex) Throw New AbortException End Try End Using Else Using proc As New Proc proc.Init("Encoding video command line encoder: " + Name) proc.SkipStrings = GetSkipStrings(commands) proc.CommandLine = commands Try proc.Start() Catch ex As AbortException Throw ex Catch ex As Exception ProcessForm.CloseProcessForm() g.ShowException(ex) Throw New AbortException End Try End Using End If End Sub Overrides Sub RunCompCheck() If CompCheckCommandLines = "" OrElse CompCheckCommandLines.Trim = "" Then ShowConfigDialog() Exit Sub End If If Not g.VerifyRequirements Then Exit Sub If Not g.IsValidSource Then Exit Sub ProcessForm.ShowForm() Log.WriteHeader("Compressibility Check") Dim script As New VideoScript script.Engine = p.Script.Engine script.Filters = p.Script.GetFiltersCopy Dim code As String Dim every = ((100 \ p.CompCheckRange) * 14).ToString If script.Engine = ScriptEngine.AviSynth Then code = "SelectRangeEvery(" + every + ",14)" Else code = "fpsnum = clip.fps_num" + BR + "fpsden = clip.fps_den" + BR + "clip = core.std.SelectEvery(clip = clip, cycle = " + every + ", offsets = range(14))" + BR + "clip = core.std.AssumeFPS(clip = clip, fpsnum = fpsnum, fpsden = fpsden)" End If Log.WriteLine(code + BR2) script.Filters.Add(New VideoFilter("aaa", "aaa", code)) script.Path = p.TempDir + p.Name + "_CompCheck." + script.FileType script.Synchronize() Dim batchPath = p.TempDir + Filepath.GetBase(p.TargetFile) + "_CompCheck.bat" Dim command = Macro.Solve(CompCheckCommandLines) File.WriteAllText(batchPath, command, Encoding.GetEncoding(850)) Log.WriteLine(command + BR2) Using proc As New Proc proc.Init(Nothing) proc.SkipStrings = GetSkipStrings(command) proc.File = "cmd.exe" proc.Arguments = "/C call """ + batchPath + """" Try proc.Start() Catch ex As AbortException ProcessForm.CloseProcessForm() Exit Sub Catch ex As Exception ProcessForm.CloseProcessForm() g.ShowException(ex) Exit Sub End Try End Using Dim bits = (New FileInfo(p.TempDir + p.Name + "_CompCheck." + OutputfileType).Length) * 8 p.Compressibility = (bits / script.GetFrames) / (p.TargetWidth * p.TargetHeight) OnAfterCompCheck() g.MainForm.Assistant() Log.WriteLine(CInt(Calc.GetPercent).ToString() + " %") ProcessForm.CloseProcessForm() End Sub End Class Class NullEncoder Inherits VideoEncoder Sub New() Name = "Just Mux" Muxer = New MkvMuxer() QualityMode = True End Sub Function GetSource() As String For Each i In {".h264", ".avc", ".h265", ".hevc", ".mpg", ".avi"} If File.Exists(Filepath.GetDirAndBase(p.SourceFile) + "_out" + i) Then Return Filepath.GetDirAndBase(p.SourceFile) + "_out" + i ElseIf File.Exists(p.TempDir + p.Name + "_out" + i) Then Return p.TempDir + p.Name + "_out" + i End If Next If FileTypes.VideoText.Contains(p.SourceFile.Ext) Then Return p.LastOriginalSourceFile Else Return p.SourceFile End If End Function Overrides ReadOnly Property OutputPath As String Get Dim source = GetSource() If Not p.VideoEncoder.Muxer.IsSupported(source.Ext) Then Select Case source.ext Case "mkv" Dim streams = MediaInfo.GetVideoStreams(source) If streams.Count = 0 Then Return source Dim stream = streams(0) If stream.Extension = "" Then Throw New Exception("demuxing of video stream format is not implemented") Return p.TempDir + Filepath.GetBase(source) + "_out" + stream.Extension End Select End If Return source End Get End Property Overrides ReadOnly Property OutputFileType As String Get Return Filepath.GetExt(OutputPath) End Get End Property Overrides Sub Encode() Dim source = GetSource() If Not p.VideoEncoder.Muxer.IsSupported(Filepath.GetExt(source)) Then Select Case Filepath.GetExt(source) Case "mkv" Video.DemuxMKV(source) End Select End If End Sub Overrides Function GetMenu() As MenuList Dim ret As New MenuList ret.Add("Container Configuration", AddressOf OpenMuxerConfigDialog) Return ret End Function Overrides Sub ShowConfigDialog() End Sub End Class