diff --git a/Code/Form1.cs b/Code/Form1.cs index 732488b..69e3f79 100644 --- a/Code/Form1.cs +++ b/Code/Form1.cs @@ -224,7 +224,7 @@ namespace Flowframes { UpdateOutputFPS(); int guiInterpFactor = interpFactorCombox.GetInt(); - if (guiInterpFactor > 2 && !GetAi().supportsAnyExp && Config.GetInt("autoEncMode") > 0) + if (!Program.busy && guiInterpFactor > 2 && !GetAi().supportsAnyExp && Config.GetInt("autoEncMode") > 0) Logger.Log($"Warning: {GetAi().aiName.Replace("_", "-")} doesn't natively support 4x/8x and will run multiple times for {guiInterpFactor}x. Auto-Encode will only work on the last run."); } diff --git a/Code/IO/IOUtils.cs b/Code/IO/IOUtils.cs index 7fb3e3a..698c461 100644 --- a/Code/IO/IOUtils.cs +++ b/Code/IO/IOUtils.cs @@ -500,6 +500,25 @@ namespace Flowframes.IO } } + public static bool MoveTo(string file, string targetFolder, bool overwrite = true) + { + string targetPath = Path.Combine(targetFolder, Path.GetFileName(file)); + try + { + if (!Directory.Exists(targetFolder)) + Directory.CreateDirectory(targetFolder); + if (overwrite) + DeleteIfExists(targetPath); + File.Move(file, targetPath); + return true; + } + catch (Exception e) + { + Logger.Log($"Failed to move {file} to {targetFolder}: {e.Message}"); + return false; + } + } + public enum Hash { MD5, CRC32 } public static string GetHash (string filename, Hash hashType) { @@ -539,7 +558,7 @@ namespace Flowframes.IO ZeroPadDir(files.Select(x => x.FullName).ToList(), targetLength); } - public static void ZeroPadDir(List files, int targetLength, List exclude = null) + public static void ZeroPadDir(List files, int targetLength, List exclude = null, bool noLog = true) { if(exclude != null) files = files.Except(exclude).ToList(); @@ -555,7 +574,8 @@ namespace Flowframes.IO } catch (Exception e) { - Logger.Log($"Failed to zero-pad {file} => {targetFilename}: {e.Message}", true); + if(!noLog) + Logger.Log($"Failed to zero-pad {file} => {targetFilename}: {e.Message}", true); } } } diff --git a/Code/Magick/MagickDedupe.cs b/Code/Magick/MagickDedupe.cs index 0bfabe8..f14d295 100644 --- a/Code/Magick/MagickDedupe.cs +++ b/Code/Magick/MagickDedupe.cs @@ -214,7 +214,7 @@ namespace Flowframes.Magick if (currentMode == Mode.None) return; - string ext = InterpolateUtils.GetExt(); + string ext = InterpolateUtils.GetOutExt(); string dupeInfoFile = Path.Combine(Interpolate.current.tempFolder, "dupes.ini"); if (!File.Exists(dupeInfoFile)) return; diff --git a/Code/Main/AutoEncode.cs b/Code/Main/AutoEncode.cs index 425096e..0ab0116 100644 --- a/Code/Main/AutoEncode.cs +++ b/Code/Main/AutoEncode.cs @@ -50,8 +50,8 @@ namespace Flowframes.Main continue; } - IOUtils.ZeroPadDir(Directory.GetFiles(interpFramesFolder, $"*.{InterpolateUtils.GetExt()}").ToList(), Padding.interpFrames, encodedFrames); - string[] interpFrames = Directory.GetFiles(interpFramesFolder, $"*.{InterpolateUtils.GetExt()}"); + IOUtils.ZeroPadDir(Directory.GetFiles(interpFramesFolder, $"*.{InterpolateUtils.GetOutExt()}").ToList(), Padding.interpFrames, encodedFrames); + string[] interpFrames = Directory.GetFiles(interpFramesFolder, $"*.{InterpolateUtils.GetOutExt()}"); unencodedFrames = interpFrames.ToList().Except(encodedFrames).ToList(); Directory.CreateDirectory(videoChunksFolder); @@ -117,7 +117,7 @@ namespace Flowframes.Main static int GetInterpFramesAmount() { - return IOUtils.GetAmountOfFiles(interpFramesFolder, false, $"*.{InterpolateUtils.GetExt()}"); + return IOUtils.GetAmountOfFiles(interpFramesFolder, false, $"*.{InterpolateUtils.GetOutExt()}"); } } } diff --git a/Code/Main/CreateVideo.cs b/Code/Main/CreateVideo.cs index 331df16..7b396c6 100644 --- a/Code/Main/CreateVideo.cs +++ b/Code/Main/CreateVideo.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using System.Windows.Forms; using Padding = Flowframes.Data.Padding; using i = Flowframes.Interpolate; +using System.Diagnostics; namespace Flowframes.Main { @@ -26,11 +27,7 @@ namespace Flowframes.Main { try { - Logger.Log("Moving interpolated frames out of temp folder..."); - string copyPath = Path.Combine(i.current.tempFolder.ReplaceLast("-temp", "-interpolated")); - Logger.Log($"{path} -> {copyPath}"); - IOUtils.CreateDir(copyPath); - IOUtils.Copy(path, copyPath, true); + await CopyOutputFrames(path, Path.GetFileNameWithoutExtension(outPath)); } catch(Exception e) { @@ -39,7 +36,7 @@ namespace Flowframes.Main return; } - if (IOUtils.GetAmountOfFiles(path, false, $"*.{InterpolateUtils.GetExt()}") <= 1) + if (IOUtils.GetAmountOfFiles(path, false, $"*.{InterpolateUtils.GetOutExt()}") <= 1) { i.Cancel("Output folder does not contain frames - An error must have occured during interpolation!", AiProcess.hasShownError); return; @@ -63,6 +60,44 @@ namespace Flowframes.Main } } + static async Task CopyOutputFrames (string framesPath, string folderName) + { + Program.mainForm.SetStatus("Copying output frames..."); + string copyPath = Path.Combine(i.current.outPath, folderName); + Logger.Log($"Copying interpolated frames to '{copyPath}'"); + IOUtils.TryDeleteIfExists(copyPath); + IOUtils.CreateDir(copyPath); + Stopwatch sw = new Stopwatch(); + sw.Restart(); + + string vfrFile = Path.Combine(framesPath.GetParentDir(), $"vfr-{i.current.interpFactor}x.ini"); + string[] vfrLines = IOUtils.ReadLines(vfrFile); + string currentInterpExt = $".{InterpolateUtils.GetOutExt()}"; + vfrLines = vfrLines.Where(x => x.Contains(currentInterpExt)).ToArray(); // Remove duration lines, leaving only filename lines + + for (int idx = 1; idx <= vfrLines.Length; idx++) + { + string line = vfrLines[idx-1]; + string inFilename = line.Split('/').Last().Remove("'"); + string framePath = Path.Combine(framesPath, inFilename); + string outFilename = Path.Combine(copyPath, idx.ToString().PadLeft(Padding.interpFrames, '0')) + InterpolateUtils.GetOutExt(true); + + if ((idx < vfrLines.Length) && vfrLines[idx].Contains(inFilename)) // If file is re-used in the next line, copy instead of move + File.Copy(framePath, outFilename); + else + File.Move(framePath, outFilename); + + if (sw.ElapsedMilliseconds >= 1000 || idx == vfrLines.Length) + { + sw.Restart(); + Logger.Log($"Copying interpolated frames to '{Path.GetFileName(copyPath)}' - {idx}/{vfrLines.Length}", false, true); + await Task.Delay(1); + } + } + + //IOUtils.ZeroPadDir(copyPath, "*", Padding.interpFrames); + } + static async Task Encode(i.OutMode mode, string framesPath, string outPath, float fps, float changeFps = -1, bool keepOriginalFpsVid = true) { currentOutFile = outPath; diff --git a/Code/Main/FrameTiming.cs b/Code/Main/FrameTiming.cs index 58f326a..57c6c92 100644 --- a/Code/Main/FrameTiming.cs +++ b/Code/Main/FrameTiming.cs @@ -30,7 +30,7 @@ namespace Flowframes.Main Logger.Log("Timestamps are disabled, using static frame rate."); bool sceneDetection = true; - string ext = InterpolateUtils.GetExt(); + string ext = InterpolateUtils.GetOutExt(); FileInfo[] frameFiles = new DirectoryInfo(framesPath).GetFiles($"*.png"); string vfrFile = Path.Combine(framesPath.GetParentDir(), $"vfr-{interpFactor}x.ini"); diff --git a/Code/Main/Interpolate.cs b/Code/Main/Interpolate.cs index 22b24a4..99587b5 100644 --- a/Code/Main/Interpolate.cs +++ b/Code/Main/Interpolate.cs @@ -53,19 +53,18 @@ namespace Flowframes await Task.Delay(10); await PostProcessFrames(); if (canceled) return; - string interpFramesDir = Path.Combine(current.tempFolder, Paths.interpDir); int frames = IOUtils.GetAmountOfFiles(current.framesFolder, false, "*.png"); int targetFrameCount = frames * current.interpFactor; - GetProgressByFrameAmount(interpFramesDir, targetFrameCount); + Utils.GetProgressByFrameAmount(current.interpFolder, targetFrameCount); if (canceled) return; Program.mainForm.SetStatus("Running AI..."); - await RunAi(interpFramesDir, targetFrameCount, current.tilesize, current.ai); + await RunAi(current.interpFolder, targetFrameCount, current.tilesize, current.ai); if (canceled) return; Program.mainForm.SetProgress(100); if(!currentlyUsingAutoEnc) - await CreateVideo.Export(interpFramesDir, current.outFilename, current.outMode); + await CreateVideo.Export(current.interpFolder, current.outFilename, current.outMode); IOUtils.ReverseRenaming(AiProcess.filenameMap, true); // Get timestamps back - Cleanup(interpFramesDir); + Cleanup(current.interpFolder); Program.mainForm.SetWorking(false); Logger.Log("Total processing time: " + FormatUtils.Time(sw.Elapsed)); sw.Stop(); @@ -161,30 +160,6 @@ namespace Flowframes await Task.WhenAll(tasks); } - public static async void GetProgressByFrameAmount(string outdir, int target) - { - bool firstProgUpd = true; - Program.mainForm.SetProgress(0); - while (Program.busy) - { - if (AiProcess.processTime.IsRunning && Directory.Exists(outdir)) - { - if (firstProgUpd && Program.mainForm.IsInFocus()) - Program.mainForm.SetTab("preview"); - firstProgUpd = false; - string[] frames = Directory.GetFiles(outdir, $"*.{Utils.GetExt()}"); - if (frames.Length > 1) - Utils.UpdateInterpProgress(frames.Length, target, frames[frames.Length - 1]); - await Task.Delay(Utils.GetProgressWaitTime(frames.Length)); - } - else - { - await Task.Delay(200); - } - } - Program.mainForm.SetProgress(-1); - } - public static void Cancel(string reason = "", bool noMsgBox = false) { if (AiProcess.currentAiProcess != null && !AiProcess.currentAiProcess.HasExited) @@ -196,6 +171,7 @@ namespace Flowframes Program.mainForm.SetProgress(0); if (Config.GetInt("processingMode") == 0 && !Config.GetBool("keepTempFolder")) IOUtils.TryDeleteIfExists(current.tempFolder); + AutoEncode.busy = false; Program.mainForm.SetWorking(false); Program.mainForm.SetTab("interpolation"); if(!Logger.GetLastLine().Contains("Canceled interpolation.")) diff --git a/Code/Main/InterpolateSteps.cs b/Code/Main/InterpolateSteps.cs index 27c79d5..287f5f2 100644 --- a/Code/Main/InterpolateSteps.cs +++ b/Code/Main/InterpolateSteps.cs @@ -131,7 +131,7 @@ namespace Flowframes.Main int frames = IOUtils.GetAmountOfFiles(current.framesFolder, false, "*.png"); int targetFrameCount = frames * current.interpFactor; - GetProgressByFrameAmount(current.interpFolder, targetFrameCount); + InterpolateUtils.GetProgressByFrameAmount(current.interpFolder, targetFrameCount); if (canceled) return; Program.mainForm.SetStatus("Running AI..."); int tilesize = current.ai.supportsTiling ? Config.GetInt($"tilesize_{current.ai.aiName}") : 512; @@ -141,7 +141,7 @@ namespace Flowframes.Main public static async Task CreateOutputVid() { - string[] outFrames = Directory.GetFiles(current.interpFolder, $"*.{InterpolateUtils.GetExt()}"); + string[] outFrames = Directory.GetFiles(current.interpFolder, $"*.{InterpolateUtils.GetOutExt()}"); if (outFrames.Length > 0 && !IOUtils.CheckImageValid(outFrames[0])) { InterpolateUtils.ShowMessage("Invalid frame files detected!\n\nIf you used Auto-Encode, this is normal, and you don't need to run " + diff --git a/Code/Main/InterpolateUtils.cs b/Code/Main/InterpolateUtils.cs index effb15a..4c6bb8c 100644 --- a/Code/Main/InterpolateUtils.cs +++ b/Code/Main/InterpolateUtils.cs @@ -9,6 +9,7 @@ using System.Drawing; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using System.Threading.Tasks; using System.Windows.Forms; using i = Flowframes.Interpolate; @@ -19,27 +20,56 @@ namespace Flowframes.Main public static PictureBox preview; public static BigPreviewForm bigPreviewForm; - public static string GetExt () + public static string GetOutExt (bool withDot = false) { + string dotStr = withDot ? "." : ""; if (Config.GetBool("jpegInterps")) - return "jpg"; - return "png"; + return dotStr + "jpg"; + return dotStr + "png"; + } + + public static int targetFrames; + public static int currentFactor; + public static async void GetProgressByFrameAmount(string outdir, int target) + { + bool firstProgUpd = true; + Program.mainForm.SetProgress(0); + targetFrames = target; + while (Program.busy) + { + if (AiProcess.processTime.IsRunning && Directory.Exists(outdir)) + { + if (firstProgUpd && Program.mainForm.IsInFocus()) + Program.mainForm.SetTab("preview"); + firstProgUpd = false; + string[] frames = Directory.GetFiles(outdir, $"*.{GetOutExt()}"); + if (frames.Length > 1) + UpdateInterpProgress(frames.Length, targetFrames, frames[frames.Length - 1]); + await Task.Delay(GetProgressWaitTime(frames.Length)); + } + else + { + await Task.Delay(200); + } + } + Program.mainForm.SetProgress(-1); } public static void UpdateInterpProgress(int frames, int target, string latestFramePath = "") { + frames = frames.Clamp(0, target); int percent = (int)Math.Round(((float)frames / target) * 100f); Program.mainForm.SetProgress(percent); float generousTime = ((AiProcess.processTime.ElapsedMilliseconds - AiProcess.lastStartupTimeMs) / 1000f); float fps = (float)frames / generousTime; - string fpsIn = (fps / i.current.interpFactor).ToString("0.00"); + string fpsIn = (fps / currentFactor).ToString("0.00"); string fpsOut = fps.ToString("0.00"); float secondsPerFrame = generousTime / (float)frames; int framesLeft = target - frames; float eta = framesLeft * secondsPerFrame; - string etaStr = FormatUtils.Time(new TimeSpan(0, 0, eta.RoundToInt())); + string etaStr = FormatUtils.Time(new TimeSpan(0, 0, eta.RoundToInt()), false); bool replaceLine = Regex.Split(Logger.textbox.Text, "\r\n|\r|\n").Last().Contains("Average Speed: "); @@ -50,7 +80,7 @@ namespace Flowframes.Main try { - if (!string.IsNullOrWhiteSpace(latestFramePath) && frames > i.current.interpFactor) + if (!string.IsNullOrWhiteSpace(latestFramePath) && frames > currentFactor) { if (bigPreviewForm == null && !preview.Visible /* ||Program.mainForm.WindowState != FormWindowState.Minimized */ /* || !Program.mainForm.IsInFocus()*/) return; // Skip if the preview is not visible or the form is not in focus Image img = IOUtils.GetImage(latestFramePath); diff --git a/Code/OS/AiProcess.cs b/Code/OS/AiProcess.cs index 704321f..9e9bc40 100644 --- a/Code/OS/AiProcess.cs +++ b/Code/OS/AiProcess.cs @@ -23,20 +23,26 @@ namespace Flowframes public static Stopwatch processTimeMulti = new Stopwatch(); public static int lastStartupTimeMs = 1000; + static string lastInPath; public static Dictionary filenameMap = new Dictionary(); // TODO: Store on disk instead for crashes? - static void AiStarted (Process proc, int startupTimeMs) + static void AiStarted (Process proc, int startupTimeMs, int factor, string inPath = "") { lastStartupTimeMs = startupTimeMs; processTime.Restart(); currentAiProcess = proc; + lastInPath = string.IsNullOrWhiteSpace(inPath) ? Interpolate.current.framesFolder : inPath; + int frames = IOUtils.GetAmountOfFiles(lastInPath, false, "*.png"); + InterpolateUtils.currentFactor = factor; + InterpolateUtils.targetFrames = (frames * factor) - (factor - 1); hasShownError = false; } static void AiFinished (string aiName) { Program.mainForm.SetProgress(100); + InterpolateUtils.UpdateInterpProgress(IOUtils.GetAmountOfFiles(Interpolate.current.interpFolder, false, "*.png"), InterpolateUtils.targetFrames); string logStr = $"Done running {aiName} - Interpolation took {FormatUtils.Time(processTime.Elapsed)}"; if (Interpolate.currentlyUsingAutoEnc && AutoEncode.HasWorkToDo()) logStr += " - Waiting for encoding to finish..."; @@ -50,8 +56,8 @@ namespace Flowframes string dainDir = Path.Combine(Paths.GetPkgPath(), Path.GetFileNameWithoutExtension(Packages.dainNcnn.fileName)); Process dain = OSUtils.NewProcess(!OSUtils.ShowHiddenCmd()); - AiStarted(dain, 1500); - dain.StartInfo.Arguments = $"{OSUtils.GetCmdArg()} cd /D {dainDir.Wrap()} & dain-ncnn-vulkan.exe {args} -f {InterpolateUtils.GetExt()} -j {GetNcnnThreads()}"; + AiStarted(dain, 1500, Interpolate.current.interpFactor); + dain.StartInfo.Arguments = $"{OSUtils.GetCmdArg()} cd /D {dainDir.Wrap()} & dain-ncnn-vulkan.exe {args} -f {InterpolateUtils.GetOutExt()} -j {GetNcnnThreads()}"; Logger.Log("Running DAIN...", false); Logger.Log("cmd.exe " + dain.StartInfo.Arguments, true); if (!OSUtils.ShowHiddenCmd()) @@ -71,7 +77,7 @@ namespace Flowframes if (Interpolate.canceled) return; if (!Interpolate.currentlyUsingAutoEnc) - IOUtils.ZeroPadDir(outPath, InterpolateUtils.GetExt(), Padding.interpFrames); + IOUtils.ZeroPadDir(outPath, InterpolateUtils.GetOutExt(), Padding.interpFrames); AiFinished("DAIN"); } @@ -113,7 +119,7 @@ namespace Flowframes if (Interpolate.canceled) return; if (!Interpolate.currentlyUsingAutoEnc) - IOUtils.ZeroPadDir(outPath, InterpolateUtils.GetExt(), Padding.interpFrames); + IOUtils.ZeroPadDir(outPath, InterpolateUtils.GetOutExt(), Padding.interpFrames); AiFinished("CAIN"); } @@ -123,8 +129,8 @@ namespace Flowframes string cainDir = Path.Combine(Paths.GetPkgPath(), Path.GetFileNameWithoutExtension(Packages.cainNcnn.fileName)); string cainExe = "cain-ncnn-vulkan.exe"; Process cain = OSUtils.NewProcess(!OSUtils.ShowHiddenCmd()); - AiStarted(cain, 1500); - cain.StartInfo.Arguments = $"{OSUtils.GetCmdArg()} cd /D {cainDir.Wrap()} & {cainExe} {args} -f {InterpolateUtils.GetExt()} -j {GetNcnnThreads()}"; + AiStarted(cain, 1500, 2); + cain.StartInfo.Arguments = $"{OSUtils.GetCmdArg()} cd /D {cainDir.Wrap()} & {cainExe} {args} -f {InterpolateUtils.GetOutExt()} -j {GetNcnnThreads()}"; Logger.Log("cmd.exe " + cain.StartInfo.Arguments, true); if (!OSUtils.ShowHiddenCmd()) { @@ -146,7 +152,7 @@ namespace Flowframes string script = "inference_video.py"; bool uhd = IOUtils.GetVideoRes(Interpolate.current.inPath).Height >= Config.GetInt("uhdThresh"); string uhdStr = uhd ? "--UHD" : ""; - string args = $" --img {framesPath.Wrap()} --exp {(int)Math.Log(interpFactor, 2)} {uhdStr} --imgformat {InterpolateUtils.GetExt()} --output {Paths.interpDir}"; + string args = $" --img {framesPath.Wrap()} --exp {(int)Math.Log(interpFactor, 2)} {uhdStr} --imgformat {InterpolateUtils.GetOutExt()} --output {Paths.interpDir}"; if (!File.Exists(Path.Combine(rifeDir, script))) { @@ -155,7 +161,7 @@ namespace Flowframes } Process rifePy = OSUtils.NewProcess(!OSUtils.ShowHiddenCmd()); - AiStarted(rifePy, 3500); + AiStarted(rifePy, 3500, Interpolate.current.interpFactor); rifePy.StartInfo.Arguments = $"{OSUtils.GetCmdArg()} cd /D {PkgUtils.GetPkgFolder(Packages.rifeCuda).Wrap()} & " + $"set CUDA_VISIBLE_DEVICES={Config.Get("torchGpus")} & {Pytorch.GetPyCmd()} {script} {args}"; Logger.Log($"Running RIFE {(uhd ? "(UHD Mode)" : "")} ({script})...".TrimWhitespaces(), false); @@ -184,8 +190,7 @@ namespace Flowframes if(times > 2) AutoEncode.paused = true; // Disable autoenc until the last iteration - string args = $" -v -i {framesPath.Wrap()} -o {outPath.Wrap()}"; - await RunRifePartial(args); + await RunRifePartial(framesPath, outPath); if (times == 4 || times == 8) // #2 { @@ -197,8 +202,7 @@ namespace Flowframes Directory.CreateDirectory(outPath); if (useAutoEnc && times == 4) AutoEncode.paused = false; - args = $" -v -i {run1ResultsPath.Wrap()} -o {outPath.Wrap()}"; - await RunRifePartial(args); + await RunRifePartial(run1ResultsPath, outPath); IOUtils.TryDeleteIfExists(run1ResultsPath); } @@ -212,27 +216,24 @@ namespace Flowframes Directory.CreateDirectory(outPath); if (useAutoEnc && times == 8) AutoEncode.paused = false; - args = $" -v -i {run2ResultsPath.Wrap()} -o {outPath.Wrap()}"; - await RunRifePartial(args); + await RunRifePartial(run2ResultsPath, outPath); IOUtils.TryDeleteIfExists(run2ResultsPath); } if (Interpolate.canceled) return; if (!Interpolate.currentlyUsingAutoEnc) - { - Logger.Log($"zero padding {outPath} with ext \"{InterpolateUtils.GetExt()}\" to length {Padding.interpFrames}"); - IOUtils.ZeroPadDir(outPath, InterpolateUtils.GetExt(), Padding.interpFrames); - } + IOUtils.ZeroPadDir(outPath, InterpolateUtils.GetOutExt(), Padding.interpFrames); AiFinished("RIFE"); } - static async Task RunRifePartial(string args) + static async Task RunRifePartial(string inPath, string outPath) { Process rifeNcnn = OSUtils.NewProcess(!OSUtils.ShowHiddenCmd()); - AiStarted(rifeNcnn, 1500); - rifeNcnn.StartInfo.Arguments = $"{OSUtils.GetCmdArg()} cd /D {PkgUtils.GetPkgFolder(Packages.rifeNcnn).Wrap()} & rife-ncnn-vulkan.exe {args} -g {Config.Get("ncnnGpus")} -f {InterpolateUtils.GetExt()} -j {GetNcnnThreads()}"; + AiStarted(rifeNcnn, 1500, 2, inPath); + rifeNcnn.StartInfo.Arguments = $"{OSUtils.GetCmdArg()} cd /D {PkgUtils.GetPkgFolder(Packages.rifeNcnn).Wrap()} & rife-ncnn-vulkan.exe " + + $" -v -i {inPath.Wrap()} -o {outPath.Wrap()} -g {Config.Get("ncnnGpus")} -f {InterpolateUtils.GetOutExt()} -j {GetNcnnThreads()}"; Logger.Log("cmd.exe " + rifeNcnn.StartInfo.Arguments, true); if (!OSUtils.ShowHiddenCmd()) { diff --git a/Code/UI/FormatUtils.cs b/Code/UI/FormatUtils.cs index d06dab6..f54a3e3 100644 --- a/Code/UI/FormatUtils.cs +++ b/Code/UI/FormatUtils.cs @@ -34,7 +34,7 @@ namespace Flowframes.UI return secs.ToString("0.00") + "s"; } - public static string Time (TimeSpan span) + public static string Time (TimeSpan span, bool allowMs = true) { if(span.TotalHours >= 1f) return span.ToString(@"hh\:mm\:ss"); @@ -42,10 +42,10 @@ namespace Flowframes.UI if (span.TotalMinutes >= 1f) return span.ToString(@"mm\:ss"); - if (span.TotalSeconds >= 2f) + if (span.TotalSeconds >= 1f || !allowMs) return span.ToString(@"ss") + "s"; - return span.Milliseconds + "ms"; + return span.ToString(@"fff") + "ms"; } public static string TimeSw(Stopwatch sw)