From 3fa47a70a47f164242c77c5820621c89b43b05b5 Mon Sep 17 00:00:00 2001 From: n00mkrad Date: Sun, 15 Jan 2023 17:23:49 +0100 Subject: [PATCH] Completely revamped output settings (quality settings WIP) --- Code/Data/EncoderInfo.cs | 20 +++ Code/Data/EncoderInfoVideo.cs | 22 +++ Code/Data/Enums.cs | 20 +++ Code/Data/ExportSettings.cs | 15 ++ Code/Data/InterpSettings.cs | 30 ++-- Code/Data/Strings.cs | 54 +++++++ Code/Extensions/ExtensionMethods.cs | 95 ++++++++++++ Code/Flowframes.csproj | 7 + Code/Form1.Designer.cs | 100 +++++++++++- Code/Form1.cs | 100 +++++++----- Code/Forms/BatchForm.cs | 5 +- Code/Forms/SettingsForm.cs | 25 +-- Code/IO/IoUtils.cs | 7 +- Code/Main/AutoEncode.cs | 6 +- Code/Main/Export.cs | 28 ++-- Code/Main/Interpolate.cs | 6 +- Code/Main/InterpolateSteps.cs | 6 +- Code/Main/InterpolateUtils.cs | 33 ++-- Code/Media/FfmpegAudioAndMetadata.cs | 6 +- Code/Media/FfmpegCommands.cs | 9 +- Code/Media/FfmpegEncode.cs | 16 +- Code/Media/FfmpegUtils.cs | 189 ++++++++--------------- Code/MiscUtils/OutputUtils.cs | 223 +++++++++++++++++++++++++++ Code/MiscUtils/ParseUtils.cs | 38 +++++ Code/Os/AiProcess.cs | 2 +- 25 files changed, 801 insertions(+), 261 deletions(-) create mode 100644 Code/Data/EncoderInfo.cs create mode 100644 Code/Data/EncoderInfoVideo.cs create mode 100644 Code/Data/Enums.cs create mode 100644 Code/Data/ExportSettings.cs create mode 100644 Code/Data/Strings.cs create mode 100644 Code/MiscUtils/OutputUtils.cs create mode 100644 Code/MiscUtils/ParseUtils.cs diff --git a/Code/Data/EncoderInfo.cs b/Code/Data/EncoderInfo.cs new file mode 100644 index 0000000..b5bd648 --- /dev/null +++ b/Code/Data/EncoderInfo.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Flowframes.Data +{ + public class EncoderInfo + { + public string Name { get; set; } = "unknown"; + + public EncoderInfo() { } + + public EncoderInfo(string name) + { + Name = name; + } + } +} diff --git a/Code/Data/EncoderInfoVideo.cs b/Code/Data/EncoderInfoVideo.cs new file mode 100644 index 0000000..77f17cb --- /dev/null +++ b/Code/Data/EncoderInfoVideo.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static Flowframes.Data.Enums.Encoding; + +namespace Flowframes.Data +{ + public class EncoderInfoVideo : EncoderInfo + { + public Codec Codec { get; set; } = (Codec)(-1); + public bool Lossless { get; set; } = false; + public bool HwAccelerated { get; set; } = false; + public int Modulo { get; set; } = 2; + public int MaxFramerate { get; set; } = 1000; + public List PixelFormats { get; set; } = new List(); + public PixelFormat PixelFormatDefault { get; set; } + public bool IsImageSequence { get; set; } = false; + public string OverideExtension { get; set; } = ""; + } +} diff --git a/Code/Data/Enums.cs b/Code/Data/Enums.cs new file mode 100644 index 0000000..69cb691 --- /dev/null +++ b/Code/Data/Enums.cs @@ -0,0 +1,20 @@ +namespace Flowframes.Data +{ + public class Enums + { + public class Output + { + public enum Format { Mp4, Mkv, Webm, Mov, Avi, Gif, Images, Realtime }; + public enum ImageFormat { Png, Jpeg, Webp }; + public enum Dithering { None, Bayer, FloydSteinberg }; + } + + public class Encoding + { + public enum Codec { H264, H265, AV1, VP9, ProRes, Gif, Png, Jpeg, Webp, Ffv1, Huffyuv, Magicyuv, Rawvideo } + public enum Encoder { X264, X265, SvtAv1, VpxVp9, Nvenc264, Nvenc265, NvencAv1, ProResKs, Gif, Png, Jpeg, Webp, Ffv1, Huffyuv, Magicyuv, Rawvideo } + public enum PixelFormat { Yuv420P, Yuva420P, Yuv420P10Le, Yuv422P, Yuv422P10Le, Yuv444P, Yuv444P10Le, Yuva444P10Le, Rgb24, Rgba, Rgb8 }; + public enum ProResProfiles { Proxy, Lt, Standard, Hq, Quad4, Quad4Xq } + } + } +} \ No newline at end of file diff --git a/Code/Data/ExportSettings.cs b/Code/Data/ExportSettings.cs new file mode 100644 index 0000000..6cbc310 --- /dev/null +++ b/Code/Data/ExportSettings.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Flowframes.Data +{ + public class ExportSettings + { + public Enums.Output.Format Format { get; set; } + public Enums.Encoding.Encoder Encoder { get; set; } + public Enums.Encoding.PixelFormat PixelFormat { get; set; } + } +} diff --git a/Code/Data/InterpSettings.cs b/Code/Data/InterpSettings.cs index 5f600cf..4eac8dd 100644 --- a/Code/Data/InterpSettings.cs +++ b/Code/Data/InterpSettings.cs @@ -25,7 +25,7 @@ namespace Flowframes public Fraction outFps; public float outItsScale; public float interpFactor; - public Interpolate.OutMode outMode; + public ExportSettings outSettings; public ModelCollection.ModelInfo model; public string tempFolder; @@ -46,7 +46,7 @@ namespace Flowframes public InterpSettings() { } - public InterpSettings(string inPathArg, string outPathArg, AI aiArg, Fraction inFpsDetectedArg, Fraction inFpsArg, float interpFactorArg, float itsScale, Interpolate.OutMode outModeArg, ModelCollection.ModelInfo modelArg) + public InterpSettings(string inPathArg, string outPathArg, AI aiArg, Fraction inFpsDetectedArg, Fraction inFpsArg, float interpFactorArg, float itsScale, ExportSettings outSettingsArg, ModelCollection.ModelInfo modelArg) { inPath = inPathArg; outPath = outPathArg; @@ -56,7 +56,7 @@ namespace Flowframes interpFactor = interpFactorArg; outFps = inFpsArg * (double)interpFactorArg; outItsScale = itsScale; - outMode = outModeArg; + outSettings = outSettingsArg; model = modelArg; alpha = false; @@ -95,7 +95,7 @@ namespace Flowframes inFps = new Fraction(); interpFactor = 0; outFps = new Fraction(); - outMode = Interpolate.OutMode.VidMp4; + outSettings = new ExportSettings(); model = null; alpha = false; stepByStep = false; @@ -112,6 +112,7 @@ namespace Flowframes entries.Add(keyValuePair[0], keyValuePair[1]); } + // TODO: Rework this ugly stuff, JSON? foreach (KeyValuePair entry in entries) { switch (entry.Key) @@ -123,7 +124,7 @@ namespace Flowframes case "INFPS": inFps = new Fraction(entry.Value); break; case "OUTFPS": outFps = new Fraction(entry.Value); break; case "INTERPFACTOR": interpFactor = float.Parse(entry.Value); break; - case "OUTMODE": outMode = (Interpolate.OutMode)Enum.Parse(typeof(Interpolate.OutMode), entry.Value); break; + case "OUTMODE": outSettings.Format = (Enums.Output.Format)Enum.Parse(typeof(Enums.Output.Format), entry.Value); break; case "MODEL": model = AiModels.GetModelByName(ai, entry.Value); break; case "INPUTRES": _inputResolution = FormatUtils.ParseSize(entry.Value); break; case "ALPHA": alpha = bool.Parse(entry.Value); break; @@ -173,14 +174,13 @@ namespace Flowframes try { bool alphaModel = model.SupportsAlpha; - bool png = outMode == Interpolate.OutMode.ImgPng; - bool gif = outMode == Interpolate.OutMode.VidGif; - bool proResAlpha = outMode == Interpolate.OutMode.VidProRes && Config.GetInt(Config.Key.proResProfile) > 3; - bool outputSupportsAlpha = png || gif || proResAlpha; + bool pngOutput = outSettings.Encoder == Enums.Encoding.Encoder.Png; + bool gifOutput = outSettings.Encoder == Enums.Encoding.Encoder.Gif; + bool proResAlpha = outSettings.Encoder == Enums.Encoding.Encoder.ProResKs && Config.GetInt(Config.Key.proResProfile) > 3; // TODO: CHECK IF WORKS WITH NEW ENCODING SETTINGS CODE + bool outputSupportsAlpha = pngOutput || gifOutput || proResAlpha; string ext = inputIsFrames ? Path.GetExtension(IoUtils.GetFilesSorted(inPath).First()).ToLowerInvariant() : Path.GetExtension(inPath).ToLowerInvariant(); alpha = (alphaModel && outputSupportsAlpha && (ext == ".gif" || ext == ".png" || ext == ".apng" || ext == ".mov")); - Logger.Log($"RefreshAlpha: model.supportsAlpha = {alphaModel} - outputSupportsAlpha = {outputSupportsAlpha} - " + - $"input ext: {ext} => alpha = {alpha}", true); + Logger.Log($"RefreshAlpha: model.supportsAlpha = {alphaModel} - outputSupportsAlpha = {outputSupportsAlpha} - input ext: {ext} => alpha = {alpha}", true); } catch (Exception e) { @@ -193,9 +193,9 @@ namespace Flowframes public void RefreshExtensions(FrameType type = FrameType.Both) { - bool pngOutput = outMode == Interpolate.OutMode.ImgPng; - bool aviHqChroma = outMode == Interpolate.OutMode.VidAvi && Config.Get(Config.Key.aviColors) != "yuv420p"; - bool proresHqChroma = outMode == Interpolate.OutMode.VidProRes && Config.GetInt(Config.Key.proResProfile) > 3; + bool pngOutput = outSettings.Encoder == Enums.Encoding.Encoder.Png; + bool aviHqChroma = outSettings.Format == Enums.Output.Format.Avi && outSettings.PixelFormat != Enums.Encoding.PixelFormat.Yuv420P; // TODO: CHECK IF WORKS WITH NEW ENCODING SETTINGS CODE + bool proresHqChroma = outSettings.Encoder == Enums.Encoding.Encoder.ProResKs && Config.GetInt(Config.Key.proResProfile) > 3; // TODO: CHECK IF WORKS WITH NEW ENCODING SETTINGS CODE bool forceHqChroma = pngOutput || aviHqChroma || proresHqChroma; @@ -230,7 +230,7 @@ namespace Flowframes s += $"INFPS|{inFps}\n"; s += $"OUTFPS|{outFps}\n"; s += $"INTERPFACTOR|{interpFactor}\n"; - s += $"OUTMODE|{outMode}\n"; + s += $"OUTMODE|{outSettings.Format}\n"; s += $"MODEL|{model.Name}\n"; s += $"INPUTRES|{InputResolution.Width}x{InputResolution.Height}\n"; s += $"OUTPUTRES|{ScaledResolution.Width}x{ScaledResolution.Height}\n"; diff --git a/Code/Data/Strings.cs b/Code/Data/Strings.cs new file mode 100644 index 0000000..d08c0fd --- /dev/null +++ b/Code/Data/Strings.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; + +namespace Flowframes.Data +{ + public class Strings + { + public static Dictionary OutputFormat = new Dictionary + { + { Enums.Output.Format.Mp4.ToString(), "MP4" }, + { Enums.Output.Format.Mkv.ToString(), "MKV" }, + { Enums.Output.Format.Webm.ToString(), "WEBM" }, + { Enums.Output.Format.Mov.ToString(), "MOV" }, + { Enums.Output.Format.Avi.ToString(), "AVI" }, + { Enums.Output.Format.Gif.ToString(), "GIF" }, + { Enums.Output.Format.Images.ToString(), "Images" }, + { Enums.Output.Format.Realtime.ToString(), "Real-time" }, + }; + + public static Dictionary Encoder = new Dictionary + { + { Enums.Encoding.Encoder.X264.ToString(), "h264" }, + { Enums.Encoding.Encoder.X265.ToString(), "h265" }, + { Enums.Encoding.Encoder.SvtAv1.ToString(), "AV1" }, + { Enums.Encoding.Encoder.VpxVp9.ToString(), "VP9" }, + { Enums.Encoding.Encoder.ProResKs.ToString(), "ProRes" }, + { Enums.Encoding.Encoder.Nvenc264.ToString(), "h264 (NVENC)" }, + { Enums.Encoding.Encoder.Nvenc265.ToString(), "h265 (NVENC)" }, + { Enums.Encoding.Encoder.NvencAv1.ToString(), "AV1 (NVENC)" }, + { Enums.Encoding.Encoder.Gif.ToString(), "Animated GIF" }, + { Enums.Encoding.Encoder.Png.ToString(), "PNG" }, + { Enums.Encoding.Encoder.Jpeg.ToString(), "JPEG" }, + { Enums.Encoding.Encoder.Webp.ToString(), "WEBP" }, + { Enums.Encoding.Encoder.Ffv1.ToString(), "FFV1" }, + { Enums.Encoding.Encoder.Huffyuv.ToString(), "HuffYUV" }, + { Enums.Encoding.Encoder.Magicyuv.ToString(), "MagicYUV" }, + { Enums.Encoding.Encoder.Rawvideo.ToString(), "Raw Video" }, + }; + + public static Dictionary PixelFormat = new Dictionary + { + { Enums.Encoding.PixelFormat.Yuv420P.ToString(), "YUV 4:2:0 8-bit" }, + { Enums.Encoding.PixelFormat.Yuva420P.ToString(), "YUVA 4:2:0 8-bit" }, + { Enums.Encoding.PixelFormat.Yuv420P10Le.ToString(), "YUV 4:2:0 10-bit" }, + { Enums.Encoding.PixelFormat.Yuv422P.ToString(), "YUV 4:2:2 8-bit" }, + { Enums.Encoding.PixelFormat.Yuv422P10Le.ToString(), "YUV 4:2:2 10-bit" }, + { Enums.Encoding.PixelFormat.Yuv444P.ToString(), "YUV 4:4:4 8-bit" }, + { Enums.Encoding.PixelFormat.Yuv444P10Le.ToString(), "YUV 4:4:4 10-bit" }, + { Enums.Encoding.PixelFormat.Yuva444P10Le.ToString(), "YUVA 4:4:4 10-bit" }, + { Enums.Encoding.PixelFormat.Rgb24.ToString(), "RGB 8-bit" }, + { Enums.Encoding.PixelFormat.Rgb8.ToString(), "RGB 256-color" }, + { Enums.Encoding.PixelFormat.Rgba.ToString(), "RGBA 8-bit" }, + }; + } +} diff --git a/Code/Extensions/ExtensionMethods.cs b/Code/Extensions/ExtensionMethods.cs index d33d1c2..123a765 100644 --- a/Code/Extensions/ExtensionMethods.cs +++ b/Code/Extensions/ExtensionMethods.cs @@ -10,6 +10,7 @@ using System.Windows.Forms; using Flowframes.Data; using System.Management.Automation; using System.Drawing; +using Flowframes.MiscUtils; namespace Flowframes { @@ -271,5 +272,99 @@ namespace Flowframes { return $"{(filePath.IsConcatFile() ? filePath.GetConcStr() : "")} -i {filePath.Wrap()}"; } + + public static string Get(this Dictionary dict, string key, bool returnKeyInsteadOfEmptyString = false, bool ignoreCase = false) + { + if (key == null) + key = ""; + + for (int i = 0; i < dict.Count; i++) + { + if (ignoreCase) + { + if (key.Lower() == dict.ElementAt(i).Key.Lower()) + return dict.ElementAt(i).Value; + } + else + { + if (key == dict.ElementAt(i).Key) + return dict.ElementAt(i).Value; + } + } + + if (returnKeyInsteadOfEmptyString) + return key; + else + return ""; + } + + public static void FillFromEnum(this ComboBox comboBox, Dictionary stringMap = null, int defaultIndex = -1, List exclusionList = null) where TEnum : Enum + { + if (stringMap == null) + stringMap = new Dictionary(); + + if (exclusionList == null) + exclusionList = new List(); + + comboBox.Items.Clear(); + var entriesToAdd = Enum.GetValues(typeof(TEnum)).Cast().Except(exclusionList); + comboBox.Items.AddRange(entriesToAdd.Select(x => stringMap.Get(x.ToString(), true)).ToArray()); + + if (defaultIndex >= 0) + comboBox.SelectedIndex = defaultIndex; + } + + public static void FillFromEnum(this ComboBox comboBox, IEnumerable entries, Dictionary stringMap = null, int defaultIndex = -1) where TEnum : Enum + { + if (stringMap == null) + stringMap = new Dictionary(); + + comboBox.Items.Clear(); + comboBox.Items.AddRange(entries.Select(x => stringMap.Get(x.ToString(), true)).ToArray()); + + if (defaultIndex >= 0 && comboBox.Items.Count > 0) + comboBox.SelectedIndex = defaultIndex; + } + + public static void SetIfTextMatches(this ComboBox comboBox, string str, bool ignoreCase = true, Dictionary stringMap = null) + { + if (stringMap == null) + stringMap = new Dictionary(); + + str = stringMap.Get(str, true, true); + + for (int i = 0; i < comboBox.Items.Count; i++) + { + if (ignoreCase) + { + if (comboBox.Items[i].ToString().Lower() == str.Lower()) + { + comboBox.SelectedIndex = i; + return; + } + } + else + { + if (comboBox.Items[i].ToString() == str) + { + comboBox.SelectedIndex = i; + return; + } + } + } + } + + public static string Lower(this string s) + { + if (s == null) + return s; + + return s.ToLowerInvariant(); + } + + public static EncoderInfoVideo GetInfo (this Enums.Encoding.Encoder enc) + { + return OutputUtils.GetEncoderInfoVideo(enc); + } } } diff --git a/Code/Flowframes.csproj b/Code/Flowframes.csproj index 595a02a..6937963 100644 --- a/Code/Flowframes.csproj +++ b/Code/Flowframes.csproj @@ -334,6 +334,10 @@ + + + + @@ -344,6 +348,7 @@ + @@ -439,6 +444,8 @@ + + diff --git a/Code/Form1.Designer.cs b/Code/Form1.Designer.cs index 3ec73bd..69aaed1 100644 --- a/Code/Form1.Designer.cs +++ b/Code/Form1.Designer.cs @@ -90,6 +90,10 @@ this.label15 = new System.Windows.Forms.Label(); this.label11 = new System.Windows.Forms.Label(); this.interpOptsTab = new System.Windows.Forms.TabPage(); + this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); + this.comboxOutputFormat = new System.Windows.Forms.ComboBox(); + this.comboxOutputCrf = new System.Windows.Forms.ComboBox(); + this.comboxOutputColors = new System.Windows.Forms.ComboBox(); this.aiInfoBtn = new HTAlt.WinForms.HTButton(); this.outSpeedCombox = new System.Windows.Forms.ComboBox(); this.completionActionPanel = new System.Windows.Forms.Panel(); @@ -137,6 +141,7 @@ this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); this.pauseBtn = new System.Windows.Forms.Button(); this.cancelBtn = new System.Windows.Forms.Button(); + this.comboxOutputEncoder = new System.Windows.Forms.ComboBox(); this.panel1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.pictureBox4)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.pictureBox3)).BeginInit(); @@ -149,6 +154,7 @@ this.panel8.SuspendLayout(); this.panel6.SuspendLayout(); this.interpOptsTab.SuspendLayout(); + this.flowLayoutPanel1.SuspendLayout(); this.completionActionPanel.SuspendLayout(); this.quickSettingsTab.SuspendLayout(); this.mpDedupePanel.SuspendLayout(); @@ -228,7 +234,7 @@ "Animated GIF (Only supports up to 50 FPS)", "Image Sequence (PNG, JPG, WEBP)", "Real-time Interpolation (Video only)"}); - this.outModeCombox.Location = new System.Drawing.Point(281, 157); + this.outModeCombox.Location = new System.Drawing.Point(283, 229); this.outModeCombox.Name = "outModeCombox"; this.outModeCombox.Size = new System.Drawing.Size(400, 23); this.outModeCombox.TabIndex = 16; @@ -915,6 +921,7 @@ // this.interpOptsTab.AllowDrop = true; this.interpOptsTab.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48))))); + this.interpOptsTab.Controls.Add(this.flowLayoutPanel1); this.interpOptsTab.Controls.Add(this.aiInfoBtn); this.interpOptsTab.Controls.Add(this.pictureBox5); this.interpOptsTab.Controls.Add(this.outSpeedCombox); @@ -954,6 +961,66 @@ this.interpOptsTab.DragDrop += new System.Windows.Forms.DragEventHandler(this.Form1_DragDrop); this.interpOptsTab.DragEnter += new System.Windows.Forms.DragEventHandler(this.Form1_DragEnter); // + // flowLayoutPanel1 + // + this.flowLayoutPanel1.Controls.Add(this.comboxOutputFormat); + this.flowLayoutPanel1.Controls.Add(this.comboxOutputEncoder); + this.flowLayoutPanel1.Controls.Add(this.comboxOutputCrf); + this.flowLayoutPanel1.Controls.Add(this.comboxOutputColors); + this.flowLayoutPanel1.Location = new System.Drawing.Point(281, 157); + this.flowLayoutPanel1.Name = "flowLayoutPanel1"; + this.flowLayoutPanel1.Size = new System.Drawing.Size(614, 23); + this.flowLayoutPanel1.TabIndex = 46; + // + // comboxOutputFormat + // + this.comboxOutputFormat.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); + this.comboxOutputFormat.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboxOutputFormat.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.comboxOutputFormat.ForeColor = System.Drawing.Color.White; + this.comboxOutputFormat.FormattingEnabled = true; + this.comboxOutputFormat.Items.AddRange(new object[] { + "MP4 Video (h264, h265, AV1)", + "MKV Video (h264, h265, AV1) (Best Audio/Subtitles Support)", + "WEBM Video (Google VP9)", + "MOV Video (Apple ProRes)", + "AVI Video (ffv1, huffyuv, magicyuv, rawvideo)", + "Animated GIF (Only supports up to 50 FPS)", + "Image Sequence (PNG, JPG, WEBP)", + "Real-time Interpolation (Video only)"}); + this.comboxOutputFormat.Location = new System.Drawing.Point(0, 0); + this.comboxOutputFormat.Margin = new System.Windows.Forms.Padding(0, 0, 6, 0); + this.comboxOutputFormat.Name = "comboxOutputFormat"; + this.comboxOutputFormat.Size = new System.Drawing.Size(80, 23); + this.comboxOutputFormat.TabIndex = 47; + this.comboxOutputFormat.SelectedIndexChanged += new System.EventHandler(this.comboxOutputFormat_SelectedIndexChanged); + // + // comboxOutputCrf + // + this.comboxOutputCrf.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); + this.comboxOutputCrf.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.comboxOutputCrf.ForeColor = System.Drawing.Color.White; + this.comboxOutputCrf.FormattingEnabled = true; + this.comboxOutputCrf.Location = new System.Drawing.Point(182, 0); + this.comboxOutputCrf.Margin = new System.Windows.Forms.Padding(0, 0, 6, 0); + this.comboxOutputCrf.Name = "comboxOutputCrf"; + this.comboxOutputCrf.Size = new System.Drawing.Size(50, 23); + this.comboxOutputCrf.TabIndex = 48; + this.comboxOutputCrf.Text = "24"; + // + // comboxOutputColors + // + this.comboxOutputColors.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); + this.comboxOutputColors.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboxOutputColors.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.comboxOutputColors.ForeColor = System.Drawing.Color.White; + this.comboxOutputColors.FormattingEnabled = true; + this.comboxOutputColors.Location = new System.Drawing.Point(238, 0); + this.comboxOutputColors.Margin = new System.Windows.Forms.Padding(0, 0, 6, 0); + this.comboxOutputColors.Name = "comboxOutputColors"; + this.comboxOutputColors.Size = new System.Drawing.Size(110, 23); + this.comboxOutputColors.TabIndex = 49; + // // aiInfoBtn // this.aiInfoBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); @@ -1033,7 +1100,7 @@ this.encodingSettingsBtn.FlatAppearance.BorderSize = 0; this.encodingSettingsBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; this.encodingSettingsBtn.ForeColor = System.Drawing.Color.White; - this.encodingSettingsBtn.Location = new System.Drawing.Point(689, 157); + this.encodingSettingsBtn.Location = new System.Drawing.Point(689, 192); this.encodingSettingsBtn.Name = "encodingSettingsBtn"; this.encodingSettingsBtn.Size = new System.Drawing.Size(206, 23); this.encodingSettingsBtn.TabIndex = 39; @@ -1641,6 +1708,29 @@ this.cancelBtn.UseVisualStyleBackColor = true; this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click); // + // comboxOutputEncoder + // + this.comboxOutputEncoder.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); + this.comboxOutputEncoder.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboxOutputEncoder.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.comboxOutputEncoder.ForeColor = System.Drawing.Color.White; + this.comboxOutputEncoder.FormattingEnabled = true; + this.comboxOutputEncoder.Items.AddRange(new object[] { + "MP4 Video (h264, h265, AV1)", + "MKV Video (h264, h265, AV1) (Best Audio/Subtitles Support)", + "WEBM Video (Google VP9)", + "MOV Video (Apple ProRes)", + "AVI Video (ffv1, huffyuv, magicyuv, rawvideo)", + "Animated GIF (Only supports up to 50 FPS)", + "Image Sequence (PNG, JPG, WEBP)", + "Real-time Interpolation (Video only)"}); + this.comboxOutputEncoder.Location = new System.Drawing.Point(86, 0); + this.comboxOutputEncoder.Margin = new System.Windows.Forms.Padding(0, 0, 6, 0); + this.comboxOutputEncoder.Name = "comboxOutputEncoder"; + this.comboxOutputEncoder.Size = new System.Drawing.Size(90, 23); + this.comboxOutputEncoder.TabIndex = 50; + this.comboxOutputEncoder.SelectedIndexChanged += new System.EventHandler(this.comboxOutputEncoder_SelectedIndexChanged); + // // Form1 // this.AllowDrop = true; @@ -1694,6 +1784,7 @@ this.panel6.PerformLayout(); this.interpOptsTab.ResumeLayout(false); this.interpOptsTab.PerformLayout(); + this.flowLayoutPanel1.ResumeLayout(false); this.completionActionPanel.ResumeLayout(false); this.completionActionPanel.PerformLayout(); this.quickSettingsTab.ResumeLayout(false); @@ -1824,6 +1915,11 @@ private System.Windows.Forms.ComboBox outSpeedCombox; private System.Windows.Forms.PictureBox pictureBox5; private HTAlt.WinForms.HTButton aiInfoBtn; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; + public System.Windows.Forms.ComboBox comboxOutputFormat; + public System.Windows.Forms.ComboBox comboxOutputCrf; + public System.Windows.Forms.ComboBox comboxOutputColors; + public System.Windows.Forms.ComboBox comboxOutputEncoder; } } diff --git a/Code/Form1.cs b/Code/Form1.cs index d3ab728..5c4566f 100644 --- a/Code/Form1.cs +++ b/Code/Form1.cs @@ -55,7 +55,8 @@ namespace Flowframes // Main Tab UiUtils.InitCombox(interpFactorCombox, 0); UiUtils.InitCombox(outSpeedCombox, 0); - UiUtils.InitCombox(outModeCombox, 0); + //UiUtils.InitCombox(outModeCombox, 0); + InitOutputUi(); UiUtils.InitCombox(aiModel, 2); // Video Utils UiUtils.InitCombox(trimCombox, 0); @@ -84,6 +85,39 @@ namespace Flowframes await Checks(); } + private void InitOutputUi() + { + comboxOutputFormat.FillFromEnum(Strings.OutputFormat, 0); + UpdateOutputUi(); + } + + private void UpdateOutputUi() + { + var outMode = ParseUtils.GetEnum(comboxOutputFormat.Text, true, Strings.OutputFormat); + comboxOutputEncoder.FillFromEnum(OutputUtils.GetAvailableEncoders(outMode), Strings.Encoder, 0); + comboxOutputEncoder.Visible = comboxOutputEncoder.Items.Count > 1; + + UpdateOutputEncodingUi(); + } + + private void UpdateOutputEncodingUi() + { + var encoder = ParseUtils.GetEnum(comboxOutputEncoder.Text, true, Strings.Encoder); + bool noEncoder = (int)encoder == -1; + + comboxOutputCrf.Visible = !noEncoder; + comboxOutputColors.Visible = !noEncoder; + + if (noEncoder) + return; + + EncoderInfoVideo info = OutputUtils.GetEncoderInfoVideo(encoder); + comboxOutputCrf.Visible = !info.Lossless; + var pixelFormats = info.PixelFormats; + comboxOutputColors.Visible = pixelFormats.Count > 0; + comboxOutputColors.FillFromEnum(pixelFormats, Strings.PixelFormat, 0); + } + async Task Checks() { try @@ -164,8 +198,7 @@ namespace Flowframes public InterpSettings GetCurrentSettings() { SetTab("interpolate"); - return new InterpSettings(inputTbox.Text.Trim(), outputTbox.Text.Trim(), GetAi(), currInFpsDetected, currInFps, - interpFactorCombox.GetFloat(), outSpeedCombox.GetInt().Clamp(1, 64), GetOutMode(), GetModel(GetAi())); + return new InterpSettings(inputTbox.Text.Trim(), outputTbox.Text.Trim(), GetAi(), currInFpsDetected, currInFps, interpFactorCombox.GetFloat(), outSpeedCombox.GetInt().Clamp(1, 64), GetExportSettings, GetModel(GetAi())); } public InterpSettings UpdateCurrentSettings(InterpSettings settings) @@ -185,7 +218,7 @@ namespace Flowframes settings.inFps = currInFps; settings.interpFactor = interpFactorCombox.GetFloat(); settings.outFps = settings.inFps * settings.interpFactor; - settings.outMode = GetOutMode(); + settings.outSettings = GetExportSettings; settings.model = GetModel(GetAi()); return settings; @@ -197,7 +230,7 @@ namespace Flowframes MainUiFunctions.SetOutPath(outputTbox, entry.outPath); interpFactorCombox.Text = entry.interpFactor.ToString(); aiCombox.SelectedIndex = Implementations.NetworksAvailable.IndexOf(Implementations.NetworksAvailable.Where(x => x.NameInternal == entry.ai.NameInternal).FirstOrDefault()); - SetOutMode(entry.outMode); + SetFormat(entry.outSettings.Format); } public void SetStatus(string str) @@ -287,7 +320,7 @@ namespace Flowframes ConfigParser.LoadComboxIndex(outModeCombox); } - private string GetAiComboboxName (AI ai) + private string GetAiComboboxName(AI ai) { return ai.FriendlyName + " - " + ai.Description; } @@ -334,7 +367,7 @@ namespace Flowframes AiProcessSuspend.Reset(); - if(Interpolate.currentSettings.outMode == Interpolate.OutMode.Realtime) + if (Interpolate.currentSettings.outSettings.Format == Enums.Output.Format.Realtime) { await Interpolate.Realtime(); SetProgress(0); @@ -345,11 +378,6 @@ namespace Flowframes } } - private async void RealtimeInterp(object sender, EventArgs e) - { - - } - public ModelCollection.ModelInfo GetModel(AI currentAi) { try @@ -362,36 +390,14 @@ namespace Flowframes } } - Interpolate.OutMode GetOutMode() + Enums.Output.Format GetOutputFormat { get { return ParseUtils.GetEnum(comboxOutputFormat.Text, true, Strings.OutputFormat); } } + Enums.Encoding.Encoder GetEncoder { get { return ParseUtils.GetEnum(comboxOutputEncoder.Text, true, Strings.Encoder); } } + Enums.Encoding.PixelFormat GetPixelFormat { get { return ParseUtils.GetEnum(comboxOutputColors.Text, true, Strings.PixelFormat); } } + ExportSettings GetExportSettings { get { return new ExportSettings() { Encoder = GetEncoder, Format = GetOutputFormat, PixelFormat = GetPixelFormat }; } } + + public void SetFormat(Enums.Output.Format format) { - Interpolate.OutMode outMode = Interpolate.OutMode.VidMp4; - if (outModeCombox.Text.ToLowerInvariant().Contains("mkv")) outMode = Interpolate.OutMode.VidMkv; - if (outModeCombox.Text.ToLowerInvariant().Contains("webm")) outMode = Interpolate.OutMode.VidWebm; - if (outModeCombox.Text.ToLowerInvariant().Contains("prores")) outMode = Interpolate.OutMode.VidProRes; - if (outModeCombox.Text.ToLowerInvariant().Contains("avi")) outMode = Interpolate.OutMode.VidAvi; - if (outModeCombox.Text.ToLowerInvariant().Contains("gif")) outMode = Interpolate.OutMode.VidGif; - if (outModeCombox.Text.ToLowerInvariant().Contains("image")) outMode = Interpolate.OutMode.ImgPng; - if (outModeCombox.Text.ToLowerInvariant().Contains("real")) outMode = Interpolate.OutMode.Realtime; - return outMode; - } - - public void SetOutMode(Interpolate.OutMode mode) - { - int targetIndex = 0; - - for (int i = 0; i < outModeCombox.Items.Count; i++) - { - string currentItem = outModeCombox.Items[i].ToString().ToLowerInvariant(); - if (mode == Interpolate.OutMode.VidMkv && currentItem.Contains("mkv")) targetIndex = i; - if (mode == Interpolate.OutMode.VidWebm && currentItem.Contains("webm")) targetIndex = i; - if (mode == Interpolate.OutMode.VidProRes && currentItem.Contains("prores")) targetIndex = i; - if (mode == Interpolate.OutMode.VidAvi && currentItem.Contains("avi")) targetIndex = i; - if (mode == Interpolate.OutMode.VidGif && currentItem.Contains("gif")) targetIndex = i; - if (mode == Interpolate.OutMode.ImgPng && currentItem.Contains("image")) targetIndex = i; - if (mode == Interpolate.OutMode.Realtime && currentItem.Contains("real")) targetIndex = i; - } - - outModeCombox.SelectedIndex = targetIndex; + outModeCombox.Text = Strings.OutputFormat.Get(format.ToString()); } public AI GetAi() @@ -768,8 +774,18 @@ namespace Flowframes { var ai = GetAi(); - if(ai != null) + if (ai != null) UiUtils.ShowMessageBox(ai.GetVerboseInfo(), UiUtils.MessageType.Message); } + + private void comboxOutputFormat_SelectedIndexChanged(object sender, EventArgs e) + { + UpdateOutputUi(); + } + + private void comboxOutputEncoder_SelectedIndexChanged(object sender, EventArgs e) + { + UpdateOutputEncodingUi(); + } } } \ No newline at end of file diff --git a/Code/Forms/BatchForm.cs b/Code/Forms/BatchForm.cs index 759feed..6bcf6cd 100644 --- a/Code/Forms/BatchForm.cs +++ b/Code/Forms/BatchForm.cs @@ -32,9 +32,8 @@ namespace Flowframes.Forms for (int i = 0; i < Program.batchQueue.Count; i++) { InterpSettings entry = Program.batchQueue.ElementAt(i); - string niceOutMode = entry.outMode.ToString().ToUpper().Remove("VID").Remove("IMG"); - string str = $"#{i+1}: {Path.GetFileName(entry.inPath).Trunc(40)} - {entry.inFps.GetFloat()} FPS => " + - $"{entry.interpFactor}x {entry.ai.NameShort} ({entry.model.Name}) => {niceOutMode}"; + string outFormat = Strings.OutputFormat.Get(entry.outSettings.Format.ToString()); + string str = $"#{i+1}: {Path.GetFileName(entry.inPath).Trunc(40)} - {entry.inFps.GetFloat()} FPS => {entry.interpFactor}x {entry.ai.NameShort} ({entry.model.Name}) => {outFormat}"; taskList.Items.Add(str); } } diff --git a/Code/Forms/SettingsForm.cs b/Code/Forms/SettingsForm.cs index bb409ec..5430539 100644 --- a/Code/Forms/SettingsForm.cs +++ b/Code/Forms/SettingsForm.cs @@ -253,18 +253,19 @@ namespace Flowframes.Forms private void mp4Enc_SelectedIndexChanged(object sender, EventArgs e) { - string text = mp4Enc.Text.ToUpper().Remove(" "); - - if (text.Contains(FfmpegUtils.Codec.H264.ToString().ToUpper())) - mp4CrfConfigKey = "h264Crf"; - - if (text.Contains(FfmpegUtils.Codec.H265.ToString().ToUpper())) - mp4CrfConfigKey = "h265Crf"; - - if (text.Contains(FfmpegUtils.Codec.Av1.ToString().ToUpper())) - mp4CrfConfigKey = "av1Crf"; - - mp4Crf.Value = Config.GetInt(mp4CrfConfigKey); + // string text = mp4Enc.Text.ToUpper().Remove(" "); + // + // if (text.Contains(FfmpegUtils.Codec.H264.ToString().ToUpper())) + // mp4CrfConfigKey = "h264Crf"; + // + // if (text.Contains(FfmpegUtils.Codec.H265.ToString().ToUpper())) + // mp4CrfConfigKey = "h265Crf"; + // + // if (text.Contains(FfmpegUtils.Codec.Av1.ToString().ToUpper())) + // mp4CrfConfigKey = "av1Crf"; + // + // mp4Crf.Value = Config.GetInt(mp4CrfConfigKey); + throw new NotImplementedException(); } private void modelDownloaderBtn_Click(object sender, EventArgs e) diff --git a/Code/IO/IoUtils.cs b/Code/IO/IoUtils.cs index c5873f8..20cb56c 100644 --- a/Code/IO/IoUtils.cs +++ b/Code/IO/IoUtils.cs @@ -565,9 +565,6 @@ namespace Flowframes.IO Fraction maxFps = max.Contains("/") ? new Fraction(max) : new Fraction(max.GetFloat()); float fps = fpsLimit ? maxFps.GetFloat() : curr.outFps.GetFloat(); - if (curr.outMode == Interpolate.OutMode.VidGif && fps > 50f) - fps = 50f; - Size outRes = await InterpolateUtils.GetOutputResolution(curr.inPath, true); string pattern = Config.Get(Config.Key.exportNamePattern); string inName = Interpolate.currentSettings.inputIsFrames ? Path.GetFileName(curr.inPath) : Path.GetFileNameWithoutExtension(curr.inPath); @@ -580,7 +577,7 @@ namespace Flowframes.IO filename = filename.Replace("[FACTOR]", curr.interpFactor.ToStringDot()); filename = filename.Replace("[AI]", curr.ai.NameShort.ToUpper()); filename = filename.Replace("[MODEL]", curr.model.Name.Remove(" ")); - filename = filename.Replace("[FPS]", fps.ToStringDot()); + filename = filename.Replace("[FPS]", fps.ToStringDot("0.###")); filename = filename.Replace("[ROUNDFPS]", fps.RoundToInt().ToString()); filename = filename.Replace("[RES]", $"{outRes.Width}x{outRes.Height}"); filename = filename.Replace("[H]", $"{outRes.Height}p"); @@ -589,7 +586,7 @@ namespace Flowframes.IO filename += Paths.fpsLimitSuffix; if (withExt) - filename += FfmpegUtils.GetExt(curr.outMode); + filename += FfmpegUtils.GetExt(curr.outSettings); return filename; } diff --git a/Code/Main/AutoEncode.cs b/Code/Main/AutoEncode.cs index 79f3010..72874a6 100644 --- a/Code/Main/AutoEncode.cs +++ b/Code/Main/AutoEncode.cs @@ -54,7 +54,7 @@ namespace Flowframes.Main { UpdateChunkAndBufferSizes(); - bool imgSeq = Interpolate.currentSettings.outMode.ToString().ToLowerInvariant().StartsWith("img"); + bool imgSeq = Interpolate.currentSettings.outSettings.Encoder.GetInfo().IsImageSequence; interpFramesFolder = interpFramesPath; videoChunksFolder = Path.Combine(interpFramesPath.GetParentDir(), Paths.chunksDir); @@ -130,12 +130,12 @@ namespace Flowframes.Main } busy = true; - string outpath = Path.Combine(videoChunksFolder, "chunks", $"{chunkNo.ToString().PadLeft(4, '0')}{FfmpegUtils.GetExt(Interpolate.currentSettings.outMode)}"); + string outpath = Path.Combine(videoChunksFolder, "chunks", $"{chunkNo.ToString().PadLeft(4, '0')}{FfmpegUtils.GetExt(Interpolate.currentSettings.outSettings)}"); string firstFile = Path.GetFileName(interpFramesLines[frameLinesToEncode.First()].Trim()); string lastFile = Path.GetFileName(interpFramesLines[frameLinesToEncode.Last()].Trim()); Logger.Log($"[AE] Encoding Chunk #{chunkNo} to using line {frameLinesToEncode.First()} ({firstFile}) through {frameLinesToEncode.Last()} ({lastFile}) - {unencodedFrameLines.Count} unencoded frames left in total", true, false, "ffmpeg"); - await Export.EncodeChunk(outpath, Interpolate.currentSettings.interpFolder, chunkNo, Interpolate.currentSettings.outMode, frameLinesToEncode.First(), frameLinesToEncode.Count); + await Export.EncodeChunk(outpath, Interpolate.currentSettings.interpFolder, chunkNo, Interpolate.currentSettings.outSettings, frameLinesToEncode.First(), frameLinesToEncode.Count); if (Interpolate.canceled) return; diff --git a/Code/Main/Export.cs b/Code/Main/Export.cs index 0c3e889..7c0023e 100644 --- a/Code/Main/Export.cs +++ b/Code/Main/Export.cs @@ -22,7 +22,7 @@ namespace Flowframes.Main { - public static async Task ExportFrames(string path, string outFolder, I.OutMode mode, bool stepByStep) + public static async Task ExportFrames(string path, string outFolder, ExportSettings exportSettings, bool stepByStep) { if(Config.GetInt(Config.Key.sceneChangeFillMode) == 1) { @@ -30,7 +30,7 @@ namespace Flowframes.Main await Blend.BlendSceneChanges(frameFile); } - if (!mode.ToString().ToLowerInvariant().Contains("vid")) // Copy interp frames out of temp folder and skip video export for image seq export + if (exportSettings.Encoder.GetInfo().IsImageSequence) // Copy interp frames out of temp folder and skip video export for image seq export { try { @@ -61,10 +61,10 @@ namespace Flowframes.Main bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt(Config.Key.maxFpsMode) == 0; if (!dontEncodeFullFpsVid) - await Encode(mode, path, Path.Combine(outFolder, await IoUtils.GetCurrentExportFilename(false, true)), I.currentSettings.outFps, new Fraction()); + await Encode(exportSettings, path, Path.Combine(outFolder, await IoUtils.GetCurrentExportFilename(false, true)), I.currentSettings.outFps, new Fraction()); if (fpsLimit) - await Encode(mode, path, Path.Combine(outFolder, await IoUtils.GetCurrentExportFilename(true, true)), I.currentSettings.outFps, maxFps); + await Encode(exportSettings, path, Path.Combine(outFolder, await IoUtils.GetCurrentExportFilename(true, true)), I.currentSettings.outFps, maxFps); } catch (Exception e) { @@ -76,7 +76,7 @@ namespace Flowframes.Main public static async Task GetPipedFfmpegCmd(bool ffplay = false) { InterpSettings s = I.currentSettings; - string encArgs = FfmpegUtils.GetEncArgs(FfmpegUtils.GetCodec(s.outMode), (s.ScaledResolution.IsEmpty ? s.InputResolution : s.ScaledResolution), s.outFps.GetFloat(), true).FirstOrDefault(); + string encArgs = FfmpegUtils.GetEncArgs(s.outSettings.Encoder, s.outSettings.PixelFormat, (s.ScaledResolution.IsEmpty ? s.InputResolution : s.ScaledResolution), s.outFps.GetFloat(), true).FirstOrDefault(); string max = Config.Get(Config.Key.maxFps); Fraction maxFps = max.Contains("/") ? new Fraction(max) : new Fraction(max.GetFloat()); @@ -84,7 +84,7 @@ namespace Flowframes.Main VidExtraData extraData = await FfmpegCommands.GetVidExtraInfo(s.inPath); string extraArgsIn = FfmpegEncode.GetFfmpegExportArgsIn(s.outFps, s.outItsScale); - string extraArgsOut = FfmpegEncode.GetFfmpegExportArgsOut(fpsLimit ? maxFps : new Fraction(), extraData, s.outMode); + string extraArgsOut = FfmpegEncode.GetFfmpegExportArgsOut(fpsLimit ? maxFps : new Fraction(), extraData, s.outSettings); if (ffplay) { @@ -196,7 +196,7 @@ namespace Flowframes.Main } } - static async Task Encode(I.OutMode mode, string framesPath, string outPath, Fraction fps, Fraction resampleFps) + static async Task Encode(ExportSettings settings, string framesPath, string outPath, Fraction fps, Fraction resampleFps) { string framesFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(I.currentSettings.interpFactor)); @@ -207,14 +207,14 @@ namespace Flowframes.Main return; } - if (mode == I.OutMode.VidGif) + if (settings.Format == Enums.Output.Format.Gif) { await FfmpegEncode.FramesToGifConcat(framesFile, outPath, fps, true, Config.GetInt(Config.Key.gifColors), resampleFps, I.currentSettings.outItsScale); } else { VidExtraData extraData = await FfmpegCommands.GetVidExtraInfo(I.currentSettings.inPath); - await FfmpegEncode.FramesToVideo(framesFile, outPath, mode, fps, resampleFps, I.currentSettings.outItsScale, extraData); + await FfmpegEncode.FramesToVideo(framesFile, outPath, settings, fps, resampleFps, I.currentSettings.outItsScale, extraData); await MuxOutputVideo(I.currentSettings.inPath, outPath); await Loop(outPath, await GetLoopTimes()); } @@ -228,7 +228,7 @@ namespace Flowframes.Main public static async Task ChunksToVideo(string tempFolder, string chunksFolder, string baseOutPath, bool isBackup = false) { - if (IoUtils.GetAmountOfFiles(chunksFolder, true, "*" + FfmpegUtils.GetExt(I.currentSettings.outMode)) < 1) + if (IoUtils.GetAmountOfFiles(chunksFolder, true, "*" + FfmpegUtils.GetExt(I.currentSettings.outSettings)) < 1) { I.Cancel("No video chunks found - An error must have occured during chunk encoding!", AiProcess.hasShownError); return; @@ -289,7 +289,7 @@ namespace Flowframes.Main await Loop(outPath, await GetLoopTimes()); } - public static async Task EncodeChunk(string outPath, string interpDir, int chunkNo, I.OutMode mode, int firstFrameNum, int framesAmount) + public static async Task EncodeChunk(string outPath, string interpDir, int chunkNo, ExportSettings settings, int firstFrameNum, int framesAmount) { string framesFileFull = Path.Combine(I.currentSettings.tempFolder, Paths.GetFrameOrderFilename(I.currentSettings.interpFactor)); string concatFile = Path.Combine(I.currentSettings.tempFolder, Paths.GetFrameOrderFilenameChunk(firstFrameNum, firstFrameNum + framesAmount)); @@ -307,7 +307,7 @@ namespace Flowframes.Main bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt(Config.Key.maxFpsMode) == 0; - if (mode.ToString().ToLowerInvariant().StartsWith("img")) // Image Sequence output mode, not video + if (settings.Encoder.GetInfo().IsImageSequence) // Image Sequence output mode, not video { string desiredFormat = Config.Get(Config.Key.imgSeqFormat); string availableFormat = Path.GetExtension(IoUtils.GetFilesSorted(interpDir)[0]).Remove(".").ToUpper(); @@ -336,14 +336,14 @@ namespace Flowframes.Main else { if (!dontEncodeFullFpsVid) - await FfmpegEncode.FramesToVideo(concatFile, outPath, mode, I.currentSettings.outFps, new Fraction(), I.currentSettings.outItsScale, extraData, AvProcess.LogMode.Hidden, true); // Encode + await FfmpegEncode.FramesToVideo(concatFile, outPath, settings, I.currentSettings.outFps, new Fraction(), I.currentSettings.outItsScale, extraData, AvProcess.LogMode.Hidden, true); // Encode if (fpsLimit) { string filename = Path.GetFileName(outPath); string newParentDir = outPath.GetParentDir() + Paths.fpsLimitSuffix; outPath = Path.Combine(newParentDir, filename); - await FfmpegEncode.FramesToVideo(concatFile, outPath, mode, I.currentSettings.outFps, maxFps, I.currentSettings.outItsScale, extraData, AvProcess.LogMode.Hidden, true); // Encode with limited fps + await FfmpegEncode.FramesToVideo(concatFile, outPath, settings, I.currentSettings.outFps, maxFps, I.currentSettings.outItsScale, extraData, AvProcess.LogMode.Hidden, true); // Encode with limited fps } } diff --git a/Code/Main/Interpolate.cs b/Code/Main/Interpolate.cs index 0df8c5a..8f28895 100644 --- a/Code/Main/Interpolate.cs +++ b/Code/Main/Interpolate.cs @@ -22,8 +22,6 @@ namespace Flowframes { public class Interpolate { - public enum OutMode { VidMp4, VidMkv, VidWebm, VidProRes, VidAvi, VidGif, ImgPng, Realtime } - public static bool currentlyUsingAutoEnc; public static InterpSettings currentSettings; public static MediaFile currentMediaFile; @@ -40,7 +38,7 @@ namespace Flowframes if (!Utils.CheckPathValid(currentSettings.inPath)) return; // Check if input path/file is valid if (!Utils.CheckAiAvailable(currentSettings.ai, currentSettings.model)) return; // Check if selected AI pkg is installed if (!AutoEncodeResume.resumeNextRun && !Utils.CheckDeleteOldTempFolder()) return; // Try to delete temp folder if an old one exists - if (!(await Utils.CheckEncoderValid(currentSettings.outFps.GetFloat()))) return; // Check encoder compat + if (!(await Utils.CheckEncoderValid())) return; // Check encoder compat Utils.ShowWarnings(currentSettings.interpFactor, currentSettings.ai); currentSettings.stepByStep = false; Program.mainForm.SetStatus("Starting..."); @@ -65,7 +63,7 @@ namespace Flowframes if (currentSettings.ai.Piped) await Export.MuxPipedVideo(currentSettings.inPath, currentSettings.FullOutPath); else - await Export.ExportFrames(currentSettings.interpFolder, currentSettings.outPath, currentSettings.outMode, false); + await Export.ExportFrames(currentSettings.interpFolder, currentSettings.outPath, currentSettings.outSettings, false); } if (!AutoEncodeResume.resumeNextRun && Config.GetBool(Config.Key.keepTempFolder) && IoUtils.GetAmountOfFiles(currentSettings.framesFolder, false) > 0) diff --git a/Code/Main/InterpolateSteps.cs b/Code/Main/InterpolateSteps.cs index 207f1c7..72e2eb7 100644 --- a/Code/Main/InterpolateSteps.cs +++ b/Code/Main/InterpolateSteps.cs @@ -93,7 +93,7 @@ namespace Flowframes.Main return; } - if (Config.GetBool(Config.Key.sbsAllowAutoEnc) && !(await InterpolateUtils.CheckEncoderValid(currentSettings.outFps.GetFloat()))) return; + if (Config.GetBool(Config.Key.sbsAllowAutoEnc) && !(await InterpolateUtils.CheckEncoderValid())) return; if (canceled) return; Program.mainForm.SetStatus("Running AI..."); @@ -119,7 +119,7 @@ namespace Flowframes.Main } } - if (!(await InterpolateUtils.CheckEncoderValid(currentSettings.outFps.GetFloat()))) return; + if (!(await InterpolateUtils.CheckEncoderValid())) return; string[] outFrames = IoUtils.GetFilesSorted(currentSettings.interpFolder, currentSettings.interpExt); @@ -130,7 +130,7 @@ namespace Flowframes.Main return; } - await Export.ExportFrames(currentSettings.interpFolder, currentSettings.outPath, currentSettings.outMode, true); + await Export.ExportFrames(currentSettings.interpFolder, currentSettings.outPath, currentSettings.outSettings, true); } public static async Task Reset() diff --git a/Code/Main/InterpolateUtils.cs b/Code/Main/InterpolateUtils.cs index adad637..06e66b5 100644 --- a/Code/Main/InterpolateUtils.cs +++ b/Code/Main/InterpolateUtils.cs @@ -121,18 +121,16 @@ namespace Flowframes.Main passes = false; } - if (passes && s.outFps.GetFloat() < 1f || s.outFps.GetFloat() > 1000f) - { - string imgSeqNote = isFile ? "" : "\n\nWhen using an image sequence as input, you always have to specify the frame rate manually."; - UiUtils.ShowMessageBox($"Invalid output frame rate ({s.outFps.GetFloat()}).\nMust be 1-1000.{imgSeqNote}"); - passes = false; - } - string fpsLimitValue = Config.Get(Config.Key.maxFps); float fpsLimit = (fpsLimitValue.Contains("/") ? new Fraction(Config.Get(Config.Key.maxFps)).GetFloat() : fpsLimitValue.GetFloat()); + int maxFps = s.outSettings.Encoder.GetInfo().MaxFramerate; - if (s.outMode == I.OutMode.VidGif && s.outFps.GetFloat() > 50 && !(fpsLimit > 0 && fpsLimit <= 50)) - Logger.Log($"Warning: GIF will be encoded at 50 FPS instead of {s.outFps.GetFloat()} as the format doesn't support frame rates that high."); + if (passes && s.outFps.GetFloat() < 1f || (s.outFps.GetFloat() > maxFps && !(fpsLimit > 0 && fpsLimit <= maxFps))) + { + string imgSeqNote = isFile ? "" : "\n\nWhen using an image sequence as input, you always have to specify the frame rate manually."; + UiUtils.ShowMessageBox($"Invalid output frame rate ({s.outFps.GetFloat()}).\nMust be 1-{maxFps}. Either lower the interpolation factor or use the \"Maximum Output Frame Rate\" option.{imgSeqNote}"); + passes = false; + } if (!passes) I.Cancel("Invalid settings detected.", true); @@ -222,20 +220,9 @@ namespace Flowframes.Main return true; } - public static async Task CheckEncoderValid(float interpFps) + public static async Task CheckEncoderValid() { - string enc = FfmpegUtils.GetEnc(FfmpegUtils.GetCodec(I.currentSettings.outMode)); - - float maxAv1Fps = 240; // SVT-AV1 only supports up to 240 FPS as of 2022-08 - float maxFps = Config.GetFloat(Config.Key.maxFps); - float encodeFps = maxFps > 0 ? interpFps.Clamp(0, maxFps) : interpFps; - - if (enc.ToLowerInvariant().Contains("av1") && encodeFps > maxAv1Fps) - { - UiUtils.ShowMessageBox($"The selected encoder only supports up to {maxAv1Fps} FPS!\nPlease use a different encoder or reduce the interpolation factor.", UiUtils.MessageType.Error); - I.Cancel(); - return false; - } + string enc = I.currentSettings.outSettings.Encoder.ToString(); if (enc.ToLowerInvariant().Contains("nvenc") && !(await FfmpegCommands.IsEncoderCompatible(enc))) { @@ -302,7 +289,7 @@ namespace Flowframes.Main return false; } - if (current.outMode == I.OutMode.VidGif) + if (current.outSettings.Format == Enums.Output.Format.Gif) { Logger.Log($"Not Using AutoEnc: Using GIF output", true); return false; diff --git a/Code/Media/FfmpegAudioAndMetadata.cs b/Code/Media/FfmpegAudioAndMetadata.cs index c313657..52b764a 100644 --- a/Code/Media/FfmpegAudioAndMetadata.cs +++ b/Code/Media/FfmpegAudioAndMetadata.cs @@ -29,9 +29,9 @@ namespace Flowframes.Media string subArgs = "-c:s " + Utils.GetSubCodecForContainer(containerExt); - bool audioCompat = Utils.ContainerSupportsAllAudioFormats(I.currentSettings.outMode, GetAudioCodecs(inputVideo)); + bool audioCompat = Utils.ContainerSupportsAllAudioFormats(I.currentSettings.outSettings.Format, GetAudioCodecs(inputVideo)); bool slowmo = I.currentSettings.outItsScale != 0 && I.currentSettings.outItsScale != 1; - string audioArgs = audioCompat && !slowmo ? "" : await Utils.GetAudioFallbackArgs(inputVideo, I.currentSettings.outMode, I.currentSettings.outItsScale); + string audioArgs = audioCompat && !slowmo ? "" : await Utils.GetAudioFallbackArgs(inputVideo, I.currentSettings.outSettings.Format, I.currentSettings.outItsScale); if (!audioCompat && !slowmo) Logger.Log("Warning: Input audio format(s) not fully supported in output container - Will re-encode.", true, false, "ffmpeg"); @@ -46,7 +46,7 @@ namespace Flowframes.Media if (!subs || (subs && !Utils.ContainerSupportsSubs(containerExt))) subArgs = "-sn"; - bool isMkv = I.currentSettings.outMode == I.OutMode.VidMkv; + bool isMkv = I.currentSettings.outSettings.Format == Data.Enums.Output.Format.Mkv; string mkvFix = isMkv ? "-max_interleave_delta 0" : ""; // https://reddit.com/r/ffmpeg/comments/efddfs/starting_new_cluster_due_to_timestamp/ string metaArg = (isMkv && meta) ? "-map 1:t?" : ""; // https://reddit.com/r/ffmpeg/comments/fw4jnh/how_to_make_ffmpeg_keep_attached_images_in_mkv_as/ string shortestArg = shortest ? "-shortest" : ""; diff --git a/Code/Media/FfmpegCommands.cs b/Code/Media/FfmpegCommands.cs index 256a491..418c29f 100644 --- a/Code/Media/FfmpegCommands.cs +++ b/Code/Media/FfmpegCommands.cs @@ -24,12 +24,19 @@ namespace Flowframes public static int GetModulo () { - return (Interpolate.currentSettings.ai.NameInternal == Implementations.flavrCuda.NameInternal) ? 8 : 2; // FLAVR input needs to be divisible by 8 + if (Interpolate.currentSettings.ai.NameInternal == Implementations.flavrCuda.NameInternal) + return 8; + + return Interpolate.currentSettings.outSettings.Encoder.GetInfo().Modulo; } public static string GetPadFilter () { int mod = GetModulo(); + + if (mod < 2) + return ""; + return $"pad=width=ceil(iw/{mod})*{mod}:height=ceil(ih/{mod})*{mod}:color=black@0"; } diff --git a/Code/Media/FfmpegEncode.cs b/Code/Media/FfmpegEncode.cs index 7189392..f98a952 100644 --- a/Code/Media/FfmpegEncode.cs +++ b/Code/Media/FfmpegEncode.cs @@ -13,14 +13,14 @@ namespace Flowframes.Media { partial class FfmpegEncode : FfmpegCommands { - public static async Task FramesToVideo(string framesFile, string outPath, Interpolate.OutMode outMode, Fraction fps, Fraction resampleFps, float itsScale, VidExtraData extraData, LogMode logMode = LogMode.OnlyLastLine, bool isChunk = false) + public static async Task FramesToVideo(string framesFile, string outPath, ExportSettings settings, Fraction fps, Fraction resampleFps, float itsScale, VidExtraData extraData, LogMode logMode = LogMode.OnlyLastLine, bool isChunk = false) { if (logMode != LogMode.Hidden) Logger.Log((resampleFps.GetFloat() <= 0) ? "Encoding video..." : $"Encoding video resampled to {resampleFps.GetString()} FPS..."); IoUtils.RenameExistingFile(outPath); Directory.CreateDirectory(outPath.GetParentDir()); - string[] encArgs = Utils.GetEncArgs(Utils.GetCodec(outMode), (Interpolate.currentSettings.ScaledResolution.IsEmpty ? Interpolate.currentSettings.InputResolution : Interpolate.currentSettings.ScaledResolution), Interpolate.currentSettings.outFps.GetFloat()); + string[] encArgs = Utils.GetEncArgs(settings.Encoder, settings.PixelFormat, (Interpolate.currentSettings.ScaledResolution.IsEmpty ? Interpolate.currentSettings.InputResolution : Interpolate.currentSettings.ScaledResolution), Interpolate.currentSettings.outFps.GetFloat()); string inArg = $"-f concat -i {Path.GetFileName(framesFile)}"; string linksDir = Path.Combine(framesFile + Paths.symlinksSuffix); @@ -33,11 +33,11 @@ namespace Flowframes.Media string args = ""; - for(int i = 0; i < encArgs.Length; i++) + for (int i = 0; i < encArgs.Length; i++) { string pre = i == 0 ? "" : $" && ffmpeg {AvProcess.GetFfmpegDefaultArgs()}"; string post = (i == 0 && encArgs.Length > 1) ? $"-f null -" : outPath.Wrap(); - args += $"{pre} {GetFfmpegExportArgsIn(fps, itsScale)} {inArg} {encArgs[i]} {GetFfmpegExportArgsOut(resampleFps, extraData, Interpolate.currentSettings.outMode, isChunk)} {post} "; + args += $"{pre} {GetFfmpegExportArgsIn(fps, itsScale)} {inArg} {encArgs[i]} {GetFfmpegExportArgsOut(resampleFps, extraData, settings, isChunk)} {post} "; } await RunFfmpeg(args, framesFile.GetParentDir(), logMode, !isChunk); @@ -50,7 +50,7 @@ namespace Flowframes.Media return $"-r {fps}"; } - public static string GetFfmpegExportArgsOut (Fraction resampleFps, VidExtraData extraData, Interpolate.OutMode outMode, bool isChunk = false) + public static string GetFfmpegExportArgsOut(Fraction resampleFps, VidExtraData extraData, ExportSettings settings, bool isChunk = false) { List filters = new List(); string extraArgs = Config.Get(Config.Key.ffEncArgs); @@ -68,14 +68,14 @@ namespace Flowframes.Media if (!string.IsNullOrWhiteSpace(extraData.displayRatio) && !extraData.displayRatio.MatchesWildcard("*N/A*")) extraArgs += $" -aspect {extraData.displayRatio}"; - if(!isChunk && outMode == Interpolate.OutMode.VidMp4) + if (!isChunk && settings.Format == Enums.Output.Format.Mp4) extraArgs += $" -movflags +faststart"; filters.Add(GetPadFilter()); return filters.Count > 0 ? $"-vf {string.Join(",", filters)}" : "" + $" {extraArgs}"; } - public static string GetConcatFileExt (string concatFilePath) + public static string GetConcatFileExt(string concatFilePath) { return Path.GetExtension(File.ReadAllLines(concatFilePath).FirstOrDefault().Split('\'')[1]); } @@ -110,7 +110,7 @@ namespace Flowframes.Media if (logMode != LogMode.Hidden) Logger.Log((resampleFps.GetFloat() <= 0) ? $"Encoding GIF..." : $"Encoding GIF resampled to {resampleFps.GetFloat().ToString().Replace(",", ".")} FPS..."); - + string framesFilename = Path.GetFileName(framesFile); string dither = Config.Get(Config.Key.gifDitherType).Split(' ').First(); string paletteFilter = palette ? $"-vf \"split[s0][s1];[s0]palettegen={colors}[p];[s1][p]paletteuse=dither={dither}\"" : ""; diff --git a/Code/Media/FfmpegUtils.cs b/Code/Media/FfmpegUtils.cs index f832e50..9f5cfae 100644 --- a/Code/Media/FfmpegUtils.cs +++ b/Code/Media/FfmpegUtils.cs @@ -8,6 +8,7 @@ using System.Drawing; using System.IO; using System.Linq; using System.Threading.Tasks; +using static Flowframes.Data.Enums.Encoding; using static Flowframes.Media.GetVideoInfo; using Stream = Flowframes.Data.Streams.Stream; @@ -170,127 +171,88 @@ namespace Flowframes.Media return false; } - public enum Codec { H264, H265, H264Nvenc, H265Nvenc, Av1, Vp9, ProRes, AviRaw, Gif } - - public static Codec GetCodec(Interpolate.OutMode mode) - { - if (mode == Interpolate.OutMode.VidMp4 || mode == Interpolate.OutMode.VidMkv) - { - int mp4MkvEnc = Config.GetInt(Config.Key.mp4Enc); - if (mp4MkvEnc == 0) return Codec.H264; - if (mp4MkvEnc == 1) return Codec.H265; - if (mp4MkvEnc == 2) return Codec.H264Nvenc; - if (mp4MkvEnc == 3) return Codec.H265Nvenc; - if (mp4MkvEnc == 4) return Codec.Av1; - } - - if (mode == Interpolate.OutMode.VidWebm) - return Codec.Vp9; - - if (mode == Interpolate.OutMode.VidProRes) - return Codec.ProRes; - - if (mode == Interpolate.OutMode.VidAvi) - return Codec.AviRaw; - - if (mode == Interpolate.OutMode.VidGif) - return Codec.Gif; - - return Codec.H264; - } - - public static string GetEnc(Codec codec) - { - switch (codec) - { - case Codec.H264: return "libx264"; - case Codec.H265: return "libx265"; - case Codec.H264Nvenc: return "h264_nvenc"; - case Codec.H265Nvenc: return "hevc_nvenc"; - case Codec.Av1: return "libsvtav1"; - case Codec.Vp9: return "libvpx-vp9"; - case Codec.ProRes: return "prores_ks"; - case Codec.AviRaw: return Config.Get(Config.Key.aviCodec); - case Codec.Gif: return "gif"; - } - - return "libx264"; - } - - public static string[] GetEncArgs(Codec codec, Size res, float fps, bool realtime = false) // Array contains as many entries as there are encoding passes. If "realtime" is true, force single pass. + public static string[] GetEncArgs(Encoder enc, PixelFormat pixFmt, Size res, float fps, bool realtime = false) // Array contains as many entries as there are encoding passes. If "realtime" is true, force single pass. { int keyint = 10; - if (codec == Codec.H264) + var args = new List(); + + EncoderInfoVideo info = OutputUtils.GetEncoderInfoVideo(enc); + args.Add($"-c:v {info.Name}"); + + if (enc == Encoder.X264 || enc == Encoder.X265 || enc == Encoder.SvtAv1 || enc == Encoder.VpxVp9 || enc == Encoder.Nvenc264 || enc == Encoder.Nvenc265 || enc == Encoder.NvencAv1) + args.Add(GetKeyIntArg(fps, keyint)); + + if (pixFmt != (PixelFormat)(-1)) + args.Add($"-pix_fmt {pixFmt.ToString().Lower()}"); + + if (enc == Encoder.X264) { - string preset = Config.Get(Config.Key.ffEncPreset).ToLowerInvariant().Remove(" "); - string g = GetKeyIntArg(fps, keyint); - return new string[] { $"-c:v {GetEnc(codec)} -crf {Config.GetInt(Config.Key.h264Crf)} -preset {preset} {g} -pix_fmt {GetPixFmt()}" }; + string preset = Config.Get(Config.Key.ffEncPreset).ToLowerInvariant().Remove(" "); // TODO: Replace this ugly stuff with enums + args.Add($"-crf {Config.GetInt(Config.Key.h264Crf)} -preset {preset}"); } - if (codec == Codec.H265) + if (enc == Encoder.X265) { - string preset = Config.Get(Config.Key.ffEncPreset).ToLowerInvariant().Remove(" "); + string preset = Config.Get(Config.Key.ffEncPreset).ToLowerInvariant().Remove(" "); // TODO: Replace this ugly stuff with enums int crf = Config.GetInt(Config.Key.h265Crf); - string g = GetKeyIntArg(fps, keyint); - return new string[] { $"-c:v {GetEnc(codec)} {(crf > 0 ? $"-crf {crf}" : "-x265-params lossless=1")} -preset {preset} {g} -pix_fmt {GetPixFmt()}" }; + args.Add($"{(crf > 0 ? $"-crf {crf}" : "-x265-params lossless=1")} -preset {preset}"); } - if (codec == Codec.H264Nvenc) - { - int cq = (Config.GetInt(Config.Key.h264Crf) * 1.1f).RoundToInt(); - return new string[] { $"-c:v {GetEnc(codec)} -b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")} -pix_fmt {GetPixFmt()}" }; - } - - if (codec == Codec.H265Nvenc) - { - int cq = (Config.GetInt(Config.Key.h265Crf) * 1.1f).RoundToInt(); - return new string[] { $"-c:v {GetEnc(codec)} -b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")} -pix_fmt {GetPixFmt()}" }; - } - - if (codec == Codec.Av1) + if (enc == Encoder.SvtAv1) { int cq = Config.GetInt(Config.Key.av1Crf); - string g = GetKeyIntArg(fps, keyint); - return new string[] { $"-c:v {GetEnc(codec)} -b:v 0 -qp {cq} {GetSvtAv1Speed()} {g} -svtav1-params enable-qm=1:enable-overlays=1:enable-tf=0:scd=0 -pix_fmt {GetPixFmt()}" }; + args.Add($"-b:v 0 -qp {cq} {GetSvtAv1Speed()} -svtav1-params enable-qm=1:enable-overlays=1:enable-tf=0:scd=0"); } - if (codec == Codec.Vp9) + if (enc == Encoder.VpxVp9) { int crf = Config.GetInt(Config.Key.vp9Crf); string qualityStr = (crf > 0) ? $"-crf {crf}" : "-lossless 1"; - string g = GetKeyIntArg(fps, keyint); string t = GetTilingArgs(res, "-tile-columns ", "-tile-rows "); if (realtime) // Force 1-pass { - return new string[] { $"-c:v {GetEnc(codec)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 {g} -pix_fmt {GetPixFmt()}" }; + args.Add($"-b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1"); } else { - return new string[] { - $"-c:v {GetEnc(codec)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 {g} -pass 1 -pix_fmt {GetPixFmt()} -an", - $"-c:v {GetEnc(codec)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 {g} -pass 2 -pix_fmt {GetPixFmt()}" + return new string[] { + $"{string.Join(" ", args)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 -pass 1 -an", + $"{string.Join(" ", args)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 -pass 2" }; } } - if (codec == Codec.ProRes) + if (enc == Encoder.Nvenc264) { - return new string[] { $"-c:v {GetEnc(codec)} -profile:v {Config.GetInt(Config.Key.proResProfile)} -pix_fmt {GetPixFmt()}" }; + int cq = (Config.GetInt(Config.Key.h264Crf) * 1.1f).RoundToInt(); + args.Add($"-b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")}"); } - if (codec == Codec.AviRaw) + if (enc == Encoder.Nvenc265) { - return new string[] { $"-c:v {GetEnc(codec)} -pix_fmt {Config.Get(Config.Key.aviColors)}" }; + int cq = (Config.GetInt(Config.Key.h265Crf) * 1.1f).RoundToInt(); + args.Add($"-b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")}"); } - if (codec == Codec.Gif) + if (enc == Encoder.NvencAv1) { - return new string[] { $"-c:v {GetEnc(codec)} -gifflags -offsetting" }; + int cq = (Config.GetInt(Config.Key.av1Crf) * 1.1f).RoundToInt(); + args.Add($"-b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")}"); } - return new string[0]; + if (enc == Encoder.ProResKs) + { + args.Add($"-profile:v {Config.GetInt(Config.Key.proResProfile)}"); + } + + if (enc == Encoder.Gif) + { + args.Add("-gifflags -offsetting"); + } + + return new string[] { string.Join(" ", args) }; } public static string GetTilingArgs(Size resolution, string colArg, string rowArg) @@ -307,12 +269,12 @@ namespace Flowframes.Media Logger.Log($"GetTilingArgs: Video resolution is {resolution.Width}x{resolution.Height} - Using 2^{cols} columns, 2^{rows} rows (=> {Math.Pow(2, cols)}x{Math.Pow(2, rows)} = {Math.Pow(2, cols) * Math.Pow(2, rows)} Tiles)", true); - return $"{(cols > 0 ? colArg+cols : "")} {(rows > 0 ? rowArg + rows : "")}"; + return $"{(cols > 0 ? colArg + cols : "")} {(rows > 0 ? rowArg + rows : "")}"; } public static string GetKeyIntArg(float fps, int intervalSeconds, string arg = "-g ") { - int keyInt = (fps * intervalSeconds).RoundToInt().Clamp(20, 300); + int keyInt = (fps * intervalSeconds).RoundToInt().Clamp(30, 600); return $"{arg}{keyInt}"; } @@ -348,34 +310,21 @@ namespace Flowframes.Media return $"-preset {arg}"; } - static string GetPixFmt() - { - switch (Config.GetInt(Config.Key.pixFmt)) - { - case 0: return "yuv420p"; - case 1: return "yuv444p"; - case 2: return "yuv420p10le"; - case 3: return "yuv444p10le"; - } - - return "yuv420p"; - } - - public static bool ContainerSupportsAllAudioFormats(Interpolate.OutMode outMode, List codecs) + public static bool ContainerSupportsAllAudioFormats(Enums.Output.Format outFormat, List codecs) { if (codecs.Count < 1) Logger.Log($"Warning: ContainerSupportsAllAudioFormats() was called, but codec list has {codecs.Count} entries.", true, false, "ffmpeg"); foreach (string format in codecs) { - if (!ContainerSupportsAudioFormat(outMode, format)) + if (!ContainerSupportsAudioFormat(outFormat, format)) return false; } return true; } - public static bool ContainerSupportsAudioFormat(Interpolate.OutMode outMode, string format) + public static bool ContainerSupportsAudioFormat(Enums.Output.Format outFormat, string format) { bool supported = false; string alias = GetAudioExt(format); @@ -383,35 +332,31 @@ namespace Flowframes.Media string[] formatsMp4 = new string[] { "m4a", "mp3", "ac3", "dts" }; string[] formatsMkv = new string[] { "m4a", "mp3", "ac3", "dts", "ogg", "mp2", "wav", "wma" }; string[] formatsWebm = new string[] { "ogg" }; - string[] formatsProres = new string[] { "m4a", "ac3", "dts", "wav" }; + string[] formatsMov = new string[] { "m4a", "ac3", "dts", "wav" }; string[] formatsAvi = new string[] { "m4a", "ac3", "dts" }; - switch (outMode) + switch (outFormat) { - case Interpolate.OutMode.VidMp4: supported = formatsMp4.Contains(alias); break; - case Interpolate.OutMode.VidMkv: supported = formatsMkv.Contains(alias); break; - case Interpolate.OutMode.VidWebm: supported = formatsWebm.Contains(alias); break; - case Interpolate.OutMode.VidProRes: supported = formatsProres.Contains(alias); break; - case Interpolate.OutMode.VidAvi: supported = formatsAvi.Contains(alias); break; + case Enums.Output.Format.Mp4: supported = formatsMp4.Contains(alias); break; + case Enums.Output.Format.Mkv: supported = formatsMkv.Contains(alias); break; + case Enums.Output.Format.Webm: supported = formatsWebm.Contains(alias); break; + case Enums.Output.Format.Mov: supported = formatsMov.Contains(alias); break; + case Enums.Output.Format.Avi: supported = formatsAvi.Contains(alias); break; } - Logger.Log($"Checking if {outMode} supports audio format '{format}' ({alias}): {supported}", true, false, "ffmpeg"); + Logger.Log($"Checking if {outFormat} supports audio format '{format}' ({alias}): {supported}", true, false, "ffmpeg"); return supported; } - public static string GetExt(Interpolate.OutMode outMode, bool dot = true) + public static string GetExt(ExportSettings settings, bool dot = true) { string ext = dot ? "." : ""; + EncoderInfoVideo info = settings.Encoder.GetInfo(); - switch (outMode) - { - case Interpolate.OutMode.VidMp4: ext += "mp4"; break; - case Interpolate.OutMode.VidMkv: ext += "mkv"; break; - case Interpolate.OutMode.VidWebm: ext += "webm"; break; - case Interpolate.OutMode.VidProRes: ext += "mov"; break; - case Interpolate.OutMode.VidAvi: ext += "avi"; break; - case Interpolate.OutMode.VidGif: ext += "gif"; break; - } + if (string.IsNullOrWhiteSpace(info.OverideExtension)) + ext += settings.Format.ToString().Lower(); + else + ext += info.OverideExtension; return ext; } @@ -440,7 +385,7 @@ namespace Flowframes.Media return "unsupported"; } - public static async Task GetAudioFallbackArgs(string videoPath, Interpolate.OutMode outMode, float itsScale) + public static async Task GetAudioFallbackArgs(string videoPath, Enums.Output.Format outFormat, float itsScale) { bool opusMp4 = Config.GetBool(Config.Key.allowOpusInMp4); int opusBr = Config.GetInt(Config.Key.opusBitrate, 128); @@ -448,7 +393,7 @@ namespace Flowframes.Media int ac = (await GetVideoInfo.GetFfprobeInfoAsync(videoPath, GetVideoInfo.FfprobeMode.ShowStreams, "channels", 0)).GetInt(); string af = GetAudioFilters(itsScale); - if (outMode == Interpolate.OutMode.VidMkv || outMode == Interpolate.OutMode.VidWebm || (outMode == Interpolate.OutMode.VidMp4 && opusMp4)) + if (outFormat == Enums.Output.Format.Mkv || outFormat == Enums.Output.Format.Webm || (outFormat == Enums.Output.Format.Mp4 && opusMp4)) return $"-c:a libopus -b:a {(ac > 4 ? $"{opusBr * 2}" : $"{opusBr}")}k -ac {(ac > 0 ? $"{ac}" : "2")} {af}"; // Double bitrate if 5ch or more, ignore ac if <= 0 else return $"-c:a aac -b:a {(ac > 4 ? $"{aacBr * 2}" : $"{aacBr}")}k -aac_coder twoloop -ac {(ac > 0 ? $"{ac}" : "2")} {af}"; diff --git a/Code/MiscUtils/OutputUtils.cs b/Code/MiscUtils/OutputUtils.cs new file mode 100644 index 0000000..23d3ad1 --- /dev/null +++ b/Code/MiscUtils/OutputUtils.cs @@ -0,0 +1,223 @@ +using Flowframes.Data; +using Flowframes.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; +using static Flowframes.Data.Enums.Encoding; +using Encoder = Flowframes.Data.Enums.Encoding.Encoder; +using PixFmt = Flowframes.Data.Enums.Encoding.PixelFormat; + +namespace Flowframes.MiscUtils +{ + internal class OutputUtils + { + public static EncoderInfoVideo GetEncoderInfoVideo(Encoder encoder) + { + if (encoder == Encoder.X264) + { + return new EncoderInfoVideo + { + Codec = Codec.H264, + Name = "libx264", + PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv444P }, + }; + } + + if (encoder == Encoder.X265) + { + return new EncoderInfoVideo + { + Codec = Codec.H265, + Name = "libx265", + PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv444P, PixFmt.Yuv420P10Le, PixFmt.Yuv444P10Le }, + }; + } + + if (encoder == Encoder.Nvenc264) + { + return new EncoderInfoVideo + { + Codec = Codec.H264, + Name = "h264_nvenc", + PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv444P }, + HwAccelerated = true, + }; + } + + if (encoder == Encoder.SvtAv1) + { + return new EncoderInfoVideo + { + Codec = Codec.AV1, + Name = "libsvtav1", + PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv420P10Le }, + PixelFormatDefault = PixFmt.Yuv420P10Le, + MaxFramerate = 240, + }; + } + + if (encoder == Encoder.VpxVp9) + { + return new EncoderInfoVideo + { + Codec = Codec.VP9, + Name = "libvpx-vp9", + PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv444P, PixFmt.Yuv420P10Le, PixFmt.Yuv444P, PixFmt.Yuv444P10Le }, + }; + } + + if (encoder == Encoder.Nvenc265) + { + return new EncoderInfoVideo + { + Codec = Codec.H265, + Name = "hevc_nvenc", + PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv444P, PixFmt.Yuv420P10Le }, + HwAccelerated = true, + }; + } + + if (encoder == Encoder.NvencAv1) + { + return new EncoderInfoVideo + { + Codec = Codec.AV1, + Name = "av1_nvenc", + PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv444P, PixFmt.Yuv420P10Le }, + PixelFormatDefault = PixFmt.Yuv420P10Le, + HwAccelerated = true, + }; + } + + if (encoder == Encoder.ProResKs) + { + return new EncoderInfoVideo + { + Codec = Codec.ProRes, + Name = "prores_ks", + PixelFormats = new List() { PixFmt.Yuv422P10Le, PixFmt.Yuv444P10Le, PixFmt.Yuva444P10Le }, + }; + } + + if (encoder == Encoder.Gif) + { + return new EncoderInfoVideo + { + Codec = Codec.Gif, + Name = "gif", + PixelFormats = new List() { PixFmt.Rgb8 }, + OverideExtension = "gif", + MaxFramerate = 50, + }; + } + + if (encoder == Encoder.Ffv1) + { + return new EncoderInfoVideo + { + Codec = Codec.Ffv1, + Name = "ffv1", + PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv444P, PixFmt.Yuv422P, PixFmt.Yuv422P, PixFmt.Yuv420P10Le, PixFmt.Yuv444P10Le }, + Lossless = true, + }; + } + + if (encoder == Encoder.Huffyuv) + { + return new EncoderInfoVideo + { + Codec = Codec.Huffyuv, + Name = "huffyuv", + PixelFormats = new List() { PixFmt.Yuv422P, PixFmt.Rgb24 }, + Lossless = true, + }; + } + + if (encoder == Encoder.Magicyuv) + { + return new EncoderInfoVideo + { + Codec = Codec.Magicyuv, + Name = "magicyuv", + PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv422P, PixFmt.Yuv444P }, + Lossless = true, + }; + } + + if (encoder == Encoder.Rawvideo) + { + return new EncoderInfoVideo + { + Codec = Codec.Rawvideo, + Name = "rawvideo", + Lossless = true, + }; + } + + if (encoder == Encoder.Png) + { + return new EncoderInfoVideo + { + Codec = Codec.Png, + Name = "png", + PixelFormats = new List() { PixFmt.Rgb24, PixFmt.Rgba }, + Lossless = true, + IsImageSequence = true, + OverideExtension = "png", + }; + } + + if (encoder == Encoder.Jpeg) + { + return new EncoderInfoVideo + { + Codec = Codec.Jpeg, + Name = "mjpeg", + PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv422P, PixFmt.Yuv444P }, + IsImageSequence = true, + OverideExtension = "jpg", + }; + } + + if (encoder == Encoder.Webp) + { + return new EncoderInfoVideo + { + Codec = Codec.Webp, + Name = "libwebp", + PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuva420P }, + IsImageSequence = true, + OverideExtension = "webp", + }; + } + + return new EncoderInfoVideo(); + } + + public static List GetSupportedCodecs(Enums.Output.Format format) + { + switch(format) + { + case Enums.Output.Format.Mp4: return new List { Codec.H264, Codec.H265, Codec.AV1 }; + case Enums.Output.Format.Mkv: return new List { Codec.H264, Codec.H265, Codec.AV1, Codec.VP9 }; + case Enums.Output.Format.Webm: return new List { Codec.VP9, Codec.AV1 }; + case Enums.Output.Format.Mov: return new List { Codec.ProRes }; + case Enums.Output.Format.Avi: return new List { Codec.Ffv1, Codec.Huffyuv, Codec.Magicyuv, Codec.Rawvideo }; + case Enums.Output.Format.Gif: return new List { Codec.Gif }; + case Enums.Output.Format.Images: return new List { Codec.Png, Codec.Jpeg, Codec.Webp }; + case Enums.Output.Format.Realtime: return new List { }; + default: return new List { }; + } + } + + public static List GetAvailableEncoders(Enums.Output.Format format) + { + var allEncoders = Enum.GetValues(typeof(Encoder)).Cast(); + var supported = GetSupportedCodecs(format); + return allEncoders.Where(e => supported.Contains(GetEncoderInfoVideo(e).Codec)).ToList(); + } + } +} diff --git a/Code/MiscUtils/ParseUtils.cs b/Code/MiscUtils/ParseUtils.cs new file mode 100644 index 0000000..723470d --- /dev/null +++ b/Code/MiscUtils/ParseUtils.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Flowframes.MiscUtils +{ + internal class ParseUtils + { + public static TEnum GetEnum(string str, bool ignoreCase = true, Dictionary stringMap = null) where TEnum : Enum + { + if (stringMap == null) + stringMap = new Dictionary(); + + str = stringMap.Get(str, true, true); + var values = Enum.GetValues(typeof(TEnum)).Cast(); + + foreach (var entry in values) + { + string entryString = stringMap.Get(entry.ToString(), true); + + if (ignoreCase) + { + if (entryString.Lower() == str.Lower()) + return entry; + } + else + { + if (entryString == str) + return entry; + } + } + + return (TEnum)(object)(-1); + } + } +} diff --git a/Code/Os/AiProcess.cs b/Code/Os/AiProcess.cs index 2f81166..c988297 100644 --- a/Code/Os/AiProcess.cs +++ b/Code/Os/AiProcess.cs @@ -678,7 +678,7 @@ namespace Flowframes.Os if (ai.Piped) // VS specific { - if (!hasShownError && Interpolate.currentSettings.outMode != Interpolate.OutMode.Realtime && line.ToLowerInvariant().Contains("fwrite() call failed")) + if (!hasShownError && Interpolate.currentSettings.outSettings.Format != Enums.Output.Format.Realtime && line.ToLowerInvariant().Contains("fwrite() call failed")) { hasShownError = true; UiUtils.ShowMessageBox($"VapourSynth interpolation failed with an unknown error. Check the log for details:\n\n{lastLogLines}", UiUtils.MessageType.Error);