Files
flowframes/Code/Main/InterpolateUtils.cs

529 lines
20 KiB
C#
Raw Normal View History

using Flowframes.Media;
using Flowframes.Data;
2020-11-23 16:51:05 +01:00
using Flowframes.Forms;
using Flowframes.IO;
using Flowframes.MiscUtils;
2020-11-23 16:51:05 +01:00
using Flowframes.OS;
using Flowframes.UI;
using System;
using System.Collections.Generic;
2020-12-23 00:07:06 +01:00
using System.Diagnostics;
2020-11-23 16:51:05 +01:00
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
2020-11-23 16:51:05 +01:00
using System.Windows.Forms;
using I = Flowframes.Interpolate;
using Padding = Flowframes.Data.Padding;
2020-11-23 16:51:05 +01:00
namespace Flowframes.Main
{
class InterpolateUtils
{
public static PictureBox preview;
public static BigPreviewForm bigPreviewForm;
public static async Task CopyLastFrame(int lastFrameNum)
{
2021-03-01 22:38:38 +01:00
if (I.canceled) return;
try
{
lastFrameNum--; // We have to do this as extracted frames start at 0, not 1
bool frameFolderInput = IOUtils.IsPathDirectory(I.current.inPath);
string targetPath = Path.Combine(I.current.framesFolder, lastFrameNum.ToString().PadLeft(Padding.inputFrames, '0') + ".png");
if (File.Exists(targetPath)) return;
Size res = IOUtils.GetImage(IOUtils.GetFilesSorted(I.current.framesFolder, false).First()).Size;
if (frameFolderInput)
{
string lastFramePath = IOUtils.GetFilesSorted(I.current.inPath, false).Last();
await FfmpegExtract.ExtractLastFrame(lastFramePath, targetPath, res);
}
else
{
await FfmpegExtract.ExtractLastFrame(I.current.inPath, targetPath, res);
}
}
catch (Exception e)
{
Logger.Log("CopyLastFrame Error: " + e.Message);
}
}
public static string GetOutExt(bool withDot = false)
{
string dotStr = withDot ? "." : "";
if (Config.GetBool("jpegInterp"))
return dotStr + "jpg";
return dotStr + "png";
}
public static int lastFrame;
public static int targetFrames;
public static string currentOutdir;
public static float currentFactor;
public static bool progressPaused = false;
public static bool progCheckRunning = false;
public static async void GetProgressByFrameAmount(string outdir, int target)
{
progCheckRunning = true;
targetFrames = target;
currentOutdir = outdir;
Logger.Log($"Starting GetProgressByFrameAmount() loop for outdir '{currentOutdir}', target is {target} frames", true);
bool firstProgUpd = true;
Program.mainForm.SetProgress(0);
2021-02-11 11:36:12 +01:00
lastFrame = 0;
peakFpsOut = 0f;
while (Program.busy)
{
if (!progressPaused && AiProcess.processTime.IsRunning && Directory.Exists(currentOutdir))
{
if (firstProgUpd && Program.mainForm.IsInFocus())
Program.mainForm.SetTab("preview");
2021-02-11 21:18:00 +01:00
firstProgUpd = false;
2021-02-11 12:58:07 +01:00
string lastFramePath = currentOutdir + "\\" + lastFrame.ToString("00000000") + $".{GetOutExt()}";
2021-02-11 21:18:00 +01:00
if (lastFrame > 1)
2021-02-11 12:58:07 +01:00
UpdateInterpProgress(lastFrame, targetFrames, lastFramePath);
2021-02-11 21:18:00 +01:00
await Task.Delay((target < 1000) ? 100 : 200); // Update 10x/sec if interpolating <1k frames, otherwise 5x/sec
2021-02-11 21:18:00 +01:00
if (lastFrame >= targetFrames)
break;
}
else
{
await Task.Delay(100);
}
}
progCheckRunning = false;
if (I.canceled)
Program.mainForm.SetProgress(0);
}
2020-11-23 16:51:05 +01:00
public static void UpdateLastFrameFromInterpOutput(string output)
{
try
{
string ncnnStr = I.current.ai.aiName.Contains("NCNN") ? " done" : "";
Regex frameRegex = new Regex($@"(?<=.)\d*(?=.{GetOutExt()}{ncnnStr})");
if (!frameRegex.IsMatch(output)) return;
lastFrame = Math.Max(int.Parse(frameRegex.Match(output).Value), lastFrame);
}
catch
{
Logger.Log($"UpdateLastFrameFromInterpOutput: Failed to get progress from '{output}' even though Regex matched!");
}
}
public static int interpolatedInputFramesCount;
public static float peakFpsOut;
public static int previewUpdateRateMs = 200;
2021-02-27 16:24:02 +01:00
2020-11-23 16:51:05 +01:00
public static void UpdateInterpProgress(int frames, int target, string latestFramePath = "")
{
if (I.canceled) return;
interpolatedInputFramesCount = ((frames / I.current.interpFactor).RoundToInt() - 1);
ResumeUtils.Save();
frames = frames.Clamp(0, target);
2020-11-23 16:51:05 +01:00
int percent = (int)Math.Round(((float)frames / target) * 100f);
Program.mainForm.SetProgress(percent);
float generousTime = ((AiProcess.processTime.ElapsedMilliseconds - AiProcess.lastStartupTimeMs) / 1000f);
2020-11-23 16:51:05 +01:00
float fps = (float)frames / generousTime;
string fpsIn = (fps / currentFactor).ToString("0.00");
2020-11-23 16:51:05 +01:00
string fpsOut = fps.ToString("0.00");
if (fps > peakFpsOut)
peakFpsOut = fps;
2020-11-23 16:51:05 +01:00
float secondsPerFrame = generousTime / (float)frames;
int framesLeft = target - frames;
float eta = framesLeft * secondsPerFrame;
string etaStr = FormatUtils.Time(new TimeSpan(0, 0, eta.RoundToInt()), false);
2020-11-23 16:51:05 +01:00
2021-02-11 12:44:42 +01:00
bool replaceLine = Regex.Split(Logger.textbox.Text, "\r\n|\r|\n").Last().Contains("Average Speed: ");
2021-02-11 12:44:42 +01:00
string logStr = $"Interpolated {frames}/{target} Frames ({percent}%) - Average Speed: {fpsIn} FPS In / {fpsOut} FPS Out - ";
logStr += $"Time: {FormatUtils.Time(AiProcess.processTime.Elapsed)} - ETA: {etaStr}";
if (AutoEncode.busy) logStr += " - Encoding...";
Logger.Log(logStr, false, replaceLine);
2020-11-23 16:51:05 +01:00
try
{
if (!string.IsNullOrWhiteSpace(latestFramePath) && frames > currentFactor)
2020-11-23 16:51:05 +01:00
{
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
2021-02-28 13:46:50 +01:00
if (timeSinceLastPreviewUpdate.IsRunning && timeSinceLastPreviewUpdate.ElapsedMilliseconds < previewUpdateRateMs) return;
2020-11-23 16:51:05 +01:00
Image img = IOUtils.GetImage(latestFramePath);
SetPreviewImg(img);
2020-11-23 16:51:05 +01:00
}
}
2021-02-28 13:46:50 +01:00
catch (Exception e)
{
Logger.Log("Error updating preview: " + e.Message, true);
}
2020-11-23 16:51:05 +01:00
}
2021-02-11 11:36:12 +01:00
public static async Task DeleteInterpolatedInputFrames()
{
interpolatedInputFramesCount = 0;
string[] inputFrames = IOUtils.GetFilesSorted(I.current.framesFolder);
for (int i = 0; i < inputFrames.Length; i++)
{
2021-02-02 21:48:18 +01:00
while (Program.busy && (i + 10) > interpolatedInputFramesCount) await Task.Delay(1000);
if (!Program.busy) break;
if (i != 0 && i != inputFrames.Length - 1)
IOUtils.OverwriteFileWithText(inputFrames[i]);
if (i % 10 == 0) await Task.Delay(10);
}
}
2021-02-27 16:24:02 +01:00
public static Stopwatch timeSinceLastPreviewUpdate = new Stopwatch();
2021-02-11 11:36:12 +01:00
public static void SetPreviewImg(Image img)
{
if (img == null)
return;
2021-02-27 16:24:02 +01:00
timeSinceLastPreviewUpdate.Restart();
preview.Image = img;
if (bigPreviewForm != null)
bigPreviewForm.SetImage(img);
}
public static Dictionary<PseudoUniqueFile, int> frameCountCache = new Dictionary<PseudoUniqueFile, int>();
public static async Task<int> GetInputFrameCountAsync(string path)
{
long filesize = IOUtils.GetFilesize(path);
PseudoUniqueFile hash = new PseudoUniqueFile(path, filesize);
if (filesize > 0 && FrameCountCacheContains(hash))
{
Logger.Log($"FrameCountCache contains this hash, using cached frame count.", true);
return GetFrameCountFromCache(hash);
}
else
{
Logger.Log($"Hash not cached, reading frame count.", true);
}
int frameCount;
2021-02-08 10:21:26 +01:00
if (IOUtils.IsPathDirectory(path))
frameCount = IOUtils.GetAmountOfFiles(path, false);
else
frameCount = await FfmpegCommands.GetFrameCountAsync(path);
Logger.Log($"Adding hash with frame count {frameCount} to cache.", true);
frameCountCache.Add(hash, frameCount);
return frameCount;
}
private static bool FrameCountCacheContains (PseudoUniqueFile hash)
{
foreach(KeyValuePair<PseudoUniqueFile, int> entry in frameCountCache)
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
return true;
return false;
}
private static int GetFrameCountFromCache(PseudoUniqueFile hash)
{
foreach (KeyValuePair<PseudoUniqueFile, int> entry in frameCountCache)
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
return entry.Value;
return 0;
}
2020-11-23 16:51:05 +01:00
public static int GetProgressWaitTime(int numFrames)
{
float hddMultiplier = !Program.lastInputPathIsSsd ? 2f : 1f;
2020-11-23 16:51:05 +01:00
int waitMs = 200;
if (numFrames > 100)
waitMs = 500;
if (numFrames > 1000)
waitMs = 1000;
if (numFrames > 2500)
waitMs = 1500;
if (numFrames > 5000)
waitMs = 2500;
return (waitMs * hddMultiplier).RoundToInt();
}
public static string GetTempFolderLoc(string inPath, string outPath)
2020-11-23 16:51:05 +01:00
{
string basePath = inPath.GetParentDir();
if (Config.GetInt("tempFolderLoc") == 1)
2020-11-23 16:51:05 +01:00
basePath = outPath.GetParentDir();
2020-11-23 16:51:05 +01:00
if (Config.GetInt("tempFolderLoc") == 2)
basePath = outPath;
2020-11-23 16:51:05 +01:00
if (Config.GetInt("tempFolderLoc") == 3)
basePath = Paths.GetExeDir();
2020-11-23 16:51:05 +01:00
if (Config.GetInt("tempFolderLoc") == 4)
{
string custPath = Config.Get("tempDirCustom");
if (IOUtils.IsDirValid(custPath))
2020-11-23 16:51:05 +01:00
basePath = custPath;
}
return Path.Combine(basePath, Path.GetFileNameWithoutExtension(inPath).StripBadChars().Remove(" ").Trunc(30, false) + "-temp");
2020-11-23 16:51:05 +01:00
}
public static bool InputIsValid(string inDir, string outDir, float fpsOut, float factor, Interpolate.OutMode outMode)
2020-11-23 16:51:05 +01:00
{
bool passes = true;
2020-11-23 16:51:05 +01:00
bool isFile = !IOUtils.IsPathDirectory(inDir);
if ((passes && isFile && !IOUtils.IsFileValid(inDir)) || (!isFile && !IOUtils.IsDirValid(inDir)))
2020-11-23 16:51:05 +01:00
{
ShowMessage("Input path is not valid!");
passes = false;
}
if (passes && !IOUtils.IsDirValid(outDir))
2020-11-23 16:51:05 +01:00
{
ShowMessage("Output path is not valid!");
passes = false;
}
if (passes && /*factor != 2 && factor != 4 && factor != 8*/ factor > 16)
2020-11-23 16:51:05 +01:00
{
ShowMessage("Interpolation factor is not valid!");
passes = false;
}
2021-02-20 00:18:00 +01:00
if (passes && outMode == I.OutMode.VidGif && fpsOut > 50 && !(Config.GetFloat("maxFps") != 0 && Config.GetFloat("maxFps") <= 50))
{
ShowMessage("Invalid output frame rate!\nGIF does not properly support frame rates above 50 FPS.\nPlease use MP4, WEBM or another video format.");
passes = false;
}
if (passes && fpsOut < 1 || fpsOut > 1000)
2020-11-23 16:51:05 +01:00
{
ShowMessage("Invalid output frame rate - Must be 1-1000.");
2020-11-23 16:51:05 +01:00
passes = false;
}
if (!passes)
I.Cancel("Invalid settings detected.", true);
2020-11-23 16:51:05 +01:00
return passes;
}
public static void PathAsciiCheck(string path, string pathTitle)
{
if (IOUtils.HasBadChars(path) || OSUtils.HasNonAsciiChars(path))
ShowMessage($"Warning: Your {pathTitle} includes special characters. This might cause problems.");
2020-11-23 16:51:05 +01:00
}
public static bool CheckAiAvailable(AI ai)
2020-11-23 16:51:05 +01:00
{
if (!PkgUtils.IsAiAvailable(ai))
2020-11-23 16:51:05 +01:00
{
ShowMessage("The selected AI is not installed!", "Error");
I.Cancel("Selected AI not available.", true);
2020-11-23 16:51:05 +01:00
return false;
}
if (I.current.ai.aiName.ToUpper().Contains("CUDA") && NvApi.gpuList.Count < 1)
{
ShowMessage("An Nvidia GPU is required for CUDA implementations!\n\nTry an NCNN implementation instead.", "Error");
I.Cancel("No CUDA-capable graphics card available.", true);
return false;
}
2020-11-23 16:51:05 +01:00
return true;
}
public static bool CheckDeleteOldTempFolder()
2020-11-23 16:51:05 +01:00
{
if (!IOUtils.TryDeleteIfExists(I.current.tempFolder))
2020-11-23 16:51:05 +01:00
{
ShowMessage("Failed to remove an existing temp folder of this video!\nMake sure you didn't open any frames in an editor.", "Error");
I.Cancel();
2020-11-23 16:51:05 +01:00
return false;
}
return true;
}
public static bool CheckPathValid(string path)
2020-11-23 16:51:05 +01:00
{
if (IOUtils.IsPathDirectory(path))
{
if (!IOUtils.IsDirValid(path))
{
ShowMessage("Input directory is not valid.");
I.Cancel();
2020-11-23 16:51:05 +01:00
return false;
}
}
else
{
if (!IsVideoValid(path))
{
ShowMessage("Input video file is not valid.");
return false;
}
}
return true;
}
public static async Task<bool> CheckEncoderValid ()
{
string enc = FFmpegUtils.GetEnc(FFmpegUtils.GetCodec(I.current.outMode));
if (!enc.ToLower().Contains("nvenc"))
return true;
if (!(await FfmpegCommands.IsEncoderCompatible(enc)))
{
ShowMessage("NVENC encoding is not available on your hardware!\nPlease use a different encoder.", "Error");
I.Cancel();
return false;
}
return true;
}
2020-11-23 16:51:05 +01:00
public static bool IsVideoValid(string videoPath)
{
if (videoPath == null || !IOUtils.IsFileValid(videoPath))
return false;
return true;
2020-11-23 16:51:05 +01:00
}
public static void ShowMessage(string msg, string title = "Message")
{
if (!BatchProcessing.busy)
MessageBox.Show(msg, title);
Logger.Log("Message: " + msg, true);
}
public static async Task<Size> GetOutputResolution(string inputPath, bool print, bool returnZeroIfUnchanged = false)
{
Size resolution = await IOUtils.GetVideoOrFramesRes(inputPath);
2021-01-27 11:41:05 +01:00
return GetOutputResolution(resolution, print, returnZeroIfUnchanged);
}
2021-01-27 11:41:05 +01:00
public static Size GetOutputResolution(Size inputRes, bool print = false, bool returnZeroIfUnchanged = false)
{
int maxHeight = RoundDiv2(Config.GetInt("maxVidHeight"));
if (inputRes.Height > maxHeight)
{
float factor = (float)maxHeight / inputRes.Height;
Logger.Log($"Un-rounded downscaled size: {(inputRes.Width * factor).ToString("0.00")}x{Config.GetInt("maxVidHeight")}", true);
int width = RoundDiv2((inputRes.Width * factor).RoundToInt());
if (print)
2020-12-21 15:03:31 +01:00
Logger.Log($"Video is bigger than the maximum - Downscaling to {width}x{maxHeight}.");
return new Size(width, maxHeight);
}
else
{
2021-01-27 11:41:05 +01:00
//return new Size(RoundDiv2(inputRes.Width), RoundDiv2(inputRes.Height));
if (returnZeroIfUnchanged)
return new Size();
else
return inputRes;
}
}
public static int RoundDiv2(int n) // Round to a number that's divisible by 2 (for h264 etc)
{
int a = (n / 2) * 2; // Smaller multiple
int b = a + 2; // Larger multiple
return (n - a > b - n) ? b : a; // Return of closest of two
}
2020-12-23 00:07:06 +01:00
public static bool CanUseAutoEnc(bool stepByStep, InterpSettings current)
{
AutoEncode.UpdateChunkAndBufferSizes();
if (current.alpha)
{
Logger.Log($"Not Using AutoEnc: Alpha mode is enabled.", true);
return false;
}
if (!current.outMode.ToString().ToLower().Contains("vid") || current.outMode.ToString().ToLower().Contains("gif"))
{
Logger.Log($"Not Using AutoEnc: Out Mode is not video ({current.outMode.ToString()})", true);
return false;
}
if (stepByStep && !Config.GetBool("sbsAllowAutoEnc"))
{
Logger.Log($"Not Using AutoEnc: Using step-by-step mode, but 'sbsAllowAutoEnc' is false.", true);
return false;
}
if (!stepByStep && Config.GetInt("autoEncMode") == 0)
{
Logger.Log($"Not Using AutoEnc: 'autoEncMode' is 0.", true);
return false;
}
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 than (chunkSize ({AutoEncode.chunkSize}) + safetyBufferFrames ({AutoEncode.safetyBufferFrames}) * 1.2f)", true);
return false;
}
return true;
}
public static async Task<bool> UseUHD()
{
return (await GetOutputResolution(I.current.inPath, false)).Height >= Config.GetInt("uhdThresh");
}
public static void FixConsecutiveSceneFrames(string sceneFramesPath, string sourceFramesPath)
2020-12-23 00:07:06 +01:00
{
if (!Directory.Exists(sceneFramesPath) || IOUtils.GetAmountOfFiles(sceneFramesPath, false) < 1)
return;
2020-12-23 00:07:06 +01:00
List<string> sceneFrames = IOUtils.GetFilesSorted(sceneFramesPath).Select(x => Path.GetFileNameWithoutExtension(x)).ToList();
List<string> sourceFrames = IOUtils.GetFilesSorted(sourceFramesPath).Select(x => Path.GetFileNameWithoutExtension(x)).ToList();
List<string> sceneFramesToDelete = new List<string>();
foreach (string scnFrame in sceneFrames)
2020-12-23 00:07:06 +01:00
{
if (sceneFramesToDelete.Contains(scnFrame))
continue;
int sourceIndexForScnFrame = sourceFrames.IndexOf(scnFrame); // Get source index of scene frame
if ((sourceIndexForScnFrame + 1) == sourceFrames.Count)
continue;
2020-12-23 00:07:06 +01:00
string followingFrame = sourceFrames[sourceIndexForScnFrame + 1]; // Get filename/timestamp of the next source frame
if (sceneFrames.Contains(followingFrame)) // If next source frame is in scene folder, add to deletion list
sceneFramesToDelete.Add(followingFrame);
}
foreach (string frame in sceneFramesToDelete)
IOUtils.TryDeleteIfExists(Path.Combine(sceneFramesPath, frame + ".png"));
}
2020-11-23 16:51:05 +01:00
}
}