diff --git a/Code/Data/EncoderInfoVideo.cs b/Code/Data/EncoderInfoVideo.cs index 77f17cb..5660dce 100644 --- a/Code/Data/EncoderInfoVideo.cs +++ b/Code/Data/EncoderInfoVideo.cs @@ -18,5 +18,7 @@ namespace Flowframes.Data public PixelFormat PixelFormatDefault { get; set; } public bool IsImageSequence { get; set; } = false; public string OverideExtension { get; set; } = ""; + public List QualityLevels { get; set; } = new List (); + public int QualityDefault { get; set; } = 0; } } diff --git a/Code/Data/Enums.cs b/Code/Data/Enums.cs index 69cb691..625bc24 100644 --- a/Code/Data/Enums.cs +++ b/Code/Data/Enums.cs @@ -14,7 +14,13 @@ 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 } + + public class Quality + { + public enum Common { Lossless, VeryHigh, High, Medium, Low, VeryLow } + public enum ProResProfile { Proxy, Lt, Standard, Hq, Quad4, Quad4Xq } + public enum GifColors { Max256, High128, Medium64, Low32, VeryLow16 } + } } } } \ No newline at end of file diff --git a/Code/Data/ExportSettings.cs b/Code/Data/ExportSettings.cs index 6cbc310..0222281 100644 --- a/Code/Data/ExportSettings.cs +++ b/Code/Data/ExportSettings.cs @@ -6,10 +6,11 @@ using System.Threading.Tasks; namespace Flowframes.Data { - public class ExportSettings + public class OutputSettings { public Enums.Output.Format Format { get; set; } public Enums.Encoding.Encoder Encoder { get; set; } public Enums.Encoding.PixelFormat PixelFormat { get; set; } + public string Quality { get; set; } = ""; } } diff --git a/Code/Data/InterpSettings.cs b/Code/Data/InterpSettings.cs index 4eac8dd..6dfb7c3 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 ExportSettings outSettings; + public OutputSettings 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, ExportSettings outSettingsArg, ModelCollection.ModelInfo modelArg) + public InterpSettings(string inPathArg, string outPathArg, AI aiArg, Fraction inFpsDetectedArg, Fraction inFpsArg, float interpFactorArg, float itsScale, OutputSettings outSettingsArg, ModelCollection.ModelInfo modelArg) { inPath = inPathArg; outPath = outPathArg; @@ -95,7 +95,7 @@ namespace Flowframes inFps = new Fraction(); interpFactor = 0; outFps = new Fraction(); - outSettings = new ExportSettings(); + outSettings = new OutputSettings(); model = null; alpha = false; stepByStep = false; diff --git a/Code/Data/Strings.cs b/Code/Data/Strings.cs index d08c0fd..83ce3ed 100644 --- a/Code/Data/Strings.cs +++ b/Code/Data/Strings.cs @@ -50,5 +50,26 @@ namespace Flowframes.Data { Enums.Encoding.PixelFormat.Rgb8.ToString(), "RGB 256-color" }, { Enums.Encoding.PixelFormat.Rgba.ToString(), "RGBA 8-bit" }, }; + + public static Dictionary VideoQuality = new Dictionary + { + { Enums.Encoding.Quality.Common.Lossless.ToString(), "Lossless" }, + { Enums.Encoding.Quality.Common.VeryHigh.ToString(), "Very High" }, + { Enums.Encoding.Quality.Common.High.ToString(), "High" }, + { Enums.Encoding.Quality.Common.Medium.ToString(), "Medium" }, + { Enums.Encoding.Quality.Common.Low.ToString(), "Low" }, + { Enums.Encoding.Quality.Common.VeryLow.ToString(), "Very Low" }, + { Enums.Encoding.Quality.ProResProfile.Proxy.ToString(), "Proxy" }, + { Enums.Encoding.Quality.ProResProfile.Lt.ToString(), "LT" }, + { Enums.Encoding.Quality.ProResProfile.Standard.ToString(), "Standard" }, + { Enums.Encoding.Quality.ProResProfile.Hq.ToString(), "HQ" }, + { Enums.Encoding.Quality.ProResProfile.Quad4.ToString(), "4444" }, + { Enums.Encoding.Quality.ProResProfile.Quad4Xq.ToString(), "4444 XQ" }, + { Enums.Encoding.Quality.GifColors.Max256.ToString(), "Max (256)" }, + { Enums.Encoding.Quality.GifColors.High128.ToString(), "High (128)" }, + { Enums.Encoding.Quality.GifColors.Medium64.ToString(), "Medium (64)" }, + { Enums.Encoding.Quality.GifColors.Low32.ToString(), "Low (32)" }, + { Enums.Encoding.Quality.GifColors.VeryLow16.ToString(), "Very Low (16)" }, + }; } } diff --git a/Code/Extensions/ExtensionMethods.cs b/Code/Extensions/ExtensionMethods.cs index 123a765..d8ed37b 100644 --- a/Code/Extensions/ExtensionMethods.cs +++ b/Code/Extensions/ExtensionMethods.cs @@ -300,27 +300,37 @@ namespace Flowframes 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; + var strings = entriesToAdd.Select(x => stringMap.Get(x.ToString(), true)); + comboBox.FillFromEnum(strings, stringMap, defaultIndex); } public static void FillFromEnum(this ComboBox comboBox, IEnumerable entries, Dictionary stringMap = null, int defaultIndex = -1) where TEnum : Enum + { + var strings = entries.Select(x => stringMap.Get(x.ToString(), true)); + comboBox.FillFromEnum(strings, stringMap, defaultIndex); + } + + public static void FillFromEnum(this ComboBox comboBox, IEnumerable entries, Dictionary stringMap, TEnum defaultEntry) where TEnum : Enum { if (stringMap == null) stringMap = new Dictionary(); comboBox.Items.Clear(); comboBox.Items.AddRange(entries.Select(x => stringMap.Get(x.ToString(), true)).ToArray()); + comboBox.Text = stringMap.Get(defaultEntry.ToString(), true); + } + + public static void FillFromEnum(this ComboBox comboBox, IEnumerable entries, Dictionary stringMap = null, int defaultIndex = -1) + { + if (stringMap == null) + stringMap = new Dictionary(); + + comboBox.Items.Clear(); + comboBox.Items.AddRange(entries.Select(x => stringMap.Get(x, true)).ToArray()); if (defaultIndex >= 0 && comboBox.Items.Count > 0) comboBox.SelectedIndex = defaultIndex; diff --git a/Code/Form1.Designer.cs b/Code/Form1.Designer.cs index 69aaed1..893710a 100644 --- a/Code/Form1.Designer.cs +++ b/Code/Form1.Designer.cs @@ -92,7 +92,8 @@ 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.comboxOutputEncoder = new System.Windows.Forms.ComboBox(); + this.comboxOutputQuality = 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(); @@ -141,7 +142,6 @@ 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(); @@ -965,7 +965,7 @@ // this.flowLayoutPanel1.Controls.Add(this.comboxOutputFormat); this.flowLayoutPanel1.Controls.Add(this.comboxOutputEncoder); - this.flowLayoutPanel1.Controls.Add(this.comboxOutputCrf); + this.flowLayoutPanel1.Controls.Add(this.comboxOutputQuality); this.flowLayoutPanel1.Controls.Add(this.comboxOutputColors); this.flowLayoutPanel1.Location = new System.Drawing.Point(281, 157); this.flowLayoutPanel1.Name = "flowLayoutPanel1"; @@ -995,18 +995,41 @@ this.comboxOutputFormat.TabIndex = 47; this.comboxOutputFormat.SelectedIndexChanged += new System.EventHandler(this.comboxOutputFormat_SelectedIndexChanged); // - // comboxOutputCrf + // comboxOutputEncoder // - 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"; + 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); + // + // comboxOutputQuality + // + this.comboxOutputQuality.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); + this.comboxOutputQuality.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboxOutputQuality.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.comboxOutputQuality.ForeColor = System.Drawing.Color.White; + this.comboxOutputQuality.FormattingEnabled = true; + this.comboxOutputQuality.Location = new System.Drawing.Point(182, 0); + this.comboxOutputQuality.Margin = new System.Windows.Forms.Padding(0, 0, 6, 0); + this.comboxOutputQuality.Name = "comboxOutputQuality"; + this.comboxOutputQuality.Size = new System.Drawing.Size(100, 23); + this.comboxOutputQuality.TabIndex = 48; // // comboxOutputColors // @@ -1015,7 +1038,7 @@ 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.Location = new System.Drawing.Point(288, 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); @@ -1708,29 +1731,6 @@ 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; @@ -1917,7 +1917,7 @@ 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 comboxOutputQuality; public System.Windows.Forms.ComboBox comboxOutputColors; public System.Windows.Forms.ComboBox comboxOutputEncoder; } diff --git a/Code/Form1.cs b/Code/Form1.cs index 5c4566f..2db4749 100644 --- a/Code/Form1.cs +++ b/Code/Form1.cs @@ -105,17 +105,23 @@ namespace Flowframes var encoder = ParseUtils.GetEnum(comboxOutputEncoder.Text, true, Strings.Encoder); bool noEncoder = (int)encoder == -1; - comboxOutputCrf.Visible = !noEncoder; + comboxOutputQuality.Visible = !noEncoder; comboxOutputColors.Visible = !noEncoder; if (noEncoder) return; EncoderInfoVideo info = OutputUtils.GetEncoderInfoVideo(encoder); - comboxOutputCrf.Visible = !info.Lossless; + + comboxOutputQuality.Visible = !info.Lossless; + comboxOutputQuality.Items.Clear(); + + if(info.QualityLevels.Count > 0) + comboxOutputQuality.FillFromEnum(info.QualityLevels, Strings.VideoQuality, info.QualityDefault); + var pixelFormats = info.PixelFormats; comboxOutputColors.Visible = pixelFormats.Count > 0; - comboxOutputColors.FillFromEnum(pixelFormats, Strings.PixelFormat, 0); + comboxOutputColors.FillFromEnum(pixelFormats, Strings.PixelFormat, info.PixelFormatDefault); } async Task Checks() @@ -393,7 +399,7 @@ namespace Flowframes 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 }; } } + OutputSettings GetExportSettings { get { return new OutputSettings() { Encoder = GetEncoder, Format = GetOutputFormat, PixelFormat = GetPixelFormat, Quality = comboxOutputQuality.Text }; } } public void SetFormat(Enums.Output.Format format) { diff --git a/Code/Main/Export.cs b/Code/Main/Export.cs index 7c0023e..16bae58 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, ExportSettings exportSettings, bool stepByStep) + public static async Task ExportFrames(string path, string outFolder, OutputSettings exportSettings, bool stepByStep) { if(Config.GetInt(Config.Key.sceneChangeFillMode) == 1) { @@ -76,7 +76,7 @@ namespace Flowframes.Main public static async Task GetPipedFfmpegCmd(bool ffplay = false) { InterpSettings s = I.currentSettings; - string encArgs = FfmpegUtils.GetEncArgs(s.outSettings.Encoder, s.outSettings.PixelFormat, (s.ScaledResolution.IsEmpty ? s.InputResolution : s.ScaledResolution), s.outFps.GetFloat(), true).FirstOrDefault(); + string encArgs = FfmpegUtils.GetEncArgs(s.outSettings, (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()); @@ -196,7 +196,7 @@ namespace Flowframes.Main } } - static async Task Encode(ExportSettings settings, string framesPath, string outPath, Fraction fps, Fraction resampleFps) + static async Task Encode(OutputSettings settings, string framesPath, string outPath, Fraction fps, Fraction resampleFps) { string framesFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(I.currentSettings.interpFactor)); @@ -209,7 +209,8 @@ namespace Flowframes.Main if (settings.Format == Enums.Output.Format.Gif) { - await FfmpegEncode.FramesToGifConcat(framesFile, outPath, fps, true, Config.GetInt(Config.Key.gifColors), resampleFps, I.currentSettings.outItsScale); + int paletteColors = OutputUtils.GetGifColors(ParseUtils.GetEnum(settings.Quality, true, Strings.VideoQuality)); + await FfmpegEncode.FramesToGifConcat(framesFile, outPath, fps, true, paletteColors, resampleFps, I.currentSettings.outItsScale); } else { @@ -289,7 +290,7 @@ namespace Flowframes.Main await Loop(outPath, await GetLoopTimes()); } - public static async Task EncodeChunk(string outPath, string interpDir, int chunkNo, ExportSettings settings, int firstFrameNum, int framesAmount) + public static async Task EncodeChunk(string outPath, string interpDir, int chunkNo, OutputSettings 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)); diff --git a/Code/Media/FfmpegEncode.cs b/Code/Media/FfmpegEncode.cs index f98a952..225e1da 100644 --- a/Code/Media/FfmpegEncode.cs +++ b/Code/Media/FfmpegEncode.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using System.Windows.Media; using static Flowframes.AvProcess; using Utils = Flowframes.Media.FfmpegUtils; @@ -13,14 +14,14 @@ namespace Flowframes.Media { partial class FfmpegEncode : FfmpegCommands { - 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) + public static async Task FramesToVideo(string framesFile, string outPath, OutputSettings 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(settings.Encoder, settings.PixelFormat, (Interpolate.currentSettings.ScaledResolution.IsEmpty ? Interpolate.currentSettings.InputResolution : Interpolate.currentSettings.ScaledResolution), Interpolate.currentSettings.outFps.GetFloat()); + string[] encArgs = Utils.GetEncArgs(settings, (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); @@ -50,7 +51,7 @@ namespace Flowframes.Media return $"-r {fps}"; } - public static string GetFfmpegExportArgsOut(Fraction resampleFps, VidExtraData extraData, ExportSettings settings, bool isChunk = false) + public static string GetFfmpegExportArgsOut(Fraction resampleFps, VidExtraData extraData, OutputSettings settings, bool isChunk = false) { List filters = new List(); string extraArgs = Config.Get(Config.Key.ffEncArgs); @@ -71,6 +72,14 @@ namespace Flowframes.Media if (!isChunk && settings.Format == Enums.Output.Format.Mp4) extraArgs += $" -movflags +faststart"; + if(settings.Format == Enums.Output.Format.Gif) + { + string dither = Config.Get(Config.Key.gifDitherType).Split(' ').First(); + int colors = OutputUtils.GetGifColors(ParseUtils.GetEnum(settings.Quality, true, Strings.VideoQuality)); + string paletteFilter = $"\"split[s0][s1];[s0]palettegen={colors}[p];[s1][p]paletteuse=dither={dither}\""; + filters.Add(paletteFilter); + } + filters.Add(GetPadFilter()); return filters.Count > 0 ? $"-vf {string.Join(",", filters)}" : "" + $" {extraArgs}"; } diff --git a/Code/Media/FfmpegUtils.cs b/Code/Media/FfmpegUtils.cs index 9f5cfae..818b022 100644 --- a/Code/Media/FfmpegUtils.cs +++ b/Code/Media/FfmpegUtils.cs @@ -171,8 +171,10 @@ namespace Flowframes.Media return false; } - 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. + public static string[] GetEncArgs(OutputSettings settings, Size res, float fps, bool realtime = false) // Array contains as many entries as there are encoding passes. If "realtime" is true, force single pass. { + Encoder enc = settings.Encoder; + PixelFormat pixFmt = settings.PixelFormat; int keyint = 10; var args = new List(); @@ -189,25 +191,26 @@ namespace Flowframes.Media if (enc == Encoder.X264) { 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}"); + int crf = OutputUtils.GetCrf(ParseUtils.GetEnum(settings.Quality, true, Strings.VideoQuality), enc); + args.Add($"-crf {crf} -preset {preset}"); } if (enc == Encoder.X265) { string preset = Config.Get(Config.Key.ffEncPreset).ToLowerInvariant().Remove(" "); // TODO: Replace this ugly stuff with enums - int crf = Config.GetInt(Config.Key.h265Crf); + int crf = OutputUtils.GetCrf(ParseUtils.GetEnum(settings.Quality, true, Strings.VideoQuality), enc); args.Add($"{(crf > 0 ? $"-crf {crf}" : "-x265-params lossless=1")} -preset {preset}"); } if (enc == Encoder.SvtAv1) { - int cq = Config.GetInt(Config.Key.av1Crf); - args.Add($"-b:v 0 -qp {cq} {GetSvtAv1Speed()} -svtav1-params enable-qm=1:enable-overlays=1:enable-tf=0:scd=0"); + int crf = OutputUtils.GetCrf(ParseUtils.GetEnum(settings.Quality, true, Strings.VideoQuality), enc); + args.Add($"-crf {crf} {GetSvtAv1Speed()} -svtav1-params enable-qm=1:enable-overlays=1:enable-tf=0:scd=0"); } if (enc == Encoder.VpxVp9) { - int crf = Config.GetInt(Config.Key.vp9Crf); + int crf = OutputUtils.GetCrf(ParseUtils.GetEnum(settings.Quality, true, Strings.VideoQuality), enc); string qualityStr = (crf > 0) ? $"-crf {crf}" : "-lossless 1"; string t = GetTilingArgs(res, "-tile-columns ", "-tile-rows "); @@ -226,20 +229,20 @@ namespace Flowframes.Media if (enc == Encoder.Nvenc264) { - int cq = (Config.GetInt(Config.Key.h264Crf) * 1.1f).RoundToInt(); - args.Add($"-b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")}"); + int crf = OutputUtils.GetCrf(ParseUtils.GetEnum(settings.Quality, true, Strings.VideoQuality), enc); + args.Add($"-b:v 0 {(crf > 0 ? $"-cq {crf} -preset p7" : "-preset lossless")}"); } if (enc == Encoder.Nvenc265) { - int cq = (Config.GetInt(Config.Key.h265Crf) * 1.1f).RoundToInt(); - args.Add($"-b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")}"); + int crf = OutputUtils.GetCrf(ParseUtils.GetEnum(settings.Quality, true, Strings.VideoQuality), enc); + args.Add($"-b:v 0 {(crf > 0 ? $"-cq {crf} -preset p7" : "-preset lossless")}"); } if (enc == Encoder.NvencAv1) { - int cq = (Config.GetInt(Config.Key.av1Crf) * 1.1f).RoundToInt(); - args.Add($"-b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")}"); + int crf = OutputUtils.GetCrf(ParseUtils.GetEnum(settings.Quality, true, Strings.VideoQuality), enc); + args.Add($"-b:v 0 -preset p7 {(crf > 0 ? $"-cq {crf}" : "-tune lossless")}"); // Lossless not supported as of Jan 2023!! } if (enc == Encoder.ProResKs) @@ -348,7 +351,7 @@ namespace Flowframes.Media return supported; } - public static string GetExt(ExportSettings settings, bool dot = true) + public static string GetExt(OutputSettings settings, bool dot = true) { string ext = dot ? "." : ""; EncoderInfoVideo info = settings.Encoder.GetInfo(); diff --git a/Code/MiscUtils/OutputUtils.cs b/Code/MiscUtils/OutputUtils.cs index 23d3ad1..08d8c18 100644 --- a/Code/MiscUtils/OutputUtils.cs +++ b/Code/MiscUtils/OutputUtils.cs @@ -23,6 +23,8 @@ namespace Flowframes.MiscUtils Codec = Codec.H264, Name = "libx264", PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv444P }, + QualityLevels = ParseUtils.GetEnumStrings(), + QualityDefault = (int)Quality.Common.VeryHigh, }; } @@ -33,6 +35,8 @@ namespace Flowframes.MiscUtils Codec = Codec.H265, Name = "libx265", PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv444P, PixFmt.Yuv420P10Le, PixFmt.Yuv444P10Le }, + QualityLevels = ParseUtils.GetEnumStrings(), + QualityDefault = (int)Quality.Common.VeryHigh, }; } @@ -43,6 +47,8 @@ namespace Flowframes.MiscUtils Codec = Codec.H264, Name = "h264_nvenc", PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv444P }, + QualityLevels = ParseUtils.GetEnumStrings(), + QualityDefault = (int)Quality.Common.VeryHigh, HwAccelerated = true, }; } @@ -55,6 +61,8 @@ namespace Flowframes.MiscUtils Name = "libsvtav1", PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv420P10Le }, PixelFormatDefault = PixFmt.Yuv420P10Le, + QualityLevels = ParseUtils.GetEnumStrings(), + QualityDefault = (int)Quality.Common.VeryHigh, MaxFramerate = 240, }; } @@ -66,6 +74,8 @@ namespace Flowframes.MiscUtils Codec = Codec.VP9, Name = "libvpx-vp9", PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv444P, PixFmt.Yuv420P10Le, PixFmt.Yuv444P, PixFmt.Yuv444P10Le }, + QualityLevels = ParseUtils.GetEnumStrings(), + QualityDefault = (int)Quality.Common.VeryHigh, }; } @@ -76,6 +86,8 @@ namespace Flowframes.MiscUtils Codec = Codec.H265, Name = "hevc_nvenc", PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv444P, PixFmt.Yuv420P10Le }, + QualityLevels = ParseUtils.GetEnumStrings(), + QualityDefault = (int)Quality.Common.VeryHigh, HwAccelerated = true, }; } @@ -87,6 +99,8 @@ namespace Flowframes.MiscUtils Codec = Codec.AV1, Name = "av1_nvenc", PixelFormats = new List() { PixFmt.Yuv420P, PixFmt.Yuv444P, PixFmt.Yuv420P10Le }, + QualityLevels = ParseUtils.GetEnumStrings(), + QualityDefault = (int)Quality.Common.VeryHigh, PixelFormatDefault = PixFmt.Yuv420P10Le, HwAccelerated = true, }; @@ -99,6 +113,8 @@ namespace Flowframes.MiscUtils Codec = Codec.ProRes, Name = "prores_ks", PixelFormats = new List() { PixFmt.Yuv422P10Le, PixFmt.Yuv444P10Le, PixFmt.Yuva444P10Le }, + QualityLevels = ParseUtils.GetEnumStrings(), + QualityDefault = (int)Quality.ProResProfile.Hq, }; } @@ -109,6 +125,8 @@ namespace Flowframes.MiscUtils Codec = Codec.Gif, Name = "gif", PixelFormats = new List() { PixFmt.Rgb8 }, + QualityLevels = ParseUtils.GetEnumStrings(), + QualityDefault = (int)Quality.GifColors.High128, OverideExtension = "gif", MaxFramerate = 50, }; @@ -199,7 +217,7 @@ namespace Flowframes.MiscUtils public static List GetSupportedCodecs(Enums.Output.Format format) { - switch(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 }; @@ -216,8 +234,53 @@ namespace Flowframes.MiscUtils 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(); + var supportedCodecs = GetSupportedCodecs(format); + var availableEncoders = supportedCodecs.SelectMany(codec => allEncoders.Where(enc => enc.GetInfo().Codec == codec)); + return availableEncoders.ToList(); } + + public static int GetCrf (Quality.Common qualityLevel, Encoder encoder) + { + int baseCrf = Crfs[qualityLevel]; + float multiplier = 1f; + + if (encoder == Encoder.X265) + multiplier = 1.0f; + if (encoder == Encoder.VpxVp9) + multiplier = 1.3f; + if (encoder == Encoder.SvtAv1) + multiplier = 1.3f; + if (encoder == Encoder.Nvenc264) + multiplier = 1.1f; + if (encoder == Encoder.Nvenc265) + multiplier = 1.15f; + if (encoder == Encoder.NvencAv1) + multiplier = 1.3f; + + return (baseCrf * multiplier).RoundToInt(); + } + + public static int GetGifColors (Quality.GifColors qualityLevel) + { + switch (qualityLevel) + { + case Quality.GifColors.Max256: return 256; + case Quality.GifColors.High128: return 128; + case Quality.GifColors.Medium64: return 64; + case Quality.GifColors.Low32: return 32; + case Quality.GifColors.VeryLow16: return 16; + default: return 128; + } + } + + public static Dictionary Crfs = new Dictionary + { + { Quality.Common.Lossless, 0 }, + { Quality.Common.VeryHigh, 16 }, + { Quality.Common.High, 20 }, + { Quality.Common.Medium, 26 }, + { Quality.Common.Low, 32 }, + { Quality.Common.VeryLow, 40 }, + }; } } diff --git a/Code/MiscUtils/ParseUtils.cs b/Code/MiscUtils/ParseUtils.cs index 723470d..b3c8c7e 100644 --- a/Code/MiscUtils/ParseUtils.cs +++ b/Code/MiscUtils/ParseUtils.cs @@ -34,5 +34,11 @@ namespace Flowframes.MiscUtils return (TEnum)(object)(-1); } + + public static List GetEnumStrings() where TEnum : Enum + { + var entries = Enum.GetValues(typeof(TEnum)).Cast(); + return entries.Select(e => e.ToString()).ToList(); + } } } diff --git a/changelog.txt b/changelog.txt index aa3a1fb..13dff8b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,10 @@ +Flowframes 1.40.0 Changelog: +- New: Added RIFE 4.6 model +- Improved: Updated ffmpeg, includes SVT-AV1 1.4.1 which speeds up encoding by ~12% +- Fixed: RIFE-NCNN-VS did not work with input resolutions not divisible by 2 +- Fixed: Issues with certain system languages (e.g. Turkish) + + Flowframes 1.39.0 Changelog: - Added real-time interpolation output mode (with RIFE-NCNN-VS) - Added RIFE 4.4 and RIFE 4.3 models