diff --git a/Code/Form1.cs b/Code/Form1.cs index 16894cf..c9216d8 100644 --- a/Code/Form1.cs +++ b/Code/Form1.cs @@ -39,8 +39,6 @@ namespace Flowframes Application.Exit(); } - Text = $"Flowframes {Updater.GetInstalledVer()}"; - // Main Tab UIUtils.InitCombox(interpFactorCombox, 0); UIUtils.InitCombox(outModeCombox, 0); @@ -50,17 +48,14 @@ namespace Flowframes Program.mainForm = this; Logger.textbox = logBox; - NvApi.Init(); InitAis(); InterpolateUtils.preview = previewPicturebox; - UpdateStepByStepControls(); - Initialized(); Checks(); - HandleArguments(); + Text = $"Flowframes {Updater.GetInstalledVer()}"; } void Checks() @@ -204,9 +199,6 @@ namespace Flowframes private void browseOutBtn_Click(object sender, EventArgs e) { - Magick.SceneDetect.RunSceneDetection(inputTbox.Text.Trim()); - return; - CommonOpenFileDialog dialog = new CommonOpenFileDialog(); dialog.InitialDirectory = inputTbox.Text.Trim(); dialog.IsFolderPicker = true; diff --git a/Code/Magick/SceneDetect.cs b/Code/Magick/SceneDetect.cs index afca09f..2a9e7f6 100644 --- a/Code/Magick/SceneDetect.cs +++ b/Code/Magick/SceneDetect.cs @@ -49,8 +49,8 @@ namespace Flowframes.Magick static async Task ProcessFrame (FileInfo frame, FileInfo lastFrame, string outFolder) { - MagickImage prevFrame = GetImage(lastFrame.FullName); - MagickImage currFrame = GetImage(frame.FullName); + MagickImage prevFrame = GetImage(lastFrame.FullName, false); + MagickImage currFrame = GetImage(frame.FullName, false); Size originalSize = new Size(currFrame.Width, currFrame.Height); int downscaleHeight = 144; @@ -60,17 +60,16 @@ namespace Flowframes.Magick double errNormalizedCrossCorrelation = currFrame.Compare(prevFrame, ErrorMetric.NormalizedCrossCorrelation); double errRootMeanSquared = currFrame.Compare(prevFrame, ErrorMetric.RootMeanSquared); - string str = $"Metrics of {frame.Name.Split('.')[0]} against {lastFrame.Name.Split('.')[0]}:\n"; + string str = $"\nMetrics of {frame.Name.Split('.')[0]} against {lastFrame.Name.Split('.')[0]}:\n"; str += $"NormalizedCrossCorrelation: {errNormalizedCrossCorrelation.ToString("0.000")}\n"; str += $"RootMeanSquared: {errRootMeanSquared.ToString("0.000")}\n"; str += "\n\n"; + bool nccTrigger = errNormalizedCrossCorrelation < 0.45f; bool rMeanSqrTrigger = errRootMeanSquared > 0.18f; bool rmsNccTrigger = errRootMeanSquared > 0.18f && errNormalizedCrossCorrelation < 0.6f; bool nccRmsTrigger = errNormalizedCrossCorrelation < 0.45f && errRootMeanSquared > 0.11f; - // if (nccTrigger) str += "\nNCC SCENE CHANGE TRIGGER!"; - // if (rMeanSqrTrigger) str += "\nROOTMEANSQR SCENE CHANGE TRIGGER!"; if (rmsNccTrigger) str += "\n\nRMS -> NCC DOUBLE SCENE CHANGE TRIGGER!"; if (nccRmsTrigger) str += "\n\nNCC -> RMS DOUBLE SCENE CHANGE TRIGGER!"; diff --git a/Code/Main/AutoEncode.cs b/Code/Main/AutoEncode.cs index d9237f4..ac19a3c 100644 --- a/Code/Main/AutoEncode.cs +++ b/Code/Main/AutoEncode.cs @@ -74,7 +74,7 @@ namespace Flowframes.Main for (int vfrLine = lastEncodedFrameNum; vfrLine < interpFramesLines.Length; vfrLine++) unencodedFrameLines.Add(vfrLine); - bool aiRunning = !AiProcess.currentAiProcess.HasExited; + bool aiRunning = !AiProcess.lastAiProcess.HasExited; if (unencodedFrameLines.Count > 0 && (unencodedFrameLines.Count >= (chunkSize + safetyBufferFrames) || !aiRunning)) // Encode every n frames, or after process has exited { @@ -167,7 +167,7 @@ namespace Flowframes.Main { if (Interpolate.canceled || interpFramesFolder == null) return false; // Logger.Log($"HasWorkToDo - Process Running: {(AiProcess.currentAiProcess != null && !AiProcess.currentAiProcess.HasExited)} - encodedFrameLines.Count: {encodedFrameLines.Count} - interpFramesLines.Length: {interpFramesLines.Length}"); - return ((AiProcess.currentAiProcess != null && !AiProcess.currentAiProcess.HasExited) || encodedFrameLines.Count < interpFramesLines.Length); + return ((AiProcess.lastAiProcess != null && !AiProcess.lastAiProcess.HasExited) || encodedFrameLines.Count < interpFramesLines.Length); } static int GetChunkSize(int targetFramesAmount) diff --git a/Code/Main/Interpolate.cs b/Code/Main/Interpolate.cs index 19b629d..f95f349 100644 --- a/Code/Main/Interpolate.cs +++ b/Code/Main/Interpolate.cs @@ -69,25 +69,24 @@ namespace Flowframes Program.mainForm.SetStatus("Done interpolating!"); } - public static async Task GetFrames (bool stepByStep = false) + public static async Task GetFrames () { current.RefreshAlpha(); + if (Config.GetBool("scnDetect")) + { + Program.mainForm.SetStatus("Extracting scenes from video..."); + await FfmpegExtract.ExtractSceneChanges(current.inPath, Path.Combine(current.tempFolder, Paths.scenesDir), current.inFps, current.inputIsFrames); + } + if (!current.inputIsFrames) // Extract if input is video, import if image sequence - await ExtractFrames(current.inPath, current.framesFolder, current.alpha, !stepByStep); + await ExtractFrames(current.inPath, current.framesFolder, current.alpha); else await FfmpegExtract.ImportImages(current.inPath, current.framesFolder, current.alpha, await Utils.GetOutputResolution(current.inPath, true)); } - public static async Task ExtractFrames(string inPath, string outPath, bool alpha, bool sceneDetect) + public static async Task ExtractFrames(string inPath, string outPath, bool alpha) { - if (sceneDetect && Config.GetBool("scnDetect")) - { - Program.mainForm.SetStatus("Extracting scenes from video..."); - await FfmpegExtract.ExtractSceneChanges(inPath, Path.Combine(current.tempFolder, Paths.scenesDir), current.inFps); - await Task.Delay(10); - } - if (canceled) return; Program.mainForm.SetStatus("Extracting frames from video..."); bool mpdecimate = Config.GetInt("dedupMode") == 2; @@ -208,16 +207,11 @@ namespace Flowframes public static void Cancel(string reason = "", bool noMsgBox = false) { - try - { - OSUtils.KillProcessTree(AiProcess.currentAiProcess.Id); - OSUtils.KillProcessTree(AvProcess.lastProcess.Id); - } - catch { } - canceled = true; Program.mainForm.SetStatus("Canceled."); Program.mainForm.SetProgress(0); + AiProcess.Kill(); + AvProcess.Kill(); if (!current.stepByStep && !Config.GetBool("keepTempFolder")) { diff --git a/Code/Main/InterpolateSteps.cs b/Code/Main/InterpolateSteps.cs index 0ec4810..5c58b83 100644 --- a/Code/Main/InterpolateSteps.cs +++ b/Code/Main/InterpolateSteps.cs @@ -45,25 +45,10 @@ namespace Flowframes.Main Logger.Log("Done running this step."); } - public static async Task ExtractSceneChanges() - { - string scenesPath = Path.Combine(current.tempFolder, Paths.scenesDir); - - if (!IOUtils.TryDeleteIfExists(scenesPath)) - { - InterpolateUtils.ShowMessage("Failed to delete existing scenes folder - Make sure no file is opened in another program!", "Error"); - return; - } - - Program.mainForm.SetStatus("Extracting scenes from video..."); - await FfmpegExtract.ExtractSceneChanges(current.inPath, scenesPath, current.inFps); - await Task.Delay(10); - } - public static async Task ExtractFramesStep() { - if (Config.GetBool("scnDetect") && !current.inputIsFrames) // Input is video - extract frames first - await ExtractSceneChanges(); + // if (Config.GetBool("scnDetect") && !current.inputIsFrames) // Input is video - extract frames first + // await ExtractSceneChanges(); if (!IOUtils.TryDeleteIfExists(current.framesFolder)) { @@ -74,7 +59,7 @@ namespace Flowframes.Main currentInputFrameCount = await InterpolateUtils.GetInputFrameCountAsync(current.inPath); AiProcess.filenameMap.Clear(); - await GetFrames(true); + await GetFrames(); await PostProcessFrames(true); } diff --git a/Code/Media/AvProcess.cs b/Code/Media/AvProcess.cs index 59dc18a..689fb82 100644 --- a/Code/Media/AvProcess.cs +++ b/Code/Media/AvProcess.cs @@ -14,7 +14,7 @@ namespace Flowframes { class AvProcess { - public static Process lastProcess; + public static Process lastAvProcess; public static Stopwatch timeSinceLastOutput = new Stopwatch(); public enum TaskType { ExtractFrames, ExtractOther, Encode, GetInfo, Merge, Other }; public static TaskType lastTask = TaskType.Other; @@ -28,6 +28,20 @@ namespace Flowframes static string defLogLevel = "warning"; + public static void Kill() + { + if (lastAvProcess == null) return; + + try + { + OSUtils.KillProcessTree(lastAvProcess.Id); + } + catch (Exception e) + { + Logger.Log($"Failed to kill lastAvProcess process tree: {e.Message}", true); + } + } + public static async Task RunFfmpeg(string args, LogMode logMode, TaskType taskType = TaskType.Other, bool progressBar = false) { await RunFfmpeg(args, "", logMode, defLogLevel, taskType, progressBar); @@ -50,7 +64,7 @@ namespace Flowframes showProgressBar = progressBar; Process ffmpeg = OSUtils.NewProcess(true); timeSinceLastOutput.Restart(); - lastProcess = ffmpeg; + lastAvProcess = ffmpeg; lastTask = taskType; if (string.IsNullOrWhiteSpace(loglevel)) @@ -126,7 +140,7 @@ namespace Flowframes public static string GetFfmpegOutput (string args) { Process ffmpeg = OSUtils.NewProcess(true); - lastProcess = ffmpeg; + lastAvProcess = ffmpeg; ffmpeg.StartInfo.Arguments = $"{GetCmdArg()} cd /D {GetAvDir().Wrap()} & ffmpeg.exe -hide_banner -y -stats {args}"; Logger.Log("cmd.exe " + ffmpeg.StartInfo.Arguments, true, false, "ffmpeg"); ffmpeg.Start(); @@ -144,7 +158,7 @@ namespace Flowframes lastOutputFfmpeg = ""; showProgressBar = progressBar; Process ffmpeg = OSUtils.NewProcess(true); - lastProcess = ffmpeg; + lastAvProcess = ffmpeg; ffmpeg.StartInfo.Arguments = $"{GetCmdArg()} cd /D {GetAvDir().Wrap()} & ffmpeg.exe -hide_banner -y -stats {args}"; Logger.Log("cmd.exe " + ffmpeg.StartInfo.Arguments, true, false, "ffmpeg"); if (setBusy) Program.mainForm.SetWorking(true); @@ -204,7 +218,7 @@ namespace Flowframes if (Program.busy) return; await Task.Delay(100); - while(!lastProcess.HasExited) + while(!lastAvProcess.HasExited) await Task.Delay(10); } } diff --git a/Code/Media/FfmpegExtract.cs b/Code/Media/FfmpegExtract.cs index d4f2ae7..5539cab 100644 --- a/Code/Media/FfmpegExtract.cs +++ b/Code/Media/FfmpegExtract.cs @@ -16,29 +16,40 @@ namespace Flowframes.Media { partial class FfmpegExtract : FfmpegCommands { - public static async Task ExtractSceneChanges(string inputFile, string frameFolderPath, float rate) + public static async Task ExtractSceneChanges(string inPath, string outDir, float rate, bool inputIsFrames = false) { Logger.Log("Extracting scene changes..."); - await VideoToFrames(inputFile, frameFolderPath, false, rate, false, false, new Size(320, 180), true); + Directory.CreateDirectory(outDir); + + string inArg = $"-i {inPath.Wrap()}"; + + if (inputIsFrames) + { + string concatFile = Path.Combine(Paths.GetDataPath(), "png-scndetect-concat-temp.ini"); + GetConcatFile(inPath, concatFile); + inArg = $"-f concat -safe 0 -i {concatFile.Wrap()}"; + } + + string scnDetect = $"-vf \"select='gt(scene,{Config.GetFloatString("scnDetectValue")})'\""; + string rateArg = (rate > 0) ? $"-r {rate.ToStringDot()}" : ""; + string args = $"{rateArg} -vsync 0 {GetTrimArg(true)} {inArg} {compr} {scnDetect} -frame_pts true -s 256x144 {GetTrimArg(false)} \"{outDir}/%{Padding.inputFrames}d.png\""; + + LogMode logMode = Interpolate.currentInputFrameCount > 50 ? LogMode.OnlyLastLine : LogMode.Hidden; + await RunFfmpeg(args, logMode, inputIsFrames ? "panic" : "warning", TaskType.ExtractFrames, true); + bool hiddenLog = Interpolate.currentInputFrameCount <= 50; - int amount = IOUtils.GetAmountOfFiles(frameFolderPath, false); + int amount = IOUtils.GetAmountOfFiles(outDir, false); Logger.Log($"Detected {amount} scene {(amount == 1 ? "change" : "changes")}.".Replace(" 0 ", " no "), false, !hiddenLog); } - public static async Task VideoToFrames(string inputFile, string framesDir, bool alpha, float rate, bool deDupe, bool delSrc) + public static async Task VideoToFrames(string inputFile, string framesDir, bool alpha, float rate, bool deDupe, bool delSrc, Size size) { - await VideoToFrames(inputFile, framesDir, alpha, rate, deDupe, delSrc, new Size()); - } - - public static async Task VideoToFrames(string inputFile, string framesDir, bool alpha, float rate, bool deDupe, bool delSrc, Size size, bool sceneDetect = false) - { - if (!sceneDetect) Logger.Log("Extracting video frames from input video..."); + Logger.Log("Extracting video frames from input video..."); string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : ""; IOUtils.CreateDir(framesDir); string timecodeStr = /* timecodes ? $"-copyts -r {FrameOrder.timebase} -frame_pts true" : */ "-frame_pts true"; - string scnDetect = sceneDetect ? $"\"select='gt(scene,{Config.GetFloatString("scnDetectValue")})'\"" : ""; string mpStr = deDupe ? ((Config.GetInt("mpdecimateMode") == 0) ? mpDecDef : mpDecAggr) : ""; - string filters = FormatUtils.ConcatStrings(new string[] { divisionFilter, scnDetect, mpStr }); + string filters = FormatUtils.ConcatStrings(new string[] { divisionFilter, mpStr }); string vf = filters.Length > 2 ? $"-vf {filters}" : ""; string rateArg = (rate > 0) ? $" -r {rate.ToStringDot()}" : ""; string pixFmt = alpha ? "-pix_fmt rgba" : "-pix_fmt rgb24"; // Use RGBA for GIF for alpha support @@ -46,7 +57,7 @@ namespace Flowframes.Media LogMode logMode = Interpolate.currentInputFrameCount > 50 ? LogMode.OnlyLastLine : LogMode.Hidden; await RunFfmpeg(args, logMode, TaskType.ExtractFrames, true); int amount = IOUtils.GetAmountOfFiles(framesDir, false, "*.png"); - if (!sceneDetect) Logger.Log($"Extracted {amount} {(amount == 1 ? "frame" : "frames")} from input.", false, true); + Logger.Log($"Extracted {amount} {(amount == 1 ? "frame" : "frames")} from input.", false, true); await Task.Delay(1); if (delSrc) DeleteSource(inputFile); @@ -58,11 +69,7 @@ namespace Flowframes.Media Logger.Log($"Importing images from {inpath} to {outpath}.", true); IOUtils.CreateDir(outpath); string concatFile = Path.Combine(Paths.GetDataPath(), "png-concat-temp.ini"); - string concatFileContent = ""; - string[] files = IOUtils.GetFilesSorted(inpath); - foreach (string img in files) - concatFileContent += $"file '{img.Replace(@"\", "/")}'\n"; - File.WriteAllText(concatFile, concatFileContent); + GetConcatFile(inpath, concatFile); string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : ""; string pixFmt = alpha ? "-pix_fmt rgba" : "-pix_fmt rgb24"; // Use RGBA for GIF for alpha support @@ -74,6 +81,17 @@ namespace Flowframes.Media DeleteSource(inpath); } + public static void GetConcatFile (string inputFilesDir, string concatFilePath) + { + string concatFileContent = ""; + string[] files = IOUtils.GetFilesSorted(inputFilesDir); + + foreach (string img in files) + concatFileContent += $"file '{img.Replace(@"\", "/")}'\n"; + + File.WriteAllText(concatFilePath, concatFileContent); + } + public static string[] GetTrimArgs() { return new string[] { GetTrimArg(true), GetTrimArg(false) }; diff --git a/Code/OS/AiProcess.cs b/Code/OS/AiProcess.cs index 669abdc..3aa44be 100644 --- a/Code/OS/AiProcess.cs +++ b/Code/OS/AiProcess.cs @@ -22,7 +22,7 @@ namespace Flowframes { public static bool hasShownError; - public static Process currentAiProcess; + public static Process lastAiProcess; public static Stopwatch processTime = new Stopwatch(); public static Stopwatch processTimeMulti = new Stopwatch(); @@ -31,11 +31,25 @@ namespace Flowframes public static Dictionary filenameMap = new Dictionary(); // TODO: Store on disk instead for crashes? + public static void Kill () + { + if (lastAiProcess == null) return; + + try + { + OSUtils.KillProcessTree(lastAiProcess.Id); + } + catch (Exception e) + { + Logger.Log($"Failed to kill currentAiProcess process tree: {e.Message}", true); + } + } + static void AiStarted (Process proc, int startupTimeMs, string inPath = "") { lastStartupTimeMs = startupTimeMs; processTime.Restart(); - currentAiProcess = proc; + lastAiProcess = proc; lastInPath = string.IsNullOrWhiteSpace(inPath) ? Interpolate.current.framesFolder : inPath; hasShownError = false; } @@ -66,7 +80,7 @@ namespace Flowframes while (Interpolate.currentlyUsingAutoEnc && Program.busy) { - if (AvProcess.lastProcess != null && !AvProcess.lastProcess.HasExited && AvProcess.lastTask == AvProcess.TaskType.Encode) + if (AvProcess.lastAvProcess != null && !AvProcess.lastAvProcess.HasExited && AvProcess.lastTask == AvProcess.TaskType.Encode) { string lastLine = AvProcess.lastOutputFfmpeg.SplitIntoLines().Last(); Logger.Log(lastLine.Trim().TrimWhitespaces(), false, Logger.GetLastLine().Contains("frame"));