From 3125670bb097c719bca15630f2174a2df9428500 Mon Sep 17 00:00:00 2001 From: N00MKRAD Date: Mon, 7 Dec 2020 00:41:07 +0100 Subject: [PATCH] ffmpeg dupes workaround, SBS ReverseRenaming fix --- Code/AudioVideo/FFmpegCommands.cs | 24 ++++++++++++++++++------ Code/Main/CreateVideo.cs | 4 ++-- Code/Main/Interpolate.cs | 13 +++++++++---- Code/Main/InterpolateSteps.cs | 8 +++++--- Code/Main/InterpolateUtils.cs | 8 ++++++++ Code/UI/MainUiFunctions.cs | 4 ++-- 6 files changed, 44 insertions(+), 17 deletions(-) diff --git a/Code/AudioVideo/FFmpegCommands.cs b/Code/AudioVideo/FFmpegCommands.cs index b3cbee6..822f638 100644 --- a/Code/AudioVideo/FFmpegCommands.cs +++ b/Code/AudioVideo/FFmpegCommands.cs @@ -95,26 +95,38 @@ namespace Flowframes DeleteSource(inputDir); } - public static async Task FramesToMp4Vfr(string framesFile, string outPath, bool useH265, int crf, float fps, int looptimes = -1) + public static async Task FramesToMp4Vfr(string framesFile, string outPath, bool useH265, int crf, float fps, bool inRate) { Logger.Log($"Encoding MP4 video with CRF {crf}..."); string enc = useH265 ? "libx265" : "libx264"; - string loopStr = (looptimes > 0) ? $"-stream_loop {looptimes}" : ""; string presetStr = $"-preset {Config.Get("ffEncPreset")}"; string vsyncStr = Config.GetInt("vfrMode") == 0 ? "-vsync 1" : "-vsync 2"; string vfrFilename = Path.GetFileName(framesFile); - string args = $" {loopStr} {vsyncStr} -f concat -r {fps.ToString().Replace(",", ".")} -i {vfrFilename} -c:v {enc} -crf {crf} {presetStr} {videoEncArgs} -threads {Config.GetInt("ffEncThreads")} -c:a copy {outPath.Wrap()}"; + + string args = $" {vsyncStr} -f concat "; + if (inRate) + args += $"-r {fps.ToString().Replace(",", ".")} -i {vfrFilename} "; + else + args += $"-i {vfrFilename} -r {fps.ToString().Replace(",", ".")} "; + + args += $"-c:v {enc} -crf {crf} {presetStr} {videoEncArgs} -threads {Config.GetInt("ffEncThreads")} -c:a copy {outPath.Wrap()}"; await AvProcess.RunFfmpeg(args, framesFile.GetParentDir(), AvProcess.LogMode.OnlyLastLine); } - public static async Task FramesToMp4VfrChunk(string framesFile, string outPath, bool useH265, int crf, float fps) + public static async Task FramesToMp4VfrChunk(string framesFile, string outPath, bool useH265, int crf, float fps, bool inRate) { - //Logger.Log($"Encoding MP4 chunk with CRF {crf}..."); string enc = useH265 ? "libx265" : "libx264"; string presetStr = $"-preset {Config.Get("ffEncPreset")}"; string vsyncStr = Config.GetInt("vfrMode") == 0 ? "-vsync 1" : "-vsync 2"; string vfrFilename = Path.GetFileName(framesFile); - string args = $" {vsyncStr} -f concat -r {fps.ToString().Replace(",", ".")} -i {vfrFilename} -c:v {enc} -crf {crf} {presetStr} {videoEncArgs} -threads {Config.GetInt("ffEncThreads")} -c:a copy {outPath.Wrap()}"; + + string args = $" {vsyncStr} -f concat "; + if (inRate) + args += $"-r {fps.ToString().Replace(",", ".")} -i {vfrFilename} "; + else + args += $"-i {vfrFilename} -r {fps.ToString().Replace(",", ".")} "; + + args += $"-c:v {enc} -crf {crf} {presetStr} {videoEncArgs} -threads {Config.GetInt("ffEncThreads")} -c:a copy {outPath.Wrap()}"; await AvProcess.RunFfmpeg(args, framesFile.GetParentDir(), AvProcess.LogMode.Hidden); } diff --git a/Code/Main/CreateVideo.cs b/Code/Main/CreateVideo.cs index 1e47958..cfa2491 100644 --- a/Code/Main/CreateVideo.cs +++ b/Code/Main/CreateVideo.cs @@ -84,7 +84,7 @@ namespace Flowframes.Main int crf = h265 ? Config.GetInt("h265Crf") : Config.GetInt("h264Crf"); string vfrFile = Path.Combine(framesPath.GetParentDir(), $"vfr-x{i.lastInterpFactor}.ini"); - await FFmpegCommands.FramesToMp4Vfr(vfrFile, outPath, h265, crf, fps, -1); + await FFmpegCommands.FramesToMp4Vfr(vfrFile, outPath, h265, crf, fps, i.constantFrameRate); /* DELETE THIS AS SOON AS I'M SURE I CAN USE VFR WITH TIMING DISABLED if (Config.GetInt("timingMode") == 1 && Config.GetInt("dedupMode") != 0) @@ -174,7 +174,7 @@ namespace Flowframes.Main string vfrFile = Path.Combine(i.currentTempDir, $"vfr-chunk-temp.ini"); File.WriteAllLines(vfrFile, IOUtils.ReadLines(vfrFileOriginal).Skip(firstFrameNum * 2).Take(framesAmount * 2)); - await FFmpegCommands.FramesToMp4VfrChunk(vfrFile, outPath, h265, crf, i.currentOutFps); + await FFmpegCommands.FramesToMp4VfrChunk(vfrFile, outPath, h265, crf, i.currentOutFps, i.constantFrameRate); IOUtils.TryDeleteIfExists(vfrFile); } diff --git a/Code/Main/Interpolate.cs b/Code/Main/Interpolate.cs index 0addc32..67f9aa7 100644 --- a/Code/Main/Interpolate.cs +++ b/Code/Main/Interpolate.cs @@ -29,6 +29,7 @@ namespace Flowframes public static float currentInFps; public static float currentOutFps; public static int currentInputFrameCount; + public static bool constantFrameRate; public static OutMode currentOutMode; public static bool currentInputIsFrames; public static bool currentlyUsingAutoEnc; @@ -63,6 +64,7 @@ namespace Flowframes Utils.PathAsciiCheck(inPath, outDir); lastAi = ai; currentInputIsFrames = IOUtils.IsPathDirectory(inPath); + currentInputFrameCount = Utils.GetInputFrameCount(inPath); Program.mainForm.SetStatus("Starting..."); Program.mainForm.SetWorking(true); await Task.Delay(10); @@ -144,16 +146,19 @@ namespace Flowframes public static async Task PostProcessFrames (bool sbsMode = false) { - currentInputFrameCount = IOUtils.GetAmountOfFiles(currentFramesPath, false, "*.png"); - - if (!Directory.Exists(currentFramesPath) || currentInputFrameCount <= 0) - Cancel("Input frames folder is empty!"); + if (!Directory.Exists(currentFramesPath) || currentInputFrameCount <= 0 || IOUtils.GetAmountOfFiles(currentFramesPath, false, "*.png") < 2) + Cancel("Extracted frames folder is empty!"); if (Config.GetInt("dedupMode") == 1) await MagickDedupe.Run(currentFramesPath); else MagickDedupe.ClearCache(); + int frameCountAfterDedupe = IOUtils.GetAmountOfFiles(currentFramesPath, false, "*.png"); + int dupesPercent = 100 - (((float)frameCountAfterDedupe / currentInputFrameCount) * 100f).RoundToInt(); + constantFrameRate = dupesPercent < 5f; // Ignore VFR timings for CFR input. TODO: Figure out how to avoid dupes when using VFR timings + Logger.Log($"{dupesPercent}% of frames are dupes, so constantFrameRate = {constantFrameRate}"); + if (canceled) return; bool useTimestamps = Config.GetInt("timingMode") == 1; // TODO: Auto-Disable timestamps if input frames are sequential, not timestamped diff --git a/Code/Main/InterpolateSteps.cs b/Code/Main/InterpolateSteps.cs index 5663942..f9eb642 100644 --- a/Code/Main/InterpolateSteps.cs +++ b/Code/Main/InterpolateSteps.cs @@ -71,9 +71,7 @@ namespace Flowframes.Main currentOutPath = e.outPath; currentTempDir = InterpolateUtils.GetTempFolderLoc(currentInPath, currentOutPath); currentFramesPath = Path.Combine(currentTempDir, Paths.framesDir); - currentInterpFramesDir = Path.Combine(currentTempDir, Paths.interpDir); - currentInputIsFrames = IOUtils.IsPathDirectory(currentInPath); } catch @@ -103,6 +101,7 @@ namespace Flowframes.Main InterpolateUtils.ShowMessage("Failed to delete existing frames folder - Make sure no file is opened in another program!", "Error"); return; } + AiProcess.filenameMap.Clear(); bool extractAudio = true; Program.mainForm.SetStatus("Extracting frames from video..."); Size resolution = IOUtils.GetVideoRes(currentInPath); @@ -138,7 +137,7 @@ namespace Flowframes.Main public static async Task DoInterpolate() { currentFramesPath = Path.Combine(currentTempDir, Paths.framesDir); - if (!Directory.Exists(currentFramesPath)) + if (!Directory.Exists(currentFramesPath) || IOUtils.GetAmountOfFiles(currentFramesPath, false, "*.png") < 2) { InterpolateUtils.ShowMessage("There are no extracted frames that can be interpolated!\nDid you run the extraction step?", "Error"); return; @@ -149,10 +148,13 @@ namespace Flowframes.Main return; } + currentInputFrameCount = InterpolateUtils.GetInputFrameCount(currentInPath); + foreach (string ini in Directory.GetFiles(currentTempDir, "*.ini", SearchOption.TopDirectoryOnly)) IOUtils.TryDeleteIfExists(ini); IOUtils.ReverseRenaming(AiProcess.filenameMap, true); // Get timestamps back + await PostProcessFrames(true); lastInterpFactor = interpFactor; diff --git a/Code/Main/InterpolateUtils.cs b/Code/Main/InterpolateUtils.cs index f8453fc..6fb22d7 100644 --- a/Code/Main/InterpolateUtils.cs +++ b/Code/Main/InterpolateUtils.cs @@ -64,6 +64,14 @@ namespace Flowframes.Main bigPreviewForm.SetImage(img); } + public static int GetInputFrameCount(string path) + { + if (IOUtils.IsPathDirectory(path)) + return IOUtils.GetAmountOfFiles(path, false); + else + return FFmpegCommands.GetFrameCount(path); + } + public static int GetProgressWaitTime(int numFrames) { float hddMultiplier = 2f; diff --git a/Code/UI/MainUiFunctions.cs b/Code/UI/MainUiFunctions.cs index 2832bce..063ffb1 100644 --- a/Code/UI/MainUiFunctions.cs +++ b/Code/UI/MainUiFunctions.cs @@ -33,9 +33,9 @@ namespace Flowframes.UI if (!Program.lastInputPathIsSsd) Logger.Log("Your file seems to be on an HDD or USB device. It is recommended to interpolate videos on an SSD drive for best performance."); if (IOUtils.IsPathDirectory(path)) - Logger.Log($"Video FPS (Loaded from fps.ini): {fpsStr} - Total Number Of Frames: {IOUtils.GetAmountOfFiles(path, false)}"); + Logger.Log($"Video FPS (Loaded from fps.ini): {fpsStr} - Total Number Of Frames: {InterpolateUtils.GetInputFrameCount(path)}"); else - Logger.Log($"Video FPS: {fpsStr} - Total Number Of Frames: {FFmpegCommands.GetFrameCount(path)}"); + Logger.Log($"Video FPS: {fpsStr} - Total Number Of Frames: {InterpolateUtils.GetInputFrameCount(path)}"); await Task.Delay(10); await PrintResolution(path); MagickDedupe.ClearCache();