From 14df754538d9dc7958452d04867e35752afbe65a Mon Sep 17 00:00:00 2001 From: N00MKRAD Date: Wed, 6 Jan 2021 17:41:18 +0100 Subject: [PATCH] Fixed and improved FPS limiting feature, added AVI codec options --- Code/AudioVideo/FFmpegCommands.cs | 34 +++++++---- Code/AudioVideo/FFmpegUtils.cs | 15 +++-- Code/ExtensionMethods.cs | 15 +++++ Code/Forms/SettingsForm.Designer.cs | 62 +++++++++++++++++-- Code/Forms/SettingsForm.cs | 4 ++ Code/IO/Config.cs | 2 + Code/Main/AutoEncode.cs | 25 +++----- Code/Main/CreateVideo.cs | 92 ++++++++++++++--------------- Code/Main/InterpolateUtils.cs | 4 +- 9 files changed, 164 insertions(+), 89 deletions(-) diff --git a/Code/AudioVideo/FFmpegCommands.cs b/Code/AudioVideo/FFmpegCommands.cs index f01cd3e..aabc0aa 100644 --- a/Code/AudioVideo/FFmpegCommands.cs +++ b/Code/AudioVideo/FFmpegCommands.cs @@ -93,26 +93,31 @@ namespace Flowframes } public static async Task FramesToVideoConcat(string framesFile, string outPath, Interpolate.OutMode outMode, float fps, AvProcess.LogMode logMode = AvProcess.LogMode.OnlyLastLine, bool isChunk = false) + { + await FramesToVideoConcat(framesFile, outPath, outMode, fps, 0, logMode, isChunk); + } + + public static async Task FramesToVideoConcat(string framesFile, string outPath, Interpolate.OutMode outMode, float fps, float resampleFps, AvProcess.LogMode logMode = AvProcess.LogMode.OnlyLastLine, bool isChunk = false) { if (logMode != AvProcess.LogMode.Hidden) - Logger.Log($"Encoding video..."); - string encArgs = Utils.GetEncArgs(Utils.GetCodec(outMode)) + " -pix_fmt yuv420p "; - if (!isChunk) encArgs += $"-movflags +faststart"; + Logger.Log((resampleFps <= 0) ? $"Encoding video..." : $"Encoding video resampled to {resampleFps.ToString().Replace(",", ".")} FPS..."); + Directory.CreateDirectory(outPath.GetParentDir()); + string encArgs = Utils.GetEncArgs(Utils.GetCodec(outMode)); + if (!isChunk) encArgs += $" -movflags +faststart"; string vfrFilename = Path.GetFileName(framesFile); - //string vsync = (Interpolate.current.interpFactor == 2) ? "-vsync 1" : "-vsync 2"; string rate = fps.ToString().Replace(",", "."); + string vf = (resampleFps <= 0) ? "" : $"-vf fps=fps={resampleFps.ToString().Replace(",", ".")}"; string extraArgs = Config.Get("ffEncArgs"); - string args = $"-loglevel error -vsync 0 -f concat -r {rate} -i {vfrFilename} {encArgs} {extraArgs} -threads {Config.GetInt("ffEncThreads")} {outPath.Wrap()}"; - //string args = $"-vsync 0 -f concat -i {vfrFilename} {encArgs} {extraArgs} -threads {Config.GetInt("ffEncThreads")} {outPath.Wrap()}"; + string args = $"-loglevel error -vsync 0 -f concat -r {rate} -i {vfrFilename} {encArgs} {vf} {extraArgs} -threads {Config.GetInt("ffEncThreads")} {outPath.Wrap()}"; await AvProcess.RunFfmpeg(args, framesFile.GetParentDir(), logMode); } - public static async Task ConcatVideos(string concatFile, string outPath, float fps, int looptimes = -1) + public static async Task ConcatVideos(string concatFile, string outPath, int looptimes = -1) { Logger.Log($"Merging videos..."); string loopStr = (looptimes > 0) ? $"-stream_loop {looptimes}" : ""; string vfrFilename = Path.GetFileName(concatFile); - string args = $" {loopStr} -vsync 1 -f concat -r {fps.ToString().Replace(",", ".")} -i {vfrFilename} -c copy -pix_fmt yuv420p {outPath.Wrap()}"; + string args = $" {loopStr} -vsync 1 -f concat -i {vfrFilename} -c copy -pix_fmt yuv420p -movflags +faststart {outPath.Wrap()}"; await AvProcess.RunFfmpeg(args, concatFile.GetParentDir(), AvProcess.LogMode.Hidden); } @@ -127,13 +132,16 @@ namespace Flowframes DeleteSource(inputPath); } - public static async Task FramesToGifConcat(string framesFile, string outPath, float fps, bool palette, int colors = 64) + public static async Task FramesToGifConcat(string framesFile, string outPath, float fps, bool palette, int colors = 64, float resampleFps = -1, AvProcess.LogMode logMode = AvProcess.LogMode.OnlyLastLine) { - Logger.Log($"Encoding GIF..."); + if (logMode != AvProcess.LogMode.Hidden) + Logger.Log((resampleFps <= 0) ? $"Encoding GIF..." : $"Encoding GIF resampled to {resampleFps.ToString().Replace(",", ".")} FPS..."); string vfrFilename = Path.GetFileName(framesFile); - string filter = palette ? $"-vf \"split[s0][s1];[s0]palettegen={colors}[p];[s1][p]paletteuse=dither=floyd_steinberg:diff_mode=rectangle\"" : ""; - string rate = fps.ToString().Replace(",", "."); - string args = $"-loglevel error -f concat -r {rate} -i {vfrFilename.Wrap()} -f gif {filter} {outPath.Wrap()}"; + string paletteFilter = palette ? $"-vf \"split[s0][s1];[s0]palettegen={colors}[p];[s1][p]paletteuse=dither=floyd_steinberg:diff_mode=rectangle\"" : ""; + string fpsFilter = (resampleFps <= 0) ? "" : $"fps=fps={resampleFps.ToStringDot()}"; + string vf = FormatUtils.ConcatStrings(new string[] { paletteFilter, fpsFilter }); + string rate = fps.ToStringDot(); + string args = $"-loglevel error -f concat -r {rate} -i {vfrFilename.Wrap()} -f gif {vf} {outPath.Wrap()}"; await AvProcess.RunFfmpeg(args, framesFile.GetParentDir(), AvProcess.LogMode.OnlyLastLine); } diff --git a/Code/AudioVideo/FFmpegUtils.cs b/Code/AudioVideo/FFmpegUtils.cs index 69bdec9..f239edf 100644 --- a/Code/AudioVideo/FFmpegUtils.cs +++ b/Code/AudioVideo/FFmpegUtils.cs @@ -40,7 +40,7 @@ namespace Flowframes.AudioVideo case Codec.H265: return "libx265"; case Codec.VP9: return "libvpx-vp9"; case Codec.ProRes: return "prores_ks"; - case Codec.AviRaw: return "rawvideo"; + case Codec.AviRaw: return Config.Get("aviCodec"); } return "libx264"; } @@ -51,12 +51,12 @@ namespace Flowframes.AudioVideo if(codec == Codec.H264) { - args += $"-crf {Config.GetInt("h264Crf")} -preset {Config.Get("ffEncPreset")}"; + args += $"-crf {Config.GetInt("h264Crf")} -preset {Config.Get("ffEncPreset")} -pix_fmt yuv420p"; } if (codec == Codec.H265) { - args += $"-crf {Config.GetInt("h265Crf")} -preset {Config.Get("ffEncPreset")}"; + args += $"-crf {Config.GetInt("h265Crf")} -preset {Config.Get("ffEncPreset")} -pix_fmt yuv420p"; } if (codec == Codec.VP9) @@ -64,12 +64,17 @@ namespace Flowframes.AudioVideo int crf = Config.GetInt("vp9Crf"); string qualityStr = (crf > 0) ? $"-crf {crf}" : "-lossless 1"; string cpuUsed = Config.GetInt("vp9Speed", 3).ToString(); - args += $"{qualityStr} -cpu-used {cpuUsed} -tile-columns 2 -tile-rows 2 -row-mt 1"; + args += $"{qualityStr} -cpu-used {cpuUsed} -tile-columns 2 -tile-rows 2 -row-mt 1 -pix_fmt yuv420p"; } if(codec == Codec.ProRes) { - args += $"-profile:v {Config.GetInt("proResProfile")}"; + args += $"-profile:v {Config.GetInt("proResProfile")} -pix_fmt yuv420p"; + } + + if (codec == Codec.AviRaw) + { + args += $"-pix_fmt {Config.Get("aviColors")}"; } return args; diff --git a/Code/ExtensionMethods.cs b/Code/ExtensionMethods.cs index 8748cd2..475afcf 100644 --- a/Code/ExtensionMethods.cs +++ b/Code/ExtensionMethods.cs @@ -165,5 +165,20 @@ namespace Flowframes { return str.Split('#')[0].SplitBy("//")[0]; } + + public static string FilenameSuffix(this string path, string suffix) + { + string filename = Path.ChangeExtension(path, null); + string ext = Path.GetExtension(path); + return filename + suffix + ext; + } + + public static string ToStringDot (this float f, string format = "") + { + if(string.IsNullOrWhiteSpace(format)) + return f.ToString().Replace(",", "."); + else + return f.ToString(format).Replace(",", "."); + } } } diff --git a/Code/Forms/SettingsForm.Designer.cs b/Code/Forms/SettingsForm.Designer.cs index cd4b103..ccaae66 100644 --- a/Code/Forms/SettingsForm.Designer.cs +++ b/Code/Forms/SettingsForm.Designer.cs @@ -151,6 +151,9 @@ this.cmdDebugMode = new System.Windows.Forms.ComboBox(); this.titleLabel = new System.Windows.Forms.Label(); this.toolTip1 = new System.Windows.Forms.ToolTip(this.components); + this.label60 = new System.Windows.Forms.Label(); + this.aviCodec = new System.Windows.Forms.ComboBox(); + this.aviColors = new System.Windows.Forms.ComboBox(); this.settingsTabList.SuspendLayout(); this.generalTab.SuspendLayout(); this.tabListPage2.SuspendLayout(); @@ -963,6 +966,9 @@ // vidExportTab // this.vidExportTab.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48))))); + this.vidExportTab.Controls.Add(this.aviColors); + this.vidExportTab.Controls.Add(this.aviCodec); + this.vidExportTab.Controls.Add(this.label60); this.vidExportTab.Controls.Add(this.proResProfile); this.vidExportTab.Controls.Add(this.label59); this.vidExportTab.Controls.Add(this.panel11); @@ -1161,8 +1167,8 @@ this.maxFpsMode.ForeColor = System.Drawing.Color.White; this.maxFpsMode.FormattingEnabled = true; this.maxFpsMode.Items.AddRange(new object[] { - "Save Video With Limited FPS", - "Save Both With Full FPS And Create Second Video With Limited FPS"}); + "Only Create Video With Limited FPS", + "Create Videos With Full FPS And Limited FPS (2x Slower Encoding!)"}); this.maxFpsMode.Location = new System.Drawing.Point(280, 127); this.maxFpsMode.Name = "maxFpsMode"; this.maxFpsMode.Size = new System.Drawing.Size(400, 21); @@ -1231,7 +1237,7 @@ // this.label18.AutoSize = true; this.label18.ForeColor = System.Drawing.Color.Silver; - this.label18.Location = new System.Drawing.Point(393, 361); + this.label18.Location = new System.Drawing.Point(393, 391); this.label18.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7); this.label18.Name = "label18"; this.label18.Size = new System.Drawing.Size(208, 13); @@ -1250,7 +1256,7 @@ "64 (Medium)", "32 (Low)", "16 (Very Low)"}); - this.gifColors.Location = new System.Drawing.Point(280, 357); + this.gifColors.Location = new System.Drawing.Point(280, 387); this.gifColors.Name = "gifColors"; this.gifColors.Size = new System.Drawing.Size(100, 21); this.gifColors.TabIndex = 41; @@ -1258,7 +1264,7 @@ // label17 // this.label17.AutoSize = true; - this.label17.Location = new System.Drawing.Point(10, 360); + this.label17.Location = new System.Drawing.Point(10, 390); this.label17.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7); this.label17.Name = "label17"; this.label17.Size = new System.Drawing.Size(154, 13); @@ -1663,6 +1669,49 @@ this.titleLabel.TabIndex = 1; this.titleLabel.Text = "Settings"; // + // label60 + // + this.label60.AutoSize = true; + this.label60.Location = new System.Drawing.Point(10, 360); + this.label60.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7); + this.label60.Name = "label60"; + this.label60.Size = new System.Drawing.Size(130, 13); + this.label60.TabIndex = 69; + this.label60.Text = "AVI: Codec / Color Space"; + // + // aviCodec + // + this.aviCodec.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); + this.aviCodec.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.aviCodec.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.aviCodec.ForeColor = System.Drawing.Color.White; + this.aviCodec.FormattingEnabled = true; + this.aviCodec.Items.AddRange(new object[] { + "ffv1", + "huffyuv", + "rawvideo"}); + this.aviCodec.Location = new System.Drawing.Point(280, 357); + this.aviCodec.Name = "aviCodec"; + this.aviCodec.Size = new System.Drawing.Size(200, 21); + this.aviCodec.TabIndex = 70; + // + // aviColors + // + this.aviColors.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64))))); + this.aviColors.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.aviColors.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.aviColors.ForeColor = System.Drawing.Color.White; + this.aviColors.FormattingEnabled = true; + this.aviColors.Items.AddRange(new object[] { + "yuv420p", + "yuv422p", + "yuv444p", + "rgb24"}); + this.aviColors.Location = new System.Drawing.Point(486, 357); + this.aviColors.Name = "aviColors"; + this.aviColors.Size = new System.Drawing.Size(194, 21); + this.aviColors.TabIndex = 71; + // // SettingsForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -1822,5 +1871,8 @@ private System.Windows.Forms.Label label57; private System.Windows.Forms.ComboBox proResProfile; private System.Windows.Forms.Label label59; + private System.Windows.Forms.ComboBox aviColors; + private System.Windows.Forms.ComboBox aviCodec; + private System.Windows.Forms.Label label60; } } \ No newline at end of file diff --git a/Code/Forms/SettingsForm.cs b/Code/Forms/SettingsForm.cs index 1eefd58..2964b02 100644 --- a/Code/Forms/SettingsForm.cs +++ b/Code/Forms/SettingsForm.cs @@ -77,6 +77,8 @@ namespace Flowframes.Forms ConfigParser.SaveGuiElement(vp9Crf); ConfigParser.SaveComboxIndex(proResProfile); ConfigParser.SaveGuiElement(gifColors); + ConfigParser.SaveGuiElement(aviCodec); + ConfigParser.SaveGuiElement(aviColors); ConfigParser.SaveGuiElement(maxFps); ConfigParser.SaveComboxIndex(maxFpsMode); ConfigParser.SaveComboxIndex(loopMode); @@ -122,6 +124,8 @@ namespace Flowframes.Forms ConfigParser.LoadGuiElement(vp9Crf); ConfigParser.LoadComboxIndex(proResProfile); ConfigParser.LoadGuiElement(gifColors); + ConfigParser.LoadGuiElement(aviCodec); + ConfigParser.LoadGuiElement(aviColors); ConfigParser.LoadGuiElement(maxFps); ConfigParser.LoadComboxIndex(maxFpsMode); ConfigParser.LoadComboxIndex(loopMode); diff --git a/Code/IO/Config.cs b/Code/IO/Config.cs index efde548..ef3ff25 100644 --- a/Code/IO/Config.cs +++ b/Code/IO/Config.cs @@ -126,6 +126,8 @@ namespace Flowframes.IO if (key == "h265Crf") return WriteDefault(key, "24"); if (key == "vp9Crf") return WriteDefault(key, "28"); if (key == "proResProfile") return WriteDefault(key, "2"); + if (key == "aviCodec") return WriteDefault(key, "ffv1"); + if (key == "aviColors") return WriteDefault(key, "yuv420p"); if (key == "gifColors") return WriteDefault(key, "128 (High)"); if (key == "minVidLength") return WriteDefault(key, "2"); // AI diff --git a/Code/Main/AutoEncode.cs b/Code/Main/AutoEncode.cs index 4d269ba..a479ec0 100644 --- a/Code/Main/AutoEncode.cs +++ b/Code/Main/AutoEncode.cs @@ -24,24 +24,25 @@ namespace Flowframes.Main public static bool paused; - public static void UpdateSafetyBufferSize () + public static void UpdateChunkAndBufferSizes () { + chunkSize = GetChunkSize(IOUtils.GetAmountOfFiles(Interpolate.current.framesFolder, false, "*.png") * Interpolate.current.interpFactor); safetyBufferFrames = Interpolate.current.ai.aiName.ToUpper().Contains("NCNN") ? 60 : 30; // Use bigger safety buffer for NCNN } public static async Task MainLoop(string interpFramesPath) { - UpdateSafetyBufferSize(); + UpdateChunkAndBufferSizes(); interpFramesFolder = interpFramesPath; videoChunksFolder = Path.Combine(interpFramesPath.GetParentDir(), Paths.chunksDir); + if (Interpolate.currentlyUsingAutoEnc) + Directory.CreateDirectory(videoChunksFolder); encodedFrameLines.Clear(); unencodedFrameLines.Clear(); - chunkSize = GetChunkSize(IOUtils.GetAmountOfFiles(Interpolate.current.framesFolder, false, "*.png") * Interpolate.current.interpFactor); Logger.Log($"Starting AutoEncode MainLoop - Chunk Size: {chunkSize} Frames - Safety Buffer: {safetyBufferFrames} Frames", true); - int videoIndex = 1; string encFile = Path.Combine(interpFramesPath.GetParentDir(), $"vfr-{Interpolate.current.interpFactor}x.ini"); interpFramesLines = IOUtils.ReadLines(encFile).Select(x => x.Split('/').Last().Remove("'")).ToArray(); // Array with frame filenames @@ -73,8 +74,6 @@ namespace Flowframes.Main unencodedFrameLines.Add(vfrLine); } - Directory.CreateDirectory(videoChunksFolder); - bool aiRunning = !AiProcess.currentAiProcess.HasExited; if (unencodedFrameLines.Count >= (chunkSize + safetyBufferFrames) || !aiRunning) // Encode every n frames, or after process has exited @@ -83,12 +82,10 @@ namespace Flowframes.Main List frameLinesToEncode = aiRunning ? unencodedFrameLines.Take(chunkSize).ToList() : unencodedFrameLines; // Take all remaining frames if process is done //Logger.Log($"{unencodedFrameLines.Count} unencoded frame lines, {IOUtils.GetAmountOfFiles(interpFramesFolder, false)} frames in interp folder", true, false, "ffmpeg"); - Logger.Log($"Encoding Chunk #{videoIndex} using {Path.GetFileName(interpFramesLines[frameLinesToEncode.First()])} through {Path.GetFileName(Path.GetFileName(interpFramesLines[frameLinesToEncode.Last()]))}", true, false, "ffmpeg"); - //IOUtils.ZeroPadDir(framesToEncode, Padding.interpFrames); // Zero-pad frames before encoding to make sure filenames match with VFR file - - string outpath = Path.Combine(videoChunksFolder, $"{videoIndex.ToString().PadLeft(4, '0')}{FFmpegUtils.GetExt(Interpolate.current.outMode)}"); + string outpath = Path.Combine(videoChunksFolder, "chunks", $"{videoIndex.ToString().PadLeft(4, '0')}{FFmpegUtils.GetExt(Interpolate.current.outMode)}"); int firstFrameNum = frameLinesToEncode[0]; + Logger.Log($"Encoding Chunk #{videoIndex} to '{outpath}' using {Path.GetFileName(interpFramesLines[frameLinesToEncode.First()])} through {Path.GetFileName(Path.GetFileName(interpFramesLines[frameLinesToEncode.Last()]))}", true, false, "ffmpeg"); await CreateVideo.EncodeChunk(outpath, Interpolate.current.outMode, firstFrameNum, frameLinesToEncode.Count); if(Interpolate.canceled) return; @@ -117,15 +114,9 @@ namespace Flowframes.Main if (Interpolate.canceled) return; - string concatFile = Path.Combine(interpFramesPath.GetParentDir(), "chunks-concat.ini"); - string concatFileContent = ""; - foreach (string vid in IOUtils.GetFilesSorted(videoChunksFolder)) - concatFileContent += $"file '{Paths.chunksDir}/{Path.GetFileName(vid)}'\n"; - File.WriteAllText(concatFile, concatFileContent); - IOUtils.ReverseRenaming(AiProcess.filenameMap, true); // Get timestamps back - await CreateVideo.ChunksToVideo(videoChunksFolder, concatFile, Interpolate.current.outFilename); + await CreateVideo.ChunksToVideos(Interpolate.current.tempFolder, videoChunksFolder, Interpolate.current.outFilename); } public static bool HasWorkToDo () diff --git a/Code/Main/CreateVideo.cs b/Code/Main/CreateVideo.cs index 032d6b6..784383a 100644 --- a/Code/Main/CreateVideo.cs +++ b/Code/Main/CreateVideo.cs @@ -46,13 +46,16 @@ namespace Flowframes.Main Program.mainForm.SetStatus("Creating output video from frames..."); try { - int maxFps = Config.GetInt("maxFps"); - if (maxFps == 0) maxFps = 500; + float maxFps = Config.GetFloat("maxFps"); + bool fpsLimit = maxFps != 0 && i.current.outFps > maxFps; - if (i.current.outFps > maxFps) - await Encode(mode, path, outPath, i.current.outFps, maxFps, (Config.GetInt("maxFpsMode") == 1)); - else + bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt("maxFpsMode") == 0; + + if(!dontEncodeFullFpsVid) await Encode(mode, path, outPath, i.current.outFps); + + if (fpsLimit) + await Encode(mode, path, outPath.FilenameSuffix($"-{maxFps.ToStringDot("0.00")}fps"), i.current.outFps, maxFps); } catch (Exception e) { @@ -95,56 +98,51 @@ namespace Flowframes.Main } } - static async Task Encode(i.OutMode mode, string framesPath, string outPath, float fps, float changeFps = -1, bool keepOriginalFpsVid = true) + static async Task Encode(i.OutMode mode, string framesPath, string outPath, float fps, float resampleFps = -1) { currentOutFile = outPath; string vfrFile = Path.Combine(framesPath.GetParentDir(), $"vfr-{i.current.interpFactor}x.ini"); if (mode == i.OutMode.VidGif) { - await FFmpegCommands.FramesToGifConcat(vfrFile, outPath, fps, true, Config.GetInt("gifColors")); + await FFmpegCommands.FramesToGifConcat(vfrFile, outPath, fps, true, Config.GetInt("gifColors"), resampleFps); } else { - int looptimes = GetLoopTimes(); - bool h265 = Config.GetInt("mp4Enc") == 1; - int crf = h265 ? Config.GetInt("h265Crf") : Config.GetInt("h264Crf"); - - await FFmpegCommands.FramesToVideoConcat(vfrFile, outPath, mode, fps); + await FFmpegCommands.FramesToVideoConcat(vfrFile, outPath, mode, fps, resampleFps); await MergeAudio(i.current.inPath, outPath); - if (changeFps > 0) - { - currentOutFile = IOUtils.FilenameSuffix(outPath, $"-{changeFps.ToString("0")}fps"); - Program.mainForm.SetStatus("Creating video with desired frame rate..."); - await FFmpegCommands.ConvertFramerate(outPath, currentOutFile, h265, crf, changeFps, !keepOriginalFpsVid); - await MergeAudio(i.current.inPath, currentOutFile); - } - + int looptimes = GetLoopTimes(); if (looptimes > 0) await Loop(currentOutFile, looptimes); - } } - public static async Task ChunksToVideo(string chunksPath, string vfrFile, string outPath) + public static async Task ChunksToVideos(string tempFolder, string chunksFolder, string baseOutPath) { - if (IOUtils.GetAmountOfFiles(chunksPath, false, $"*{FFmpegUtils.GetExt(i.current.outMode)}") < 1) + if (IOUtils.GetAmountOfFiles(chunksFolder, true, $"*{FFmpegUtils.GetExt(i.current.outMode)}") < 1) { i.Cancel("No video chunks found - An error must have occured during chunk encoding!", AiProcess.hasShownError); return; } + await Task.Delay(10); Program.mainForm.SetStatus("Merging video chunks..."); try { - int maxFps = Config.GetInt("maxFps"); - if (maxFps == 0) maxFps = 500; + DirectoryInfo chunksDir = new DirectoryInfo(chunksFolder); + foreach(DirectoryInfo dir in chunksDir.GetDirectories()) + { + string suffix = dir.Name.Replace("chunks", ""); + string tempConcatFile = Path.Combine(tempFolder, $"chunks-concat{suffix}.ini"); + string concatFileContent = ""; + foreach (string vid in IOUtils.GetFilesSorted(dir.FullName)) + concatFileContent += $"file '{Paths.chunksDir}/{dir.Name}/{Path.GetFileName(vid)}'\n"; + File.WriteAllText(tempConcatFile, concatFileContent); - if (i.current.outFps > maxFps) - await MergeChunks(chunksPath, vfrFile, outPath, i.current.outFps, maxFps, (Config.GetInt("maxFpsMode") == 1)); - else - await MergeChunks(chunksPath, vfrFile, outPath, i.current.outFps); + Logger.Log($"CreateVideo: Running MergeChunks() for vfrFile '{Path.GetFileName(tempConcatFile)}'", true); + await MergeChunks(tempConcatFile, baseOutPath.FilenameSuffix(suffix)); + } } catch (Exception e) { @@ -153,28 +151,14 @@ namespace Flowframes.Main } } - static async Task MergeChunks(string chunksPath, string vfrFile, string outPath, float fps, float changeFps = -1, bool keepOriginalFpsVid = true) + static async Task MergeChunks(string vfrFile, string outPath) { - int looptimes = GetLoopTimes(); - - bool h265 = Config.GetInt("mp4Enc") == 1; - int crf = h265 ? Config.GetInt("h265Crf") : Config.GetInt("h264Crf"); - - await FFmpegCommands.ConcatVideos(vfrFile, outPath, fps, -1); + await FFmpegCommands.ConcatVideos(vfrFile, outPath, -1); await MergeAudio(i.current.inPath, outPath); + int looptimes = GetLoopTimes(); if (looptimes > 0) await Loop(outPath, looptimes); - - if (changeFps > 0) - { - string newOutPath = IOUtils.FilenameSuffix(outPath, $"-{changeFps.ToString("0")}fps"); - Program.mainForm.SetStatus("Creating video with desired frame rate..."); - await FFmpegCommands.ConvertFramerate(outPath, newOutPath, h265, crf, changeFps, !keepOriginalFpsVid); - await MergeAudio(i.current.inPath, newOutPath); - if (looptimes > 0) - await Loop(newOutPath, looptimes); - } } public static async Task EncodeChunk(string outPath, i.OutMode mode, int firstFrameNum, int framesAmount) @@ -183,7 +167,21 @@ namespace Flowframes.Main string vfrFile = Path.Combine(i.current.tempFolder, $"vfr-chunk-{firstFrameNum}-{firstFrameNum + framesAmount}.ini"); File.WriteAllLines(vfrFile, IOUtils.ReadLines(vfrFileOriginal).Skip(firstFrameNum).Take(framesAmount)); - await FFmpegCommands.FramesToVideoConcat(vfrFile, outPath, mode, i.current.outFps, AvProcess.LogMode.Hidden, true); + float maxFps = Config.GetFloat("maxFps"); + bool fpsLimit = maxFps != 0 && i.current.outFps > maxFps; + + bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt("maxFpsMode") == 0; + + if(!dontEncodeFullFpsVid) + await FFmpegCommands.FramesToVideoConcat(vfrFile, outPath, mode, i.current.outFps, AvProcess.LogMode.Hidden, true); // Encode + + if (fpsLimit) + { + string filename = Path.GetFileName(outPath); + string newParentDir = outPath.GetParentDir() + "-" + maxFps.ToStringDot("0.00") + "fps"; + outPath = Path.Combine(newParentDir, filename); + await FFmpegCommands.FramesToVideoConcat(vfrFile, outPath, mode, i.current.outFps, maxFps, AvProcess.LogMode.Hidden, true); // Encode with limited fps + } } static async Task Loop(string outPath, int looptimes) diff --git a/Code/Main/InterpolateUtils.cs b/Code/Main/InterpolateUtils.cs index f22d9fc..d84031e 100644 --- a/Code/Main/InterpolateUtils.cs +++ b/Code/Main/InterpolateUtils.cs @@ -333,7 +333,7 @@ namespace Flowframes.Main public static bool UseAutoEnc (bool stepByStep, InterpSettings current) { - AutoEncode.UpdateSafetyBufferSize(); + AutoEncode.UpdateChunkAndBufferSizes(); if (!current.outMode.ToString().ToLower().Contains("vid")) { @@ -356,7 +356,7 @@ namespace Flowframes.Main int inFrames = IOUtils.GetAmountOfFiles(current.framesFolder, false); if (inFrames * current.interpFactor < (AutoEncode.chunkSize + AutoEncode.safetyBufferFrames) * 1.2f) { - Logger.Log($"Not Using AutoEnc: Input frames ({inFrames}) * factor ({current.interpFactor}) is smaller (chunkSize ({AutoEncode.chunkSize}) + safetyBufferFrames ({AutoEncode.safetyBufferFrames}) * 1.2f", true); + Logger.Log($"Not Using AutoEnc: Input frames ({inFrames}) * factor ({current.interpFactor}) is smaller than (chunkSize ({AutoEncode.chunkSize}) + safetyBufferFrames ({AutoEncode.safetyBufferFrames}) * 1.2f)", true); return false; }