Case-sensitive rename operation (2/2)

This commit is contained in:
n00mkrad
2021-08-23 16:50:18 +02:00
parent 2c14fa9515
commit 9433269813
88 changed files with 36439 additions and 0 deletions

31
Code/Data/AI.cs Normal file
View File

@@ -0,0 +1,31 @@
using Flowframes.IO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Data
{
public struct AI
{
public string aiName;
public string aiNameShort;
public string friendlyName;
public string description;
public string pkgDir;
public int[] supportedFactors;
public bool multiPass; // Are multiple passes needed to get to the desired interp factor?
public AI(string aiNameArg, string friendlyNameArg, string descArg, string pkgDirArg, int[] factorsArg, bool multiPassArg = false)
{
aiName = aiNameArg;
aiNameShort = aiNameArg.Split(' ')[0].Split('_')[0];
friendlyName = friendlyNameArg;
description = descArg;
pkgDir = pkgDirArg;
supportedFactors = factorsArg;
multiPass = multiPassArg;
}
}
}

18
Code/Data/AudioTrack.cs Normal file
View File

@@ -0,0 +1,18 @@
using System.Globalization;
namespace Flowframes.Data
{
class AudioTrack
{
public int streamIndex;
public string metadata;
public string codec;
public AudioTrack(int streamNum, string metaStr, string codecStr)
{
streamIndex = streamNum;
metadata = metaStr.Trim().Replace("_", ".").Replace(" ", ".");
codec = codecStr.Trim().Replace("_", ".");
}
}
}

14
Code/Data/Filetypes.cs Normal file
View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Data
{
class Filetypes
{
public static readonly string[] imagesOpenCv = new string[] { ".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tif", ".tiff" };
public static readonly string[] imagesInterpCompat = new string[] { ".png", ".jpg", ".jpeg", ".webp", ".bmp" };
}
}

256
Code/Data/Fraction.cs Normal file
View File

@@ -0,0 +1,256 @@
using System;
using System.Windows.Navigation;
namespace Flowframes.Data
{
public struct Fraction
{
public int Numerator;
public int Denominator;
public static Fraction Zero = new Fraction(0, 0);
public Fraction(int numerator, int denominator)
{
this.Numerator = numerator;
this.Denominator = denominator;
//If denominator negative...
if (this.Denominator < 0)
{
//...move the negative up to the numerator
this.Numerator = -this.Numerator;
this.Denominator = -this.Denominator;
}
}
public Fraction(int numerator, Fraction denominator)
{
//divide the numerator by the denominator fraction
this = new Fraction(numerator, 1) / denominator;
}
public Fraction(Fraction numerator, int denominator)
{
//multiply the numerator fraction by 1 over the denominator
this = numerator * new Fraction(1, denominator);
}
public Fraction(Fraction fraction)
{
Numerator = fraction.Numerator;
Denominator = fraction.Denominator;
}
public Fraction(float value)
{
Numerator = (value * 10000f).RoundToInt();
Denominator = 10000;
this = GetReduced();
}
public Fraction(string text)
{
try
{
string[] numbers = text.Split('/');
Numerator = numbers[0].GetInt();
Denominator = numbers[1].GetInt();
}
catch
{
try
{
Numerator = text.GetFloat().RoundToInt();
Denominator = 1;
}
catch
{
Numerator = 0;
Denominator = 0;
}
}
Logger.Log($"Fraction from String: Fraction(\"{text}\") => {Numerator}/{Denominator}", true);
}
private static int getGCD(int a, int b)
{
//Drop negative signs
a = Math.Abs(a);
b = Math.Abs(b);
//Return the greatest common denominator between two integers
while (a != 0 && b != 0)
{
if (a > b)
a %= b;
else
b %= a;
}
if (a == 0)
return b;
else
return a;
}
private static int getLCD(int a, int b)
{
//Return the Least Common Denominator between two integers
return (a * b) / getGCD(a, b);
}
public Fraction ToDenominator(int targetDenominator)
{
//Multiply the fraction by a factor to make the denominator
//match the target denominator
Fraction modifiedFraction = this;
//Cannot reduce to smaller denominators
if (targetDenominator < this.Denominator)
return modifiedFraction;
//The target denominator must be a factor of the current denominator
if (targetDenominator % this.Denominator != 0)
return modifiedFraction;
if (this.Denominator != targetDenominator)
{
int factor = targetDenominator / this.Denominator;
modifiedFraction.Denominator = targetDenominator;
modifiedFraction.Numerator *= factor;
}
return modifiedFraction;
}
public Fraction GetReduced()
{
//Reduce the fraction to lowest terms
Fraction modifiedFraction = this;
try
{
//While the numerator and denominator share a greatest common denominator,
//keep dividing both by it
int gcd = 0;
while (Math.Abs(gcd = getGCD(modifiedFraction.Numerator, modifiedFraction.Denominator)) != 1)
{
modifiedFraction.Numerator /= gcd;
modifiedFraction.Denominator /= gcd;
}
//Make sure only a single negative sign is on the numerator
if (modifiedFraction.Denominator < 0)
{
modifiedFraction.Numerator = -this.Numerator;
modifiedFraction.Denominator = -this.Denominator;
}
}
catch (Exception e)
{
Logger.Log($"Failed to reduce fraction ({modifiedFraction}): {e.Message}", true);
}
return modifiedFraction;
}
public Fraction GetReciprocal()
{
//Flip the numerator and the denominator
return new Fraction(this.Denominator, this.Numerator);
}
public static Fraction operator +(Fraction fraction1, Fraction fraction2)
{
//Check if either fraction is zero
if (fraction1.Denominator == 0)
return fraction2;
else if (fraction2.Denominator == 0)
return fraction1;
//Get Least Common Denominator
int lcd = getLCD(fraction1.Denominator, fraction2.Denominator);
//Transform the fractions
fraction1 = fraction1.ToDenominator(lcd);
fraction2 = fraction2.ToDenominator(lcd);
//Return sum
return new Fraction(fraction1.Numerator + fraction2.Numerator, lcd).GetReduced();
}
public static Fraction operator -(Fraction fraction1, Fraction fraction2)
{
//Get Least Common Denominator
int lcd = getLCD(fraction1.Denominator, fraction2.Denominator);
//Transform the fractions
fraction1 = fraction1.ToDenominator(lcd);
fraction2 = fraction2.ToDenominator(lcd);
//Return difference
return new Fraction(fraction1.Numerator - fraction2.Numerator, lcd).GetReduced();
}
public static Fraction operator *(Fraction fract, int multi)
{
int numerator = fract.Numerator * multi;
int denomenator = fract.Denominator;
return new Fraction(numerator, denomenator).GetReduced();
}
public static Fraction operator *(Fraction fract, float multi)
{
int numerator = (fract.Numerator * multi).RoundToInt();
int denomenator = fract.Denominator;
return new Fraction(numerator, denomenator).GetReduced();
}
public static Fraction operator *(Fraction fraction1, Fraction fraction2)
{
int numerator = fraction1.Numerator * fraction2.Numerator;
int denomenator = fraction1.Denominator * fraction2.Denominator;
return new Fraction(numerator, denomenator).GetReduced();
}
public static Fraction operator /(Fraction fraction1, Fraction fraction2)
{
return new Fraction(fraction1 * fraction2.GetReciprocal()).GetReduced();
}
public double ToDouble()
{
return (double)this.Numerator / this.Denominator;
}
public override string ToString()
{
return Numerator + "/" + Denominator;
}
public float GetFloat()
{
if (Denominator < 1) // Avoid div by zero
return 0f;
return (float)Numerator / (float)Denominator;
}
public long GetLong()
{
return (long)Numerator / (long)Denominator;
}
public string GetString()
{
return ((float)Numerator / Denominator).ToString();
}
}
}

View File

@@ -0,0 +1,28 @@
using Flowframes.IO;
using System;
using System.Collections.Generic;
namespace Flowframes.Data
{
class Implementations
{
public static AI rifeCuda = new AI("RIFE_CUDA", "RIFE", "CUDA/Pytorch Implementation of RIFE (Nvidia Only!)", "rife-cuda", new int[] { 2, 4, 8, 16 });
public static AI rifeNcnn = new AI("RIFE_NCNN", "RIFE (NCNN)", "Vulkan/NCNN Implementation of RIFE", "rife-ncnn", new int[] { 2, 4, 8 }, true);
public static AI flavrCuda = new AI("FLAVR_CUDA", "FLAVR", "Experimental Pytorch Implementation of FLAVR (Nvidia Only!)", "flavr-cuda", new int[] { 2, 4, 8 });
public static AI dainNcnn = new AI("DAIN_NCNN", "DAIN (NCNN)", "Vulkan/NCNN Implementation of DAIN", "dain-ncnn", new int[] { 2, 3, 4, 5, 6, 7, 8 });
public static AI xvfiCuda = new AI("XVFI_CUDA", "XVFI", "CUDA/Pytorch Implementation of XVFI (Nvidia Only!)", "xvfi-cuda", new int[] { 2, 3, 4, 5, 6, 7, 8, 9, 10 });
public static List<AI> networks = new List<AI> { rifeCuda, rifeNcnn, flavrCuda, dainNcnn, xvfiCuda };
public static AI GetAi (string aiName)
{
foreach(AI ai in networks)
{
if (ai.aiName == aiName)
return ai;
}
return networks[0];
}
}
}

252
Code/Data/InterpSettings.cs Normal file
View File

@@ -0,0 +1,252 @@
using Flowframes.Media;
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.Main;
using Flowframes.MiscUtils;
using Flowframes.Ui;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.VisualBasic.Logging;
namespace Flowframes
{
public class InterpSettings
{
public string inPath;
public string outPath;
public AI ai;
public Fraction inFps;
public Fraction inFpsDetected;
public Fraction outFps;
public float interpFactor;
public Interpolate.OutMode outMode;
public ModelCollection.ModelInfo model;
public string tempFolder;
public string framesFolder;
public string interpFolder;
public bool inputIsFrames;
public Size inputResolution;
public Size scaledResolution;
public bool alpha;
public bool stepByStep;
public string framesExt;
public string interpExt;
public InterpSettings(string inPathArg, string outPathArg, AI aiArg, Fraction inFpsDetectedArg, Fraction inFpsArg, int interpFactorArg, Interpolate.OutMode outModeArg, ModelCollection.ModelInfo modelArg)
{
inPath = inPathArg;
outPath = outPathArg;
ai = aiArg;
inFpsDetected = inFpsDetectedArg;
inFps = inFpsArg;
interpFactor = interpFactorArg;
outFps = inFpsArg * interpFactorArg;
outMode = outModeArg;
model = modelArg;
alpha = false;
stepByStep = false;
framesExt = "";
interpExt = "";
try
{
tempFolder = InterpolateUtils.GetTempFolderLoc(inPath, outPath);
framesFolder = Path.Combine(tempFolder, Paths.framesDir);
interpFolder = Path.Combine(tempFolder, Paths.interpDir);
inputIsFrames = IoUtils.IsPathDirectory(inPath);
}
catch
{
Logger.Log("Tried to create InterpSettings struct without an inpath. Can't set tempFolder, framesFolder and interpFolder.", true);
tempFolder = "";
framesFolder = "";
interpFolder = "";
inputIsFrames = false;
}
inputResolution = new Size(0, 0);
scaledResolution = new Size(0, 0);
RefreshExtensions();
}
public InterpSettings (string serializedData)
{
inPath = "";
outPath = "";
ai = Implementations.networks[0];
inFpsDetected = new Fraction();
inFps = new Fraction();
interpFactor = 0;
outFps = new Fraction();
outMode = Interpolate.OutMode.VidMp4;
model = null;
alpha = false;
stepByStep = false;
inputResolution = new Size(0, 0);
scaledResolution = new Size(0, 0);
framesExt = "";
interpExt = "";
Dictionary<string, string> entries = new Dictionary<string, string>();
foreach(string line in serializedData.SplitIntoLines())
{
if (line.Length < 3) continue;
string[] keyValuePair = line.Split('|');
entries.Add(keyValuePair[0], keyValuePair[1]);
}
foreach (KeyValuePair<string, string> entry in entries)
{
switch (entry.Key)
{
case "INPATH": inPath = entry.Value; break;
case "OUTPATH": outPath = entry.Value; break;
case "AI": ai = Implementations.GetAi(entry.Value); break;
case "INFPSDETECTED": inFpsDetected = new Fraction(entry.Value); break;
case "INFPS": inFps = new Fraction(entry.Value); break;
case "OUTFPS": outFps = new Fraction(entry.Value); break;
case "INTERPFACTOR": interpFactor = float.Parse(entry.Value); break;
case "OUTMODE": outMode = (Interpolate.OutMode)Enum.Parse(typeof(Interpolate.OutMode), entry.Value); break;
case "MODEL": model = AiModels.GetModelByName(ai, entry.Value); break;
case "INPUTRES": inputResolution = FormatUtils.ParseSize(entry.Value); break;
case "OUTPUTRES": scaledResolution = FormatUtils.ParseSize(entry.Value); break;
case "ALPHA": alpha = bool.Parse(entry.Value); break;
case "STEPBYSTEP": stepByStep = bool.Parse(entry.Value); break;
case "FRAMESEXT": framesExt = entry.Value; break;
case "INTERPEXT": interpExt = entry.Value; break;
}
}
try
{
tempFolder = InterpolateUtils.GetTempFolderLoc(inPath, outPath);
framesFolder = Path.Combine(tempFolder, Paths.framesDir);
interpFolder = Path.Combine(tempFolder, Paths.interpDir);
inputIsFrames = IoUtils.IsPathDirectory(inPath);
}
catch
{
Logger.Log("Tried to create InterpSettings struct without an inpath. Can't set tempFolder, framesFolder and interpFolder.", true);
tempFolder = "";
framesFolder = "";
interpFolder = "";
inputIsFrames = false;
}
RefreshExtensions();
}
public void UpdatePaths (string inPathArg, string outPathArg)
{
inPath = inPathArg;
outPath = outPathArg;
tempFolder = InterpolateUtils.GetTempFolderLoc(inPath, outPath);
framesFolder = Path.Combine(tempFolder, Paths.framesDir);
interpFolder = Path.Combine(tempFolder, Paths.interpDir);
inputIsFrames = IoUtils.IsPathDirectory(inPath);
}
public async Task<Size> GetInputRes()
{
await RefreshResolutions();
return inputResolution;
}
public async Task<Size> GetScaledRes()
{
await RefreshResolutions();
return scaledResolution;
}
async Task RefreshResolutions ()
{
if (inputResolution.IsEmpty || scaledResolution.IsEmpty)
{
inputResolution = await GetMediaResolutionCached.GetSizeAsync(inPath);
scaledResolution = InterpolateUtils.GetOutputResolution(inputResolution, false, true);
}
}
public void RefreshAlpha ()
{
try
{
bool alphaModel = model.supportsAlpha;
bool outputSupportsAlpha = (outMode == Interpolate.OutMode.ImgPng || outMode == Interpolate.OutMode.VidGif);
string ext = inputIsFrames ? Path.GetExtension(IoUtils.GetFilesSorted(inPath).First()).ToLower() : Path.GetExtension(inPath).ToLower();
alpha = (alphaModel && outputSupportsAlpha && (ext == ".gif" || ext == ".png" || ext == ".apng"));
Logger.Log($"RefreshAlpha: model.supportsAlpha = {alphaModel} - outputSupportsAlpha = {outputSupportsAlpha} - " +
$"input ext: {ext} => alpha = {alpha}", true);
}
catch (Exception e)
{
Logger.Log("RefreshAlpha Error: " + e.Message, true);
alpha = false;
}
}
public enum FrameType { Import, Interp, Both };
public void RefreshExtensions(FrameType type = FrameType.Both)
{
bool pngOutput = outMode == Interpolate.OutMode.ImgPng;
bool aviHqChroma = outMode == Interpolate.OutMode.VidAvi && Config.Get(Config.Key.aviColors) != "yuv420p";
bool proresHqChroma = outMode == Interpolate.OutMode.VidProRes && Config.GetInt(Config.Key.proResProfile) > 3;
bool forceHqChroma = pngOutput || aviHqChroma || proresHqChroma;
Logger.Log($"RefreshExtensions({type}) - alpha = {alpha} pngOutput = {pngOutput} aviHqChroma = {aviHqChroma} proresHqChroma = {proresHqChroma}", true);
if (alpha || forceHqChroma) // Force PNG if alpha is enabled, or output is not 4:2:0 subsampled
{
if(type == FrameType.Both || type == FrameType.Import)
framesExt = ".png";
if (type == FrameType.Both || type == FrameType.Interp)
interpExt = ".png";
}
else
{
if (type == FrameType.Both || type == FrameType.Import)
framesExt = (Config.GetBool(Config.Key.jpegFrames) ? ".jpg" : ".png");
if (type == FrameType.Both || type == FrameType.Interp)
interpExt = (Config.GetBool(Config.Key.jpegInterp) ? ".jpg" : ".png");
}
Logger.Log($"RefreshExtensions - Using '{framesExt}' for imported frames, using '{interpExt}' for interpolated frames", true);
}
public string Serialize ()
{
string s = $"INPATH|{inPath}\n";
s += $"OUTPATH|{outPath}\n";
s += $"AI|{ai.aiName}\n";
s += $"INFPSDETECTED|{inFpsDetected}\n";
s += $"INFPS|{inFps}\n";
s += $"OUTFPS|{outFps}\n";
s += $"INTERPFACTOR|{interpFactor}\n";
s += $"OUTMODE|{outMode}\n";
s += $"MODEL|{model.name}\n";
s += $"INPUTRES|{inputResolution.Width}x{inputResolution.Height}\n";
s += $"OUTPUTRES|{scaledResolution.Width}x{scaledResolution.Height}\n";
s += $"ALPHA|{alpha}\n";
s += $"STEPBYSTEP|{stepByStep}\n";
s += $"FRAMESEXT|{framesExt}\n";
s += $"INTERPEXT|{interpExt}\n";
return s;
}
}
}

View File

@@ -0,0 +1,63 @@
using Flowframes.IO;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
namespace Flowframes.Data
{
public class ModelCollection
{
public AI ai;
public List<ModelInfo> models;
public class ModelInfo
{
public AI ai;
public string name;
public string desc;
public string dir;
public bool supportsAlpha;
public bool isDefault;
public ModelInfo(AI ai, string name, string desc, string dir, bool supportsAlpha, bool isDefault)
{
this.ai = ai;
this.name = name;
this.desc = desc;
this.dir = dir;
this.supportsAlpha = supportsAlpha;
this.isDefault = isDefault;
}
public string GetUiString()
{
return $"{name} - {desc}{(supportsAlpha ? " (Supports Transparency)" : "")}{(isDefault ? " (Recommended)" : "")}";
}
public override string ToString()
{
return $"{name} - {desc} ({dir}){(supportsAlpha ? " (Supports Transparency)" : "")}{(isDefault ? " (Recommended)" : "")}";
}
}
public ModelCollection(AI ai, string jsonContentOrPath)
{
if (IoUtils.IsPathValid(jsonContentOrPath) && File.Exists(jsonContentOrPath))
jsonContentOrPath = File.ReadAllText(jsonContentOrPath);
models = new List<ModelInfo>();
dynamic data = JsonConvert.DeserializeObject(jsonContentOrPath);
foreach (var item in data)
{
bool alpha = false;
bool.TryParse((string)item.supportsAlpha, out alpha);
bool def = false;
bool.TryParse((string)item.isDefault, out def);
models.Add(new ModelInfo(ai, (string)item.name, (string)item.desc, (string)item.dir, alpha, def));
}
}
}
}

15
Code/Data/Padding.cs Normal file
View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Data
{
class Padding
{
public const int inputFrames = 9;
public const int inputFramesRenamed = 8;
public const int interpFrames = 8;
}
}

75
Code/Data/Paths.cs Normal file
View File

@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Flowframes.IO
{
class Paths
{
public const string framesDir = "frames";
public const string interpDir = "interp";
public const string chunksDir = "vchunks";
public const string resumeDir = "resumedata";
public const string scenesDir = "scenes";
public const string symlinksSuffix = "-symlinks";
public const string alphaSuffix = "-a";
public const string prevSuffix = "-previous";
public const string fpsLimitSuffix = "-fpsLimit";
public const string backupSuffix = ".bak";
public const string frameOrderPrefix = "frames";
public const string audioSuffix = "audio";
public const string audioVideoDir = "av";
public const string licensesDir = "licenses";
public static string GetFrameOrderFilename(float factor)
{
return $"{frameOrderPrefix}-{factor.ToStringDot()}x.ini";
}
public static string GetFrameOrderFilenameChunk (int from, int to)
{
return $"{frameOrderPrefix}-chunk-{from}-{to}.ini";
}
public static string GetExe()
{
return System.Reflection.Assembly.GetEntryAssembly().GetName().CodeBase.Replace("file:///", "");
}
public static string GetExeDir()
{
return AppDomain.CurrentDomain.BaseDirectory;
}
public static string GetVerPath()
{
return Path.Combine(GetDataPath(), "ver.ini");
}
public static string GetDataPath ()
{
string path = Path.Combine(GetExeDir(), "FlowframesData");
Directory.CreateDirectory(path);
return path;
}
public static string GetPkgPath()
{
string path = Path.Combine(GetDataPath(), "pkgs");
Directory.CreateDirectory(path);
return path;
}
public static string GetLogPath()
{
string path = Path.Combine(GetDataPath(), "logs");
Directory.CreateDirectory(path);
return path;
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Data
{
class PseudoUniqueFile
{
public string path;
public long filesize;
public PseudoUniqueFile (string pathArg, long filesizeArg)
{
path = pathArg;
filesize = filesizeArg;
}
}
}

56
Code/Data/ResumeState.cs Normal file
View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Data
{
public struct ResumeState
{
public bool autoEncode;
public int interpolatedInputFrames;
public ResumeState (bool autoEncArg, int lastInterpInFrameArg)
{
autoEncode = autoEncArg;
interpolatedInputFrames = lastInterpInFrameArg;
}
public ResumeState(string serializedData)
{
autoEncode = false;
interpolatedInputFrames = 0;
Dictionary<string, string> entries = new Dictionary<string, string>();
foreach (string line in serializedData.SplitIntoLines())
{
if (line.Length < 3) continue;
string[] keyValuePair = line.Split('|');
entries.Add(keyValuePair[0], keyValuePair[1]);
}
foreach (KeyValuePair<string, string> entry in entries)
{
switch (entry.Key)
{
case "AUTOENC": autoEncode = bool.Parse(entry.Value); break;
case "INTERPOLATEDINPUTFRAMES": interpolatedInputFrames = entry.Value.GetInt(); break;
}
}
}
public override string ToString ()
{
string s = $"AUTOENC|{autoEncode}\n";
if (!autoEncode)
{
s += $"INTERPOLATEDINPUTFRAMES|{interpolatedInputFrames}";
}
return s;
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Globalization;
namespace Flowframes.Data
{
class SubtitleTrack
{
public int streamIndex;
public string lang;
//public string langFriendly;
public string encoding;
public SubtitleTrack(int streamNum, string metaStr, string encodingStr)
{
streamIndex = streamNum;
lang = metaStr.Trim().Replace("_", ".").Replace(" ", ".");
//langFriendly = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(metaStr.ToLower().Trim().Replace("_", ".").Replace(" ", "."));
encoding = encodingStr.Trim();
}
}
}

122
Code/Data/VidExtraData.cs Normal file
View File

@@ -0,0 +1,122 @@
using Flowframes.IO;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Flowframes.Data
{
class VidExtraData
{
// Color
public string colorSpace = "";
public string colorRange = "";
public string colorTransfer = "";
public string colorPrimaries = "";
// Aspect Ratio
public string displayRatio = "";
private readonly string[] validColorSpaces = new string[] { "bt709", "bt470m", "bt470bg", "smpte170m", "smpte240m", "linear", "log100",
"log316", "iec61966-2-4", "bt1361e", "iec61966-2-1", "bt2020-10", "bt2020-12", "smpte2084", "smpte428", "arib-std-b67" };
public VidExtraData(string ffprobeOutput)
{
string[] lines = ffprobeOutput.SplitIntoLines();
if (!Config.GetBool(Config.Key.keepColorSpace, true))
return;
foreach (string line in lines)
{
if (line.Contains("color_range"))
{
colorRange = line.Split('=').LastOrDefault();
continue;
}
if (line.Contains("color_space"))
{
colorSpace = line.Split('=').LastOrDefault();
continue;
}
if (line.Contains("color_transfer"))
{
colorTransfer = line.Split('=').LastOrDefault();
continue;
}
if (line.Contains("color_primaries"))
{
colorPrimaries = line.Split('=').LastOrDefault();
continue;
}
if (line.Contains("display_aspect_ratio") && Config.GetBool(Config.Key.keepAspectRatio, true))
{
displayRatio = line.Split('=').LastOrDefault();
continue;
}
}
if (!validColorSpaces.Contains(colorSpace.Trim()))
{
Logger.Log($"Warning: Color Space '{colorSpace.Trim()}' not valid.", true, false, "ffmpeg");
colorSpace = "";
}
if (colorRange.Trim() == "unknown")
colorRange = "";
if (!validColorSpaces.Contains(colorTransfer.Trim()))
{
Logger.Log($"Warning: Color Transfer '{colorTransfer.Trim()}' not valid.", true, false, "ffmpeg");
colorTransfer = "";
}
else
{
colorTransfer = colorTransfer.Replace("bt470bg", "gamma28").Replace("bt470m", "gamma28"); // https://forum.videohelp.com/threads/394596-Color-Matrix
}
if (!validColorSpaces.Contains(colorPrimaries.Trim()))
{
Logger.Log($"Warning: Color Primaries '{colorPrimaries.Trim()}' not valid.", true, false, "ffmpeg");
colorPrimaries = "";
}
}
public bool HasAnyValues ()
{
if (!string.IsNullOrWhiteSpace(colorSpace))
return true;
if (!string.IsNullOrWhiteSpace(colorRange))
return true;
if (!string.IsNullOrWhiteSpace(colorTransfer))
return true;
if (!string.IsNullOrWhiteSpace(colorPrimaries))
return true;
return false;
}
public bool HasAllValues()
{
if (string.IsNullOrWhiteSpace(colorSpace))
return false;
if (string.IsNullOrWhiteSpace(colorRange))
return false;
if (string.IsNullOrWhiteSpace(colorTransfer))
return false;
if (string.IsNullOrWhiteSpace(colorPrimaries))
return false;
return true;
}
}
}

View File

@@ -0,0 +1,214 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using Flowframes.Data;
using System.Management.Automation;
namespace Flowframes
{
public static class ExtensionMethods
{
public static string TrimNumbers(this string s, bool allowDotComma = false)
{
if (!allowDotComma)
s = Regex.Replace(s, "[^0-9]", "");
else
s = Regex.Replace(s, "[^.,0-9]", "");
return s.Trim();
}
public static int GetInt(this TextBox textbox)
{
return GetInt(textbox.Text);
}
public static int GetInt(this ComboBox combobox)
{
return GetInt(combobox.Text);
}
public static int GetInt(this string str)
{
if (str.Length < 1 || str == null)
return 0;
try
{
return int.Parse(str.TrimNumbers());
}
catch (Exception e)
{
Logger.Log("Failed to parse \"" + str + "\" to int: " + e.Message, true);
return 0;
}
}
public static bool GetBool(this string str)
{
try
{
return bool.Parse(str);
}
catch
{
return false;
}
}
public static float GetFloat(this TextBox textbox)
{
return GetFloat(textbox.Text);
}
public static float GetFloat(this ComboBox combobox)
{
return GetFloat(combobox.Text);
}
public static float GetFloat(this string str)
{
if (str.Length < 1 || str == null)
return 0f;
string num = str.TrimNumbers(true).Replace(",", ".");
float value;
float.TryParse(num, NumberStyles.Any, CultureInfo.InvariantCulture, out value);
return value;
}
public static string Wrap(this string path, bool addSpaceFront = false, bool addSpaceEnd = false)
{
string s = "\"" + path + "\"";
if (addSpaceFront)
s = " " + s;
if (addSpaceEnd)
s = s + " ";
return s;
}
public static string GetParentDir(this string path)
{
return Directory.GetParent(path).FullName;
}
public static int RoundToInt(this float f)
{
return (int)Math.Round(f);
}
public static int Clamp(this int i, int min, int max)
{
if (i < min)
i = min;
if (i > max)
i = max;
return i;
}
public static string[] SplitIntoLines(this string str)
{
return Regex.Split(str, "\r\n|\r|\n");
}
public static string Trunc(this string inStr, int maxChars, bool addEllipsis = true)
{
string str = inStr.Length <= maxChars ? inStr : inStr.Substring(0, maxChars);
if(addEllipsis && inStr.Length > maxChars)
str += "…";
return str;
}
public static string StripBadChars(this string str)
{
string outStr = Regex.Replace(str, @"[^\u0020-\u007E]", string.Empty);
outStr = outStr.Remove("(").Remove(")").Remove("[").Remove("]").Remove("{").Remove("}").Remove("%").Remove("'").Remove("~");
return outStr;
}
public static string StripNumbers(this string str)
{
return new string(str.Where(c => c != '-' && (c < '0' || c > '9')).ToArray());
}
public static string Remove(this string str, string stringToRemove)
{
if (str == null || stringToRemove == null)
return str;
return str.Replace(stringToRemove, "");
}
public static string TrimWhitespaces(this string str)
{
if (str == null) return str;
var newString = new StringBuilder();
bool previousIsWhitespace = false;
for (int i = 0; i < str.Length; i++)
{
if (Char.IsWhiteSpace(str[i]))
{
if (previousIsWhitespace)
continue;
previousIsWhitespace = true;
}
else
{
previousIsWhitespace = false;
}
newString.Append(str[i]);
}
return newString.ToString();
}
public static string ReplaceLast (this string str, string stringToReplace, string replaceWith)
{
int place = str.LastIndexOf(stringToReplace);
if (place == -1)
return str;
return str.Remove(place, stringToReplace.Length).Insert(place, replaceWith);
}
public static string[] SplitBy (this string str, string splitBy)
{
return str.Split(new string[] { splitBy }, StringSplitOptions.None);
}
public static string RemoveComments (this string str)
{
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(",", ".");
}
public static string[] Split(this string str, string trimStr)
{
return str.Split(new string[] { trimStr }, StringSplitOptions.None);
}
public static bool MatchesWildcard(this string str, string wildcard)
{
WildcardPattern pattern = new WildcardPattern(wildcard);
return pattern.IsMatch(str);
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Win32Interop;
namespace Flowframes.Extensions
{
public static class ProcessExtensions
{
[Flags]
public enum ThreadAccess : int
{
TERMINATE = (0x0001),
SUSPEND_RESUME = (0x0002),
GET_CONTEXT = (0x0008),
SET_CONTEXT = (0x0010),
SET_INFORMATION = (0x0020),
QUERY_INFORMATION = (0x0040),
SET_THREAD_TOKEN = (0x0080),
IMPERSONATE = (0x0100),
DIRECT_IMPERSONATION = (0x0200)
}
[DllImport("kernel32.dll")]
static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
[DllImport("kernel32.dll")]
static extern uint SuspendThread(IntPtr hThread);
[DllImport("kernel32.dll")]
static extern int ResumeThread(IntPtr hThread);
public static void Suspend(this Process process)
{
foreach (ProcessThread thread in process.Threads)
{
var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
if (pOpenThread == IntPtr.Zero)
break;
SuspendThread(pOpenThread);
}
}
public static void Resume(this Process process)
{
foreach (ProcessThread thread in process.Threads)
{
var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
if (pOpenThread == IntPtr.Zero)
break;
ResumeThread(pOpenThread);
}
}
}
}

266
Code/Forms/BatchForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,266 @@
namespace Flowframes.Forms
{
partial class BatchForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(BatchForm));
this.titleLabel = new System.Windows.Forms.Label();
this.stopBtn = new System.Windows.Forms.Button();
this.runBtn = new System.Windows.Forms.Button();
this.addToQueue = new System.Windows.Forms.Button();
this.forceStopBtn = new System.Windows.Forms.Button();
this.clearBtn = new System.Windows.Forms.Button();
this.taskList = new System.Windows.Forms.ListBox();
this.clearSelectedBtn = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.panel1 = new System.Windows.Forms.Panel();
this.moveDownBtn = new System.Windows.Forms.Button();
this.moveUpBtn = new System.Windows.Forms.Button();
this.panel1.SuspendLayout();
this.SuspendLayout();
//
// titleLabel
//
this.titleLabel.AutoSize = true;
this.titleLabel.Font = new System.Drawing.Font("Yu Gothic UI", 21.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.titleLabel.ForeColor = System.Drawing.Color.White;
this.titleLabel.Location = new System.Drawing.Point(12, 9);
this.titleLabel.Margin = new System.Windows.Forms.Padding(3, 0, 3, 10);
this.titleLabel.Name = "titleLabel";
this.titleLabel.Size = new System.Drawing.Size(323, 40);
this.titleLabel.TabIndex = 1;
this.titleLabel.Text = "Batch Processing Queue";
//
// stopBtn
//
this.stopBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.stopBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.stopBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.stopBtn.ForeColor = System.Drawing.Color.White;
this.stopBtn.Location = new System.Drawing.Point(682, 351);
this.stopBtn.Name = "stopBtn";
this.stopBtn.Size = new System.Drawing.Size(250, 40);
this.stopBtn.TabIndex = 35;
this.stopBtn.Text = "Stop After Current Task";
this.stopBtn.UseVisualStyleBackColor = false;
this.stopBtn.Visible = false;
this.stopBtn.Click += new System.EventHandler(this.stopBtn_Click);
//
// runBtn
//
this.runBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.runBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.runBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.runBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.runBtn.ForeColor = System.Drawing.Color.White;
this.runBtn.Location = new System.Drawing.Point(682, 443);
this.runBtn.Name = "runBtn";
this.runBtn.Size = new System.Drawing.Size(250, 42);
this.runBtn.TabIndex = 36;
this.runBtn.Text = "Start";
this.runBtn.UseVisualStyleBackColor = false;
this.runBtn.Click += new System.EventHandler(this.runBtn_Click);
//
// addToQueue
//
this.addToQueue.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.addToQueue.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.addToQueue.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.addToQueue.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.addToQueue.ForeColor = System.Drawing.Color.White;
this.addToQueue.Location = new System.Drawing.Point(682, 65);
this.addToQueue.Name = "addToQueue";
this.addToQueue.Size = new System.Drawing.Size(250, 40);
this.addToQueue.TabIndex = 39;
this.addToQueue.Text = "Add Current Configuration To Queue";
this.addToQueue.UseVisualStyleBackColor = false;
this.addToQueue.Click += new System.EventHandler(this.addToQueue_Click);
//
// forceStopBtn
//
this.forceStopBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.forceStopBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.forceStopBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.forceStopBtn.ForeColor = System.Drawing.Color.White;
this.forceStopBtn.Location = new System.Drawing.Point(682, 397);
this.forceStopBtn.Name = "forceStopBtn";
this.forceStopBtn.Size = new System.Drawing.Size(250, 40);
this.forceStopBtn.TabIndex = 40;
this.forceStopBtn.Text = "Force Stop Now";
this.forceStopBtn.UseVisualStyleBackColor = false;
this.forceStopBtn.Visible = false;
this.forceStopBtn.Click += new System.EventHandler(this.forceStopBtn_Click);
//
// clearBtn
//
this.clearBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.clearBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.clearBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.clearBtn.ForeColor = System.Drawing.Color.White;
this.clearBtn.Location = new System.Drawing.Point(682, 157);
this.clearBtn.Name = "clearBtn";
this.clearBtn.Size = new System.Drawing.Size(250, 40);
this.clearBtn.TabIndex = 41;
this.clearBtn.Text = "Clear All Queue Entries";
this.clearBtn.UseVisualStyleBackColor = false;
this.clearBtn.Click += new System.EventHandler(this.clearBtn_Click);
//
// taskList
//
this.taskList.AllowDrop = true;
this.taskList.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.taskList.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.taskList.ForeColor = System.Drawing.Color.White;
this.taskList.FormattingEnabled = true;
this.taskList.ItemHeight = 16;
this.taskList.Location = new System.Drawing.Point(12, 65);
this.taskList.Name = "taskList";
this.taskList.Size = new System.Drawing.Size(664, 420);
this.taskList.TabIndex = 43;
this.taskList.SelectedIndexChanged += new System.EventHandler(this.taskList_SelectedIndexChanged);
this.taskList.DragDrop += new System.Windows.Forms.DragEventHandler(this.taskList_DragDrop);
this.taskList.DragEnter += new System.Windows.Forms.DragEventHandler(this.taskList_DragEnter);
//
// clearSelectedBtn
//
this.clearSelectedBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.clearSelectedBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.clearSelectedBtn.Enabled = false;
this.clearSelectedBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.clearSelectedBtn.ForeColor = System.Drawing.Color.White;
this.clearSelectedBtn.Location = new System.Drawing.Point(682, 111);
this.clearSelectedBtn.Name = "clearSelectedBtn";
this.clearSelectedBtn.Size = new System.Drawing.Size(250, 40);
this.clearSelectedBtn.TabIndex = 44;
this.clearSelectedBtn.Text = "Clear Selected Queue Entry";
this.clearSelectedBtn.UseVisualStyleBackColor = false;
this.clearSelectedBtn.Click += new System.EventHandler(this.clearSelectedBtn_Click);
//
// label1
//
this.label1.ForeColor = System.Drawing.Color.White;
this.label1.Location = new System.Drawing.Point(6, 6);
this.label1.Margin = new System.Windows.Forms.Padding(6);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(238, 111);
this.label1.TabIndex = 45;
this.label1.Text = "Tip:\r\nYou can also drag and drop multiple videos into the list.\r\nThey will be add" +
"ed to the queue using the interpolation settings set in the GUI.";
//
// panel1
//
this.panel1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(40)))), ((int)(((byte)(40)))));
this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.panel1.Controls.Add(this.label1);
this.panel1.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.panel1.Location = new System.Drawing.Point(682, 203);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(250, 142);
this.panel1.TabIndex = 46;
//
// moveDownBtn
//
this.moveDownBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.moveDownBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.moveDownBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.moveDownBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.moveDownBtn.ForeColor = System.Drawing.Color.White;
this.moveDownBtn.Location = new System.Drawing.Point(636, 445);
this.moveDownBtn.Margin = new System.Windows.Forms.Padding(0);
this.moveDownBtn.Name = "moveDownBtn";
this.moveDownBtn.Size = new System.Drawing.Size(35, 35);
this.moveDownBtn.TabIndex = 47;
this.moveDownBtn.Text = "↓";
this.moveDownBtn.UseVisualStyleBackColor = false;
this.moveDownBtn.Visible = false;
this.moveDownBtn.Click += new System.EventHandler(this.moveDownBtn_Click);
//
// moveUpBtn
//
this.moveUpBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.moveUpBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.moveUpBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.moveUpBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.moveUpBtn.ForeColor = System.Drawing.Color.White;
this.moveUpBtn.Location = new System.Drawing.Point(597, 445);
this.moveUpBtn.Margin = new System.Windows.Forms.Padding(0);
this.moveUpBtn.Name = "moveUpBtn";
this.moveUpBtn.Size = new System.Drawing.Size(35, 35);
this.moveUpBtn.TabIndex = 48;
this.moveUpBtn.Text = "↑";
this.moveUpBtn.UseVisualStyleBackColor = false;
this.moveUpBtn.Visible = false;
this.moveUpBtn.Click += new System.EventHandler(this.moveUpBtn_Click);
//
// BatchForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(32)))), ((int)(((byte)(32)))));
this.ClientSize = new System.Drawing.Size(944, 501);
this.Controls.Add(this.moveUpBtn);
this.Controls.Add(this.moveDownBtn);
this.Controls.Add(this.panel1);
this.Controls.Add(this.clearSelectedBtn);
this.Controls.Add(this.taskList);
this.Controls.Add(this.clearBtn);
this.Controls.Add(this.forceStopBtn);
this.Controls.Add(this.addToQueue);
this.Controls.Add(this.runBtn);
this.Controls.Add(this.stopBtn);
this.Controls.Add(this.titleLabel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MaximizeBox = false;
this.Name = "BatchForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Batch Processing";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.BatchForm_FormClosing);
this.Load += new System.EventHandler(this.BatchForm_Load);
this.panel1.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label titleLabel;
private System.Windows.Forms.Button stopBtn;
private System.Windows.Forms.Button runBtn;
private System.Windows.Forms.Button addToQueue;
private System.Windows.Forms.Button forceStopBtn;
private System.Windows.Forms.Button clearBtn;
private System.Windows.Forms.ListBox taskList;
private System.Windows.Forms.Button clearSelectedBtn;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Button moveDownBtn;
private System.Windows.Forms.Button moveUpBtn;
}
}

181
Code/Forms/BatchForm.cs Normal file
View File

@@ -0,0 +1,181 @@
using Flowframes.IO;
using Flowframes.Main;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using Flowframes.Data;
namespace Flowframes.Forms
{
public partial class BatchForm : Form
{
public BatchForm()
{
AutoScaleMode = AutoScaleMode.None;
InitializeComponent();
BatchProcessing.currentBatchForm = this;
}
private void addToQueue_Click(object sender, EventArgs e)
{
Program.batchQueue.Enqueue(Program.mainForm.GetCurrentSettings());
RefreshGui();
}
public void RefreshGui ()
{
taskList.Items.Clear();
for (int i = 0; i < Program.batchQueue.Count; i++)
{
InterpSettings entry = Program.batchQueue.ElementAt(i);
string niceOutMode = entry.outMode.ToString().ToUpper().Remove("VID").Remove("IMG");
string str = $"#{i+1}: {Path.GetFileName(entry.inPath).Trunc(40)} - {entry.inFps.GetFloat()} FPS => " +
$"{entry.interpFactor}x {entry.ai.aiNameShort} ({entry.model.name}) => {niceOutMode}";
taskList.Items.Add(str);
}
}
private void RefreshIndex ()
{
for(int i = 0; i < taskList.Items.Count; i++)
{
string[] split = taskList.Items[i].ToString().Split(':');
split[0] = $"#{i+1}";
taskList.Items[i] = string.Join(":", split);
}
}
public void SetWorking (bool working)
{
runBtn.Enabled = !working;
addToQueue.Enabled = !working;
stopBtn.Visible = working;
forceStopBtn.Visible = working;
stopBtn.Enabled = working;
}
private void BatchForm_Load(object sender, EventArgs e)
{
SetWorking(BatchProcessing.busy);
RefreshGui();
}
private void MoveListItem(int direction)
{
if (taskList.SelectedItem == null || taskList.SelectedIndex < 0)
return;
int newIndex = taskList.SelectedIndex + direction;
if (newIndex < 0 || newIndex >= taskList.Items.Count)
return; // Index out of range - nothing to do
object selected = taskList.SelectedItem;
taskList.Items.Remove(selected);
taskList.Items.Insert(newIndex, selected);
RefreshIndex();
taskList.SetSelected(newIndex, true);
}
private void runBtn_Click(object sender, EventArgs e)
{
stopBtn.Enabled = true;
BatchProcessing.Start();
Program.mainForm.WindowState = FormWindowState.Normal;
Program.mainForm.BringToFront();
}
private void clearBtn_Click(object sender, EventArgs e)
{
Program.batchQueue.Clear();
RefreshGui();
}
private void stopBtn_Click(object sender, EventArgs e)
{
stopBtn.Enabled = false;
BatchProcessing.stopped = true;
}
private void forceStopBtn_Click(object sender, EventArgs e)
{
Interpolate.Cancel("Force stopped by user.");
BatchProcessing.stopped = true;
}
private void BatchForm_FormClosing(object sender, FormClosingEventArgs e)
{
BatchProcessing.currentBatchForm = null;
}
private void clearSelectedBtn_Click(object sender, EventArgs e)
{
if (taskList.SelectedItem == null) return;
Queue<InterpSettings> temp = new Queue<InterpSettings>();
for(int i = 0; i < Program.batchQueue.Count; i++)
{
if (i != taskList.SelectedIndex)
temp.Enqueue(Program.batchQueue.ElementAt(i));
}
Program.batchQueue = temp;
RefreshGui();
}
private void taskList_SelectedIndexChanged(object sender, EventArgs e)
{
bool sel = taskList.SelectedItem != null;
clearSelectedBtn.Enabled = sel;
moveUpBtn.Visible = sel;
moveDownBtn.Visible = sel;
}
private void taskList_DragEnter(object sender, DragEventArgs e) { e.Effect = DragDropEffects.Copy; }
private async void taskList_DragDrop(object sender, DragEventArgs e)
{
string[] droppedPaths = (string[])e.Data.GetData(DataFormats.FileDrop);
await LoadDroppedPaths(droppedPaths);
}
public async Task LoadDroppedPaths (string[] droppedPaths, bool start = false)
{
foreach (string path in droppedPaths)
{
Logger.Log($"BatchForm: Dropped path: '{path}'", true);
InterpSettings current = Program.mainForm.GetCurrentSettings();
string outDir = (Config.GetInt("outFolderLoc") == 0) ? path.GetParentDir() : Config.Get("custOutDir").Trim();
current.UpdatePaths(path, outDir);
current.inFpsDetected = await IoUtils.GetFpsFolderOrVideo(path);
current.inFps = current.inFpsDetected;
current.outFps = current.inFps * current.interpFactor;
Program.batchQueue.Enqueue(current);
RefreshGui();
}
if (start)
runBtn_Click(null, null);
}
private void moveUpBtn_Click(object sender, EventArgs e)
{
MoveListItem(-1);
}
private void moveDownBtn_Click(object sender, EventArgs e)
{
MoveListItem(1);
}
}
}

3185
Code/Forms/BatchForm.resx Normal file

File diff suppressed because it is too large Load Diff

70
Code/Forms/BigPreviewForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,70 @@
namespace Flowframes.Forms
{
partial class BigPreviewForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(BigPreviewForm));
this.picBox = new System.Windows.Forms.PictureBox();
((System.ComponentModel.ISupportInitialize)(this.picBox)).BeginInit();
this.SuspendLayout();
//
// picBox
//
this.picBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.picBox.Image = global::Flowframes.Properties.Resources.baseline_image_white_48dp_4x_25pcAlpha;
this.picBox.Location = new System.Drawing.Point(0, 0);
this.picBox.Margin = new System.Windows.Forms.Padding(0);
this.picBox.Name = "picBox";
this.picBox.Size = new System.Drawing.Size(704, 601);
this.picBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
this.picBox.TabIndex = 0;
this.picBox.TabStop = false;
//
// BigPreviewForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(32)))), ((int)(((byte)(32)))));
this.ClientSize = new System.Drawing.Size(704, 601);
this.Controls.Add(this.picBox);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "BigPreviewForm";
this.Text = "Resizable Preview";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.BigPreviewForm_FormClosing);
this.Load += new System.EventHandler(this.BigPreviewForm_Load);
((System.ComponentModel.ISupportInitialize)(this.picBox)).EndInit();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.PictureBox picBox;
}
}

View File

@@ -0,0 +1,37 @@
using Flowframes.Main;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Flowframes.Ui;
namespace Flowframes.Forms
{
public partial class BigPreviewForm : Form
{
public BigPreviewForm()
{
InitializeComponent();
}
private void BigPreviewForm_Load(object sender, EventArgs e)
{
}
public void SetImage (Image img)
{
picBox.Image = img;
}
private void BigPreviewForm_FormClosing(object sender, FormClosingEventArgs e)
{
InterpolationProgress.bigPreviewForm = null;
}
}
}

File diff suppressed because it is too large Load Diff

356
Code/Forms/DebugForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,356 @@

namespace Flowframes.Forms
{
partial class DebugForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DebugForm));
this.titleLabel = new System.Windows.Forms.Label();
this.tabPage2 = new System.Windows.Forms.TabPage();
this.panel2 = new System.Windows.Forms.Panel();
this.label1 = new System.Windows.Forms.Label();
this.panel1 = new System.Windows.Forms.Panel();
this.configDataGrid = new System.Windows.Forms.DataGridView();
this.tabPage1 = new System.Windows.Forms.TabPage();
this.copyTextClipboardBtn = new HTAlt.WinForms.HTButton();
this.monospaceBtn = new HTAlt.WinForms.HTButton();
this.refreshBtn = new HTAlt.WinForms.HTButton();
this.textWrapBtn = new HTAlt.WinForms.HTButton();
this.clearLogsBtn = new HTAlt.WinForms.HTButton();
this.openLogFolderBtn = new HTAlt.WinForms.HTButton();
this.logFilesDropdown = new System.Windows.Forms.ComboBox();
this.logBox = new System.Windows.Forms.TextBox();
this.htTabControl1 = new HTAlt.WinForms.HTTabControl();
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
this.tabPage2.SuspendLayout();
this.panel2.SuspendLayout();
this.panel1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.configDataGrid)).BeginInit();
this.tabPage1.SuspendLayout();
this.htTabControl1.SuspendLayout();
this.SuspendLayout();
//
// titleLabel
//
this.titleLabel.AutoSize = true;
this.titleLabel.Font = new System.Drawing.Font("Yu Gothic UI", 21.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.titleLabel.ForeColor = System.Drawing.Color.White;
this.titleLabel.Location = new System.Drawing.Point(12, 9);
this.titleLabel.Margin = new System.Windows.Forms.Padding(3, 0, 3, 10);
this.titleLabel.Name = "titleLabel";
this.titleLabel.Size = new System.Drawing.Size(175, 40);
this.titleLabel.TabIndex = 2;
this.titleLabel.Text = "Debug Tools";
//
// tabPage2
//
this.tabPage2.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.tabPage2.Controls.Add(this.panel2);
this.tabPage2.Controls.Add(this.panel1);
this.tabPage2.Location = new System.Drawing.Point(4, 27);
this.tabPage2.Name = "tabPage2";
this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
this.tabPage2.Size = new System.Drawing.Size(912, 396);
this.tabPage2.TabIndex = 1;
this.tabPage2.Text = "Config Editor";
this.tabPage2.Enter += new System.EventHandler(this.tabPage2_Enter);
//
// panel2
//
this.panel2.Controls.Add(this.label1);
this.panel2.Location = new System.Drawing.Point(6, 6);
this.panel2.Name = "panel2";
this.panel2.Size = new System.Drawing.Size(900, 68);
this.panel2.TabIndex = 5;
//
// label1
//
this.label1.AutoSize = true;
this.label1.ForeColor = System.Drawing.SystemColors.Control;
this.label1.Location = new System.Drawing.Point(3, 3);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(415, 60);
this.label1.TabIndex = 0;
this.label1.Text = resources.GetString("label1.Text");
//
// panel1
//
this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panel1.Controls.Add(this.configDataGrid);
this.panel1.Location = new System.Drawing.Point(6, 80);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(900, 310);
this.panel1.TabIndex = 4;
//
// configDataGrid
//
this.configDataGrid.AllowUserToResizeColumns = false;
this.configDataGrid.AllowUserToResizeRows = false;
this.configDataGrid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.configDataGrid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells;
this.configDataGrid.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.AllCells;
this.configDataGrid.ClipboardCopyMode = System.Windows.Forms.DataGridViewClipboardCopyMode.EnableWithoutHeaderText;
this.configDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.configDataGrid.Location = new System.Drawing.Point(0, 0);
this.configDataGrid.MultiSelect = false;
this.configDataGrid.Name = "configDataGrid";
this.configDataGrid.Size = new System.Drawing.Size(900, 310);
this.configDataGrid.TabIndex = 0;
this.configDataGrid.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.configDataGrid_CellValueChanged);
this.configDataGrid.RowsAdded += new System.Windows.Forms.DataGridViewRowsAddedEventHandler(this.configDataGrid_RowsAdded);
this.configDataGrid.RowsRemoved += new System.Windows.Forms.DataGridViewRowsRemovedEventHandler(this.configDataGrid_RowsRemoved);
//
// tabPage1
//
this.tabPage1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.tabPage1.Controls.Add(this.copyTextClipboardBtn);
this.tabPage1.Controls.Add(this.monospaceBtn);
this.tabPage1.Controls.Add(this.refreshBtn);
this.tabPage1.Controls.Add(this.textWrapBtn);
this.tabPage1.Controls.Add(this.clearLogsBtn);
this.tabPage1.Controls.Add(this.openLogFolderBtn);
this.tabPage1.Controls.Add(this.logFilesDropdown);
this.tabPage1.Controls.Add(this.logBox);
this.tabPage1.Location = new System.Drawing.Point(4, 27);
this.tabPage1.Name = "tabPage1";
this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
this.tabPage1.Size = new System.Drawing.Size(912, 396);
this.tabPage1.TabIndex = 0;
this.tabPage1.Text = "Log Viewer";
//
// copyTextClipboardBtn
//
this.copyTextClipboardBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.copyTextClipboardBtn.FlatAppearance.BorderSize = 0;
this.copyTextClipboardBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.copyTextClipboardBtn.ForeColor = System.Drawing.Color.White;
this.copyTextClipboardBtn.Location = new System.Drawing.Point(600, 6);
this.copyTextClipboardBtn.Name = "copyTextClipboardBtn";
this.copyTextClipboardBtn.Size = new System.Drawing.Size(150, 23);
this.copyTextClipboardBtn.TabIndex = 80;
this.copyTextClipboardBtn.Text = "Copy Text To Clipboard";
this.copyTextClipboardBtn.UseVisualStyleBackColor = false;
this.copyTextClipboardBtn.Click += new System.EventHandler(this.copyTextClipboardBtn_Click);
//
// monospaceBtn
//
this.monospaceBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.monospaceBtn.ButtonImage = global::Flowframes.Properties.Resources.baseline_format_size_white_48dp;
this.monospaceBtn.DrawImage = true;
this.monospaceBtn.FlatAppearance.BorderSize = 0;
this.monospaceBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.monospaceBtn.ForeColor = System.Drawing.Color.White;
this.monospaceBtn.ImageSizeMode = HTAlt.WinForms.HTButton.ButtonImageSizeMode.Zoom;
this.monospaceBtn.Location = new System.Drawing.Point(241, 6);
this.monospaceBtn.Name = "monospaceBtn";
this.monospaceBtn.Size = new System.Drawing.Size(23, 23);
this.monospaceBtn.TabIndex = 79;
this.toolTip.SetToolTip(this.monospaceBtn, "Toggle Monospace Font");
this.monospaceBtn.UseVisualStyleBackColor = false;
this.monospaceBtn.Click += new System.EventHandler(this.monospaceBtn_Click);
//
// refreshBtn
//
this.refreshBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.refreshBtn.ButtonImage = global::Flowframes.Properties.Resources.baseline_refresh_white_48dp;
this.refreshBtn.DrawImage = true;
this.refreshBtn.FlatAppearance.BorderSize = 0;
this.refreshBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.refreshBtn.ForeColor = System.Drawing.Color.White;
this.refreshBtn.ImageSizeMode = HTAlt.WinForms.HTButton.ButtonImageSizeMode.Zoom;
this.refreshBtn.Location = new System.Drawing.Point(212, 6);
this.refreshBtn.Name = "refreshBtn";
this.refreshBtn.Size = new System.Drawing.Size(23, 23);
this.refreshBtn.TabIndex = 78;
this.toolTip.SetToolTip(this.refreshBtn, "Refresh");
this.refreshBtn.UseVisualStyleBackColor = false;
this.refreshBtn.Click += new System.EventHandler(this.refreshBtn_Click);
//
// textWrapBtn
//
this.textWrapBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.textWrapBtn.ButtonImage = global::Flowframes.Properties.Resources.baseline_wrap_text_white_48dp;
this.textWrapBtn.DrawImage = true;
this.textWrapBtn.FlatAppearance.BorderSize = 0;
this.textWrapBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.textWrapBtn.ForeColor = System.Drawing.Color.White;
this.textWrapBtn.ImageSizeMode = HTAlt.WinForms.HTButton.ButtonImageSizeMode.Zoom;
this.textWrapBtn.Location = new System.Drawing.Point(270, 5);
this.textWrapBtn.Name = "textWrapBtn";
this.textWrapBtn.Size = new System.Drawing.Size(23, 23);
this.textWrapBtn.TabIndex = 77;
this.toolTip.SetToolTip(this.textWrapBtn, "Toggle Text Wrap");
this.textWrapBtn.UseVisualStyleBackColor = false;
this.textWrapBtn.Click += new System.EventHandler(this.textWrapBtn_Click);
//
// clearLogsBtn
//
this.clearLogsBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.clearLogsBtn.FlatAppearance.BorderSize = 0;
this.clearLogsBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.clearLogsBtn.ForeColor = System.Drawing.Color.White;
this.clearLogsBtn.Location = new System.Drawing.Point(756, 6);
this.clearLogsBtn.Name = "clearLogsBtn";
this.clearLogsBtn.Size = new System.Drawing.Size(150, 23);
this.clearLogsBtn.TabIndex = 76;
this.clearLogsBtn.Text = "Clear Logs";
this.clearLogsBtn.UseVisualStyleBackColor = false;
this.clearLogsBtn.Click += new System.EventHandler(this.clearLogsBtn_Click);
//
// openLogFolderBtn
//
this.openLogFolderBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.openLogFolderBtn.FlatAppearance.BorderSize = 0;
this.openLogFolderBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.openLogFolderBtn.ForeColor = System.Drawing.Color.White;
this.openLogFolderBtn.Location = new System.Drawing.Point(444, 6);
this.openLogFolderBtn.Name = "openLogFolderBtn";
this.openLogFolderBtn.Size = new System.Drawing.Size(150, 23);
this.openLogFolderBtn.TabIndex = 75;
this.openLogFolderBtn.Text = "Open Log Folder";
this.openLogFolderBtn.UseVisualStyleBackColor = false;
this.openLogFolderBtn.Click += new System.EventHandler(this.openLogFolderBtn_Click);
//
// logFilesDropdown
//
this.logFilesDropdown.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.logFilesDropdown.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.logFilesDropdown.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.logFilesDropdown.ForeColor = System.Drawing.Color.White;
this.logFilesDropdown.FormattingEnabled = true;
this.logFilesDropdown.Location = new System.Drawing.Point(6, 6);
this.logFilesDropdown.Name = "logFilesDropdown";
this.logFilesDropdown.Size = new System.Drawing.Size(200, 23);
this.logFilesDropdown.TabIndex = 73;
this.logFilesDropdown.SelectedIndexChanged += new System.EventHandler(this.logFilesDropdown_SelectedIndexChanged);
//
// logBox
//
this.logBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.logBox.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.logBox.Cursor = System.Windows.Forms.Cursors.Arrow;
this.logBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.logBox.ForeColor = System.Drawing.Color.White;
this.logBox.Location = new System.Drawing.Point(6, 35);
this.logBox.MinimumSize = new System.Drawing.Size(4, 21);
this.logBox.Multiline = true;
this.logBox.Name = "logBox";
this.logBox.ReadOnly = true;
this.logBox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.logBox.Size = new System.Drawing.Size(900, 355);
this.logBox.TabIndex = 6;
this.logBox.TabStop = false;
this.logBox.WordWrap = false;
//
// htTabControl1
//
this.htTabControl1.AllowDrop = true;
this.htTabControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.htTabControl1.BackgroundColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
this.htTabControl1.BorderTabLineColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(122)))), ((int)(((byte)(204)))));
this.htTabControl1.Controls.Add(this.tabPage1);
this.htTabControl1.Controls.Add(this.tabPage2);
this.htTabControl1.DisableClose = true;
this.htTabControl1.DisableDragging = true;
this.htTabControl1.Font = new System.Drawing.Font("Segoe UI", 9F);
this.htTabControl1.HoverTabButtonColor = System.Drawing.Color.FromArgb(((int)(((byte)(82)))), ((int)(((byte)(176)))), ((int)(((byte)(239)))));
this.htTabControl1.HoverTabColor = System.Drawing.Color.FromArgb(((int)(((byte)(28)))), ((int)(((byte)(151)))), ((int)(((byte)(234)))));
this.htTabControl1.HoverUnselectedTabButtonColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85)))));
this.htTabControl1.Location = new System.Drawing.Point(12, 62);
this.htTabControl1.Name = "htTabControl1";
this.htTabControl1.Padding = new System.Drawing.Point(14, 4);
this.htTabControl1.SelectedIndex = 0;
this.htTabControl1.SelectedTabButtonColor = System.Drawing.Color.FromArgb(((int)(((byte)(28)))), ((int)(((byte)(151)))), ((int)(((byte)(234)))));
this.htTabControl1.SelectedTabColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(122)))), ((int)(((byte)(204)))));
this.htTabControl1.Size = new System.Drawing.Size(920, 427);
this.htTabControl1.TabIndex = 3;
this.htTabControl1.TextColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(255)))), ((int)(((byte)(255)))));
this.htTabControl1.UnderBorderTabLineColor = System.Drawing.Color.FromArgb(((int)(((byte)(67)))), ((int)(((byte)(67)))), ((int)(((byte)(70)))));
this.htTabControl1.UnselectedBorderTabLineColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70)))));
this.htTabControl1.UnselectedTabColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70)))));
this.htTabControl1.UpDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70)))));
this.htTabControl1.UpDownTextColor = System.Drawing.Color.FromArgb(((int)(((byte)(109)))), ((int)(((byte)(109)))), ((int)(((byte)(112)))));
//
// DebugForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(32)))), ((int)(((byte)(32)))));
this.ClientSize = new System.Drawing.Size(944, 501);
this.Controls.Add(this.htTabControl1);
this.Controls.Add(this.titleLabel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "DebugForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Debug Tools";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.DebugForm_FormClosing);
this.Shown += new System.EventHandler(this.DebugForm_Shown);
this.tabPage2.ResumeLayout(false);
this.panel2.ResumeLayout(false);
this.panel2.PerformLayout();
this.panel1.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.configDataGrid)).EndInit();
this.tabPage1.ResumeLayout(false);
this.tabPage1.PerformLayout();
this.htTabControl1.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label titleLabel;
private System.Windows.Forms.TabPage tabPage2;
private System.Windows.Forms.TabPage tabPage1;
private HTAlt.WinForms.HTTabControl htTabControl1;
private System.Windows.Forms.DataGridView configDataGrid;
private System.Windows.Forms.Panel panel2;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.TextBox logBox;
private System.Windows.Forms.ComboBox logFilesDropdown;
private HTAlt.WinForms.HTButton clearLogsBtn;
private HTAlt.WinForms.HTButton openLogFolderBtn;
private HTAlt.WinForms.HTButton textWrapBtn;
private HTAlt.WinForms.HTButton refreshBtn;
private System.Windows.Forms.ToolTip toolTip;
private HTAlt.WinForms.HTButton monospaceBtn;
private HTAlt.WinForms.HTButton copyTextClipboardBtn;
}
}

108
Code/Forms/DebugForm.cs Normal file
View File

@@ -0,0 +1,108 @@
using Flowframes.IO;
using Flowframes.Ui;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Flowframes.Forms
{
public partial class DebugForm : Form
{
public bool configGridChanged;
public DebugForm()
{
InitializeComponent();
}
private void DebugForm_Shown(object sender, EventArgs e)
{
configDataGrid.Font = new Font("Consolas", 9f);
RefreshLogs();
}
void RefreshLogs ()
{
DebugFormHelper.FillLogDropdown(logFilesDropdown);
}
private void DebugForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (!configGridChanged)
return;
DialogResult dialogResult = MessageBox.Show($"Save the modified configuration file?", "Save Configuration?", MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.Yes)
DebugFormHelper.SaveGrid(configDataGrid);
}
private void configDataGrid_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex > -1)
configGridChanged = true;
}
private void configDataGrid_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
{
configGridChanged = true;
}
private void configDataGrid_RowsRemoved(object sender, DataGridViewRowsRemovedEventArgs e)
{
configGridChanged = true;
}
private void logFilesDropdown_SelectedIndexChanged(object sender, EventArgs e)
{
DebugFormHelper.RefreshLogBox(logBox, logFilesDropdown.Text);
}
private void textWrapBtn_Click(object sender, EventArgs e)
{
logBox.WordWrap = !logBox.WordWrap;
}
private void openLogFolderBtn_Click(object sender, EventArgs e)
{
Process.Start("explorer.exe", Paths.GetLogPath());
}
private void clearLogsBtn_Click(object sender, EventArgs e)
{
foreach (string str in logFilesDropdown.Items)
File.WriteAllText(Path.Combine(Paths.GetLogPath(), str), "");
logFilesDropdown_SelectedIndexChanged(null, null);
}
private void refreshBtn_Click(object sender, EventArgs e)
{
RefreshLogs();
}
private void monospaceBtn_Click(object sender, EventArgs e)
{
DebugFormHelper.ToggleMonospace(logBox);
}
private void copyTextClipboardBtn_Click(object sender, EventArgs e)
{
Clipboard.SetText(logBox.Text);
}
private void tabPage2_Enter(object sender, EventArgs e)
{
DebugFormHelper.LoadGrid(configDataGrid);
configGridChanged = false;
}
}
}

3197
Code/Forms/DebugForm.resx Normal file

File diff suppressed because it is too large Load Diff

263
Code/Forms/ModelDownloadForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,263 @@

namespace Flowframes.Forms
{
partial class ModelDownloadForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ModelDownloadForm));
this.longProgBar = new HTAlt.WinForms.HTProgressBar();
this.downloadModelsBtn = new HTAlt.WinForms.HTButton();
this.titleLabel = new System.Windows.Forms.Label();
this.progressCircle = new CircularProgressBar.CircularProgressBar();
this.label39 = new System.Windows.Forms.Label();
this.closeBtn = new HTAlt.WinForms.HTButton();
this.cancelBtn = new HTAlt.WinForms.HTButton();
this.statusLabel = new System.Windows.Forms.Label();
this.rifeCuda = new System.Windows.Forms.CheckBox();
this.rifeNcnn = new System.Windows.Forms.CheckBox();
this.dainNcnn = new System.Windows.Forms.CheckBox();
this.flavrCuda = new System.Windows.Forms.CheckBox();
this.SuspendLayout();
//
// longProgBar
//
this.longProgBar.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.longProgBar.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.longProgBar.BorderThickness = 0;
this.longProgBar.Location = new System.Drawing.Point(12, 274);
this.longProgBar.Name = "longProgBar";
this.longProgBar.Size = new System.Drawing.Size(600, 15);
this.longProgBar.TabIndex = 34;
//
// downloadModelsBtn
//
this.downloadModelsBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.downloadModelsBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.downloadModelsBtn.FlatAppearance.BorderSize = 0;
this.downloadModelsBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.downloadModelsBtn.ForeColor = System.Drawing.Color.White;
this.downloadModelsBtn.Location = new System.Drawing.Point(12, 239);
this.downloadModelsBtn.Name = "downloadModelsBtn";
this.downloadModelsBtn.Size = new System.Drawing.Size(150, 23);
this.downloadModelsBtn.TabIndex = 87;
this.downloadModelsBtn.Text = "Download Model Files";
this.downloadModelsBtn.UseVisualStyleBackColor = false;
this.downloadModelsBtn.Click += new System.EventHandler(this.downloadModelsBtn_Click);
//
// titleLabel
//
this.titleLabel.AutoSize = true;
this.titleLabel.Font = new System.Drawing.Font("Yu Gothic UI", 21.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.titleLabel.ForeColor = System.Drawing.Color.White;
this.titleLabel.Location = new System.Drawing.Point(12, 9);
this.titleLabel.Margin = new System.Windows.Forms.Padding(3, 0, 3, 10);
this.titleLabel.Name = "titleLabel";
this.titleLabel.Size = new System.Drawing.Size(262, 40);
this.titleLabel.TabIndex = 89;
this.titleLabel.Text = "Model Downloader";
//
// progressCircle
//
this.progressCircle.AnimationFunction = WinFormAnimation.KnownAnimationFunctions.Liner;
this.progressCircle.AnimationSpeed = 500;
this.progressCircle.BackColor = System.Drawing.Color.Transparent;
this.progressCircle.Font = new System.Drawing.Font("Microsoft Sans Serif", 72F, System.Drawing.FontStyle.Bold);
this.progressCircle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.progressCircle.InnerColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(32)))), ((int)(((byte)(32)))));
this.progressCircle.InnerMargin = 2;
this.progressCircle.InnerWidth = -1;
this.progressCircle.Location = new System.Drawing.Point(572, 12);
this.progressCircle.MarqueeAnimationSpeed = 2000;
this.progressCircle.Name = "progressCircle";
this.progressCircle.OuterColor = System.Drawing.Color.Gray;
this.progressCircle.OuterMargin = -21;
this.progressCircle.OuterWidth = 21;
this.progressCircle.ProgressColor = System.Drawing.Color.LimeGreen;
this.progressCircle.ProgressWidth = 8;
this.progressCircle.SecondaryFont = new System.Drawing.Font("Microsoft Sans Serif", 36F);
this.progressCircle.Size = new System.Drawing.Size(40, 40);
this.progressCircle.StartAngle = 270;
this.progressCircle.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
this.progressCircle.SubscriptColor = System.Drawing.Color.FromArgb(((int)(((byte)(166)))), ((int)(((byte)(166)))), ((int)(((byte)(166)))));
this.progressCircle.SubscriptMargin = new System.Windows.Forms.Padding(10, -35, 0, 0);
this.progressCircle.SubscriptText = ".23";
this.progressCircle.SuperscriptColor = System.Drawing.Color.FromArgb(((int)(((byte)(166)))), ((int)(((byte)(166)))), ((int)(((byte)(166)))));
this.progressCircle.SuperscriptMargin = new System.Windows.Forms.Padding(10, 35, 0, 0);
this.progressCircle.SuperscriptText = "°C";
this.progressCircle.TabIndex = 90;
this.progressCircle.TextMargin = new System.Windows.Forms.Padding(8, 8, 0, 0);
this.progressCircle.Value = 33;
this.progressCircle.Visible = false;
//
// label39
//
this.label39.Location = new System.Drawing.Point(12, 69);
this.label39.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
this.label39.Name = "label39";
this.label39.Size = new System.Drawing.Size(600, 125);
this.label39.TabIndex = 91;
this.label39.Text = resources.GetString("label39.Text");
//
// closeBtn
//
this.closeBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.closeBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.closeBtn.FlatAppearance.BorderSize = 0;
this.closeBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.closeBtn.ForeColor = System.Drawing.Color.White;
this.closeBtn.Location = new System.Drawing.Point(168, 239);
this.closeBtn.Name = "closeBtn";
this.closeBtn.Size = new System.Drawing.Size(150, 23);
this.closeBtn.TabIndex = 92;
this.closeBtn.Text = "Close";
this.closeBtn.UseVisualStyleBackColor = false;
this.closeBtn.Click += new System.EventHandler(this.closeBtn_Click);
//
// cancelBtn
//
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.cancelBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.cancelBtn.FlatAppearance.BorderSize = 0;
this.cancelBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.cancelBtn.ForeColor = System.Drawing.Color.White;
this.cancelBtn.Location = new System.Drawing.Point(168, 239);
this.cancelBtn.Name = "cancelBtn";
this.cancelBtn.Size = new System.Drawing.Size(150, 23);
this.cancelBtn.TabIndex = 93;
this.cancelBtn.Text = "Cancel";
this.cancelBtn.UseVisualStyleBackColor = false;
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
//
// statusLabel
//
this.statusLabel.AutoSize = true;
this.statusLabel.ForeColor = System.Drawing.Color.White;
this.statusLabel.Location = new System.Drawing.Point(329, 244);
this.statusLabel.Margin = new System.Windows.Forms.Padding(8, 0, 3, 0);
this.statusLabel.Name = "statusLabel";
this.statusLabel.Size = new System.Drawing.Size(0, 13);
this.statusLabel.TabIndex = 94;
//
// rifeCuda
//
this.rifeCuda.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.rifeCuda.AutoSize = true;
this.rifeCuda.Checked = true;
this.rifeCuda.CheckState = System.Windows.Forms.CheckState.Checked;
this.rifeCuda.Location = new System.Drawing.Point(12, 203);
this.rifeCuda.Name = "rifeCuda";
this.rifeCuda.Size = new System.Drawing.Size(83, 17);
this.rifeCuda.TabIndex = 95;
this.rifeCuda.Text = "RIFE CUDA";
this.rifeCuda.UseVisualStyleBackColor = true;
//
// rifeNcnn
//
this.rifeNcnn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.rifeNcnn.AutoSize = true;
this.rifeNcnn.Checked = true;
this.rifeNcnn.CheckState = System.Windows.Forms.CheckState.Checked;
this.rifeNcnn.Location = new System.Drawing.Point(101, 203);
this.rifeNcnn.Name = "rifeNcnn";
this.rifeNcnn.Size = new System.Drawing.Size(84, 17);
this.rifeNcnn.TabIndex = 96;
this.rifeNcnn.Text = "RIFE NCNN";
this.rifeNcnn.UseVisualStyleBackColor = true;
//
// dainNcnn
//
this.dainNcnn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.dainNcnn.AutoSize = true;
this.dainNcnn.Checked = true;
this.dainNcnn.CheckState = System.Windows.Forms.CheckState.Checked;
this.dainNcnn.Location = new System.Drawing.Point(191, 203);
this.dainNcnn.Name = "dainNcnn";
this.dainNcnn.Size = new System.Drawing.Size(86, 17);
this.dainNcnn.TabIndex = 97;
this.dainNcnn.Text = "DAIN NCNN";
this.dainNcnn.UseVisualStyleBackColor = true;
//
// flavrCuda
//
this.flavrCuda.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.flavrCuda.AutoSize = true;
this.flavrCuda.Checked = true;
this.flavrCuda.CheckState = System.Windows.Forms.CheckState.Checked;
this.flavrCuda.Location = new System.Drawing.Point(283, 203);
this.flavrCuda.Name = "flavrCuda";
this.flavrCuda.Size = new System.Drawing.Size(93, 17);
this.flavrCuda.TabIndex = 98;
this.flavrCuda.Text = "FLAVR CUDA";
this.flavrCuda.UseVisualStyleBackColor = true;
//
// ModelDownloadForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(32)))), ((int)(((byte)(32)))));
this.ClientSize = new System.Drawing.Size(624, 301);
this.Controls.Add(this.flavrCuda);
this.Controls.Add(this.dainNcnn);
this.Controls.Add(this.rifeNcnn);
this.Controls.Add(this.rifeCuda);
this.Controls.Add(this.statusLabel);
this.Controls.Add(this.closeBtn);
this.Controls.Add(this.cancelBtn);
this.Controls.Add(this.label39);
this.Controls.Add(this.progressCircle);
this.Controls.Add(this.titleLabel);
this.Controls.Add(this.downloadModelsBtn);
this.Controls.Add(this.longProgBar);
this.ForeColor = System.Drawing.SystemColors.ControlLightLight;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "ModelDownloadForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Model Downloader";
this.Load += new System.EventHandler(this.ModelDownloadForm_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private HTAlt.WinForms.HTProgressBar longProgBar;
private HTAlt.WinForms.HTButton downloadModelsBtn;
private System.Windows.Forms.Label titleLabel;
private CircularProgressBar.CircularProgressBar progressCircle;
private System.Windows.Forms.Label label39;
private HTAlt.WinForms.HTButton closeBtn;
private HTAlt.WinForms.HTButton cancelBtn;
private System.Windows.Forms.Label statusLabel;
private System.Windows.Forms.CheckBox rifeCuda;
private System.Windows.Forms.CheckBox rifeNcnn;
private System.Windows.Forms.CheckBox dainNcnn;
private System.Windows.Forms.CheckBox flavrCuda;
}
}

View File

@@ -0,0 +1,84 @@
using Flowframes.MiscUtils;
using Microsoft.WindowsAPICodePack.Taskbar;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Flowframes.Forms
{
public partial class ModelDownloadForm : Form
{
public ModelDownloadForm()
{
InitializeComponent();
}
private void ModelDownloadForm_Load(object sender, EventArgs e)
{
}
public void SetWorking(bool state, bool allowCancel = true)
{
Logger.Log($"ModelDownloadForm SetWorking({state})", true);
SetProgress(-1);
Control[] controlsToDisable = new Control[] { downloadModelsBtn };
Control[] controlsToHide = new Control[] { closeBtn };
progressCircle.Visible = state;
foreach (Control c in controlsToDisable)
c.Enabled = !state;
foreach (Control c in controlsToHide)
c.Visible = !state;
Program.busy = state;
Program.mainForm.UpdateStepByStepControls();
}
public void SetProgress(int percent)
{
percent = percent.Clamp(0, 100);
TaskbarManager.Instance.SetProgressValue(percent, 100);
longProgBar.Value = percent;
longProgBar.Refresh();
}
public void SetStatus(string status)
{
statusLabel.Text = status;
}
public void SetDownloadBtnEnabled(bool state)
{
downloadModelsBtn.Enabled = state;
}
private void downloadModelsBtn_Click(object sender, EventArgs e)
{
ModelDownloadFormUtils.form = this;
bool rifeC = rifeCuda.Checked;
bool rifeN = rifeNcnn.Checked;
bool dainN = dainNcnn.Checked;
bool flavrC = flavrCuda.Checked;
ModelDownloadFormUtils.DownloadModels(rifeC, rifeN, dainN, flavrC);
}
private void cancelBtn_Click(object sender, EventArgs e)
{
ModelDownloadFormUtils.Cancel();
}
private void closeBtn_Click(object sender, EventArgs e)
{
ModelDownloadFormUtils.Cancel();
Close();
}
}
}

File diff suppressed because it is too large Load Diff

2538
Code/Forms/SettingsForm.Designer.cs generated Normal file

File diff suppressed because it is too large Load Diff

286
Code/Forms/SettingsForm.cs Normal file
View File

@@ -0,0 +1,286 @@
using Flowframes.IO;
using Flowframes.Media;
using Flowframes.MiscUtils;
using Microsoft.WindowsAPICodePack.Dialogs;
using System;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms;
#pragma warning disable IDE1006
namespace Flowframes.Forms
{
public partial class SettingsForm : Form
{
bool initialized = false;
public SettingsForm(int index = 0)
{
AutoScaleMode = AutoScaleMode.None;
InitializeComponent();
settingsTabList.SelectedIndex = index;
}
private void SettingsForm_Load(object sender, EventArgs e)
{
MinimumSize = new Size(Width, Height);
MaximumSize = new Size(Width, (Height * 1.5f).RoundToInt());
LoadSettings();
initialized = true;
Task.Run(() => CheckModelCacheSize());
}
public async Task CheckModelCacheSize ()
{
await Task.Delay(200);
long modelFoldersBytes = 0;
foreach (string modelFolder in ModelDownloader.GetAllModelFolders())
modelFoldersBytes += IoUtils.GetDirSize(modelFolder, true);
if (modelFoldersBytes > 1024 * 1024)
{
clearModelCacheBtn.Enabled = true;
clearModelCacheBtn.Text = $"Clear Model Cache ({FormatUtils.Bytes(modelFoldersBytes)})";
}
else
{
clearModelCacheBtn.Enabled = false;
}
}
private void SettingsForm_FormClosing(object sender, FormClosingEventArgs e)
{
SaveSettings();
Program.mainForm.UpdateStepByStepControls();
Program.mainForm.LoadQuickSettings();
}
void SaveSettings ()
{
// Clamp...
mp4Crf.Text = ((int)mp4Crf.Value).Clamp(0, 50).ToString();
vp9Crf.Text = ((int)vp9Crf.Value).Clamp(0, 63).ToString();
// Remove spaces...
torchGpus.Text = torchGpus.Text.Replace(" ", "");
ncnnGpus.Text = ncnnGpus.Text.Replace(" ", "");
// General
ConfigParser.SaveComboxIndex(processingMode);
ConfigParser.SaveGuiElement(maxVidHeight, ConfigParser.StringMode.Int);
ConfigParser.SaveComboxIndex(tempFolderLoc);
ConfigParser.SaveComboxIndex(outFolderLoc);
ConfigParser.SaveGuiElement(keepTempFolder);
ConfigParser.SaveGuiElement(exportNamePattern);
ConfigParser.SaveGuiElement(exportNamePatternLoop);
ConfigParser.SaveGuiElement(delLogsOnStartup);
ConfigParser.SaveGuiElement(clearLogOnInput);
ConfigParser.SaveGuiElement(disablePreview);
// Interpolation
ConfigParser.SaveGuiElement(keepAudio);
ConfigParser.SaveGuiElement(keepSubs);
ConfigParser.SaveGuiElement(keepMeta);
ConfigParser.SaveGuiElement(enableAlpha);
ConfigParser.SaveGuiElement(jpegFrames);
ConfigParser.SaveComboxIndex(dedupMode);
ConfigParser.SaveComboxIndex(mpdecimateMode);
ConfigParser.SaveGuiElement(dedupThresh);
ConfigParser.SaveGuiElement(enableLoop);
ConfigParser.SaveGuiElement(scnDetect);
ConfigParser.SaveGuiElement(scnDetectValue);
ConfigParser.SaveComboxIndex(sceneChangeFillMode);
ConfigParser.SaveComboxIndex(autoEncMode);
ConfigParser.SaveComboxIndex(autoEncBackupMode);
ConfigParser.SaveGuiElement(sbsAllowAutoEnc);
ConfigParser.SaveGuiElement(alwaysWaitForAutoEnc);
// AI
ConfigParser.SaveGuiElement(torchGpus);
ConfigParser.SaveGuiElement(ncnnGpus);
ConfigParser.SaveGuiElement(ncnnThreads);
ConfigParser.SaveGuiElement(uhdThresh);
ConfigParser.SaveGuiElement(rifeCudaFp16);
ConfigParser.SaveGuiElement(dainNcnnTilesize, ConfigParser.StringMode.Int);
// Export
ConfigParser.SaveGuiElement(minOutVidLength, ConfigParser.StringMode.Int);
ConfigParser.SaveGuiElement(maxFps);
ConfigParser.SaveComboxIndex(maxFpsMode);
ConfigParser.SaveComboxIndex(loopMode);
ConfigParser.SaveGuiElement(fixOutputDuration);
// Encoding
ConfigParser.SaveComboxIndex(mp4Enc);
ConfigParser.SaveComboxIndex(pixFmt);
Config.Set(mp4CrfConfigKey, mp4Crf.Value.ToString());
ConfigParser.SaveGuiElement(vp9Crf);
ConfigParser.SaveComboxIndex(proResProfile);
ConfigParser.SaveGuiElement(aviCodec);
ConfigParser.SaveGuiElement(aviColors);
ConfigParser.SaveGuiElement(gifColors);
ConfigParser.SaveGuiElement(gifDitherType);
ConfigParser.SaveGuiElement(imgSeqFormat);
// Debugging
ConfigParser.SaveComboxIndex(cmdDebugMode);
ConfigParser.SaveGuiElement(autoDedupFrames);
ConfigParser.SaveGuiElement(mdlBaseUrl);
ConfigParser.SaveGuiElement(ffEncThreads, ConfigParser.StringMode.Int);
ConfigParser.SaveGuiElement(ffEncPreset);
ConfigParser.SaveGuiElement(ffEncArgs);
}
void LoadSettings()
{
// General
ConfigParser.LoadComboxIndex(processingMode);
ConfigParser.LoadGuiElement(maxVidHeight);
ConfigParser.LoadComboxIndex(tempFolderLoc); ConfigParser.LoadGuiElement(tempDirCustom);
ConfigParser.LoadComboxIndex(outFolderLoc); ConfigParser.LoadGuiElement(custOutDir);
ConfigParser.LoadGuiElement(delLogsOnStartup);
ConfigParser.LoadGuiElement(keepTempFolder);
ConfigParser.LoadGuiElement(exportNamePattern);
ConfigParser.LoadGuiElement(exportNamePatternLoop);
ConfigParser.LoadGuiElement(clearLogOnInput);
ConfigParser.LoadGuiElement(disablePreview);
// Interpolation
ConfigParser.LoadGuiElement(keepAudio);
ConfigParser.LoadGuiElement(keepSubs);
ConfigParser.LoadGuiElement(keepMeta);
ConfigParser.LoadGuiElement(enableAlpha);
ConfigParser.LoadGuiElement(jpegFrames);
ConfigParser.LoadComboxIndex(dedupMode);
ConfigParser.LoadComboxIndex(mpdecimateMode);
ConfigParser.LoadGuiElement(dedupThresh);
ConfigParser.LoadGuiElement(enableLoop);
ConfigParser.LoadGuiElement(scnDetect);
ConfigParser.LoadGuiElement(scnDetectValue);
ConfigParser.LoadComboxIndex(sceneChangeFillMode);
ConfigParser.LoadComboxIndex(autoEncMode);
ConfigParser.LoadComboxIndex(autoEncBackupMode);
ConfigParser.LoadGuiElement(sbsAllowAutoEnc);
ConfigParser.LoadGuiElement(alwaysWaitForAutoEnc);
// AI
ConfigParser.LoadGuiElement(torchGpus);
ConfigParser.LoadGuiElement(ncnnGpus);
ConfigParser.LoadGuiElement(ncnnThreads);
ConfigParser.LoadGuiElement(uhdThresh);
ConfigParser.LoadGuiElement(rifeCudaFp16);
ConfigParser.LoadGuiElement(dainNcnnTilesize);
// Export
ConfigParser.LoadGuiElement(minOutVidLength);
ConfigParser.LoadGuiElement(maxFps);
ConfigParser.LoadComboxIndex(maxFpsMode);
ConfigParser.LoadComboxIndex(loopMode);
ConfigParser.LoadGuiElement(fixOutputDuration);
// Encoding
ConfigParser.LoadComboxIndex(mp4Enc);
ConfigParser.LoadComboxIndex(pixFmt);
ConfigParser.LoadGuiElement(vp9Crf);
ConfigParser.LoadComboxIndex(proResProfile);
ConfigParser.LoadGuiElement(aviCodec);
ConfigParser.LoadGuiElement(aviColors);
ConfigParser.LoadGuiElement(gifColors);
ConfigParser.LoadGuiElement(gifDitherType);
ConfigParser.LoadGuiElement(imgSeqFormat);
// Debugging
ConfigParser.LoadComboxIndex(cmdDebugMode);
ConfigParser.LoadGuiElement(autoDedupFrames);
ConfigParser.LoadGuiElement(mdlBaseUrl);
ConfigParser.LoadGuiElement(ffEncThreads);
ConfigParser.LoadGuiElement(ffEncPreset);
ConfigParser.LoadGuiElement(ffEncArgs);
}
private void tempFolderLoc_SelectedIndexChanged(object sender, EventArgs e)
{
tempDirBrowseBtn.Visible = tempFolderLoc.SelectedIndex == 4;
tempDirCustom.Visible = tempFolderLoc.SelectedIndex == 4;
}
private void outFolderLoc_SelectedIndexChanged(object sender, EventArgs e)
{
custOutDirBrowseBtn.Visible = outFolderLoc.SelectedIndex == 1;
custOutDir.Visible = outFolderLoc.SelectedIndex == 1;
}
private void tempDirBrowseBtn_Click(object sender, EventArgs e)
{
CommonOpenFileDialog dialog = new CommonOpenFileDialog { InitialDirectory = tempDirCustom.Text.Trim(), IsFolderPicker = true };
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
tempDirCustom.Text = dialog.FileName;
ConfigParser.SaveGuiElement(tempDirCustom);
}
private void custOutDirBrowseBtn_Click(object sender, EventArgs e)
{
CommonOpenFileDialog dialog = new CommonOpenFileDialog { InitialDirectory = custOutDir.Text.Trim(), IsFolderPicker = true };
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
custOutDir.Text = dialog.FileName;
ConfigParser.SaveGuiElement(custOutDir);
}
private void cmdDebugMode_SelectedIndexChanged(object sender, EventArgs e)
{
if (initialized && cmdDebugMode.SelectedIndex == 2)
MessageBox.Show("If you enable this, you need to close the CMD window manually after the process has finished, otherwise processing will be paused!", "Notice");
}
private void dedupMode_SelectedIndexChanged(object sender, EventArgs e)
{
dedupeSensLabel.Visible = dedupMode.SelectedIndex != 0;
magickDedupePanel.Visible = dedupMode.SelectedIndex == 1;
mpDedupePanel.Visible = dedupMode.SelectedIndex == 2;
}
private void clearModelCacheBtn_Click(object sender, EventArgs e)
{
ModelDownloader.DeleteAllModels();
clearModelCacheBtn.Text = "Clear Model Cache";
CheckModelCacheSize();
}
string mp4CrfConfigKey;
private void mp4Enc_SelectedIndexChanged(object sender, EventArgs e)
{
string text = mp4Enc.Text.ToUpper().Remove(" ");
if (text.Contains(FfmpegUtils.Codec.H264.ToString().ToUpper()))
mp4CrfConfigKey = "h264Crf";
if (text.Contains(FfmpegUtils.Codec.H265.ToString().ToUpper()))
mp4CrfConfigKey = "h265Crf";
if (text.Contains(FfmpegUtils.Codec.Av1.ToString().ToUpper()))
mp4CrfConfigKey = "av1Crf";
mp4Crf.Value = Config.GetInt(mp4CrfConfigKey);
}
private void modelDownloaderBtn_Click(object sender, EventArgs e)
{
new ModelDownloadForm().ShowDialog();
CheckModelCacheSize();
}
private void autoEncMode_SelectedIndexChanged(object sender, EventArgs e)
{
autoEncBlockPanel.Visible = autoEncMode.SelectedIndex == 0;
}
private async void resetBtn_Click(object sender, EventArgs e)
{
DialogResult dialog = MessageBox.Show($"Are you sure you want to reset the configuration?", "Are you sure?", MessageBoxButtons.YesNo);
if (dialog == DialogResult.No)
return;
await Config.Reset(3, this);
SettingsForm_Load(null, null);
}
}
}

3214
Code/Forms/SettingsForm.resx Normal file

File diff suppressed because it is too large Load Diff

118
Code/Forms/TimeoutForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,118 @@

namespace Flowframes.Forms
{
partial class TimeoutForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TimeoutForm));
this.mainLabel = new System.Windows.Forms.Label();
this.countdownLabel = new System.Windows.Forms.Label();
this.cancelActionBtn = new HTAlt.WinForms.HTButton();
this.skipCountdownBtn = new HTAlt.WinForms.HTButton();
this.SuspendLayout();
//
// mainLabel
//
this.mainLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.mainLabel.ForeColor = System.Drawing.Color.White;
this.mainLabel.Location = new System.Drawing.Point(12, 9);
this.mainLabel.Name = "mainLabel";
this.mainLabel.Size = new System.Drawing.Size(320, 23);
this.mainLabel.TabIndex = 0;
this.mainLabel.Text = "Waiting before running action...";
this.mainLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// countdownLabel
//
this.countdownLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.countdownLabel.ForeColor = System.Drawing.Color.White;
this.countdownLabel.Location = new System.Drawing.Point(12, 42);
this.countdownLabel.Margin = new System.Windows.Forms.Padding(3, 10, 3, 0);
this.countdownLabel.Name = "countdownLabel";
this.countdownLabel.Size = new System.Drawing.Size(320, 23);
this.countdownLabel.TabIndex = 1;
this.countdownLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// cancelActionBtn
//
this.cancelActionBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.cancelActionBtn.FlatAppearance.BorderSize = 0;
this.cancelActionBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.cancelActionBtn.ForeColor = System.Drawing.Color.White;
this.cancelActionBtn.Location = new System.Drawing.Point(12, 126);
this.cancelActionBtn.Name = "cancelActionBtn";
this.cancelActionBtn.Size = new System.Drawing.Size(320, 23);
this.cancelActionBtn.TabIndex = 39;
this.cancelActionBtn.Text = "Cancel Action";
this.cancelActionBtn.UseVisualStyleBackColor = false;
this.cancelActionBtn.Click += new System.EventHandler(this.cancelActionBtn_Click);
//
// skipCountdownBtn
//
this.skipCountdownBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.skipCountdownBtn.FlatAppearance.BorderSize = 0;
this.skipCountdownBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.skipCountdownBtn.ForeColor = System.Drawing.Color.White;
this.skipCountdownBtn.Location = new System.Drawing.Point(12, 94);
this.skipCountdownBtn.Margin = new System.Windows.Forms.Padding(3, 6, 3, 6);
this.skipCountdownBtn.Name = "skipCountdownBtn";
this.skipCountdownBtn.Size = new System.Drawing.Size(320, 23);
this.skipCountdownBtn.TabIndex = 40;
this.skipCountdownBtn.Text = "Run Action Now";
this.skipCountdownBtn.UseVisualStyleBackColor = false;
this.skipCountdownBtn.Click += new System.EventHandler(this.skipCountdownBtn_Click);
//
// TimeoutForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(32)))), ((int)(((byte)(32)))));
this.ClientSize = new System.Drawing.Size(344, 161);
this.Controls.Add(this.skipCountdownBtn);
this.Controls.Add(this.cancelActionBtn);
this.Controls.Add(this.countdownLabel);
this.Controls.Add(this.mainLabel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "TimeoutForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Timeout";
this.Load += new System.EventHandler(this.TimeoutForm_Load);
this.Shown += new System.EventHandler(this.TimeoutForm_Shown);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Label mainLabel;
private System.Windows.Forms.Label countdownLabel;
private HTAlt.WinForms.HTButton cancelActionBtn;
private HTAlt.WinForms.HTButton skipCountdownBtn;
}
}

77
Code/Forms/TimeoutForm.cs Normal file
View File

@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Flowframes.Forms
{
public partial class TimeoutForm : Form
{
string actionName = "";
int waitSeconds;
public delegate void ActionCallback();
public static ActionCallback actionCallback;
bool cancelCountdown = false;
public TimeoutForm(string action, ActionCallback callback, int waitSecs = 20, string windowTitle = "Timeout")
{
actionName = action;
Text = windowTitle;
actionCallback = callback;
waitSeconds = waitSecs;
InitializeComponent();
}
private void TimeoutForm_Load(object sender, EventArgs e)
{
}
private void TimeoutForm_Shown(object sender, EventArgs e)
{
mainLabel.Text = $"Waiting before running action \"{actionName}\"";
WaitAndRun();
}
async Task WaitAndRun ()
{
Show();
WindowState = FormWindowState.Normal;
Activate();
for (int i = waitSeconds; i > 0; i--)
{
countdownLabel.Text = $"{i}s";
await Task.Delay(1000);
}
if (cancelCountdown)
return;
actionCallback();
Close();
}
private void skipCountdownBtn_Click(object sender, EventArgs e)
{
cancelCountdown = true;
actionCallback();
Close();
}
private void cancelActionBtn_Click(object sender, EventArgs e)
{
cancelCountdown = true;
Close();
}
}
}

3185
Code/Forms/TimeoutForm.resx Normal file

File diff suppressed because it is too large Load Diff

208
Code/Forms/UpdaterForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,208 @@
namespace Flowframes.Forms
{
partial class UpdaterForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(UpdaterForm));
this.titleLabel = new System.Windows.Forms.Label();
this.label13 = new System.Windows.Forms.Label();
this.updatePatreonBtn = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.installedLabel = new System.Windows.Forms.Label();
this.latestLabel = new System.Windows.Forms.Label();
this.statusLabel = new System.Windows.Forms.Label();
this.downloadingLabel = new System.Windows.Forms.Label();
this.updateFreeBtn = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// titleLabel
//
this.titleLabel.AutoSize = true;
this.titleLabel.Font = new System.Drawing.Font("Yu Gothic UI", 21.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.titleLabel.ForeColor = System.Drawing.Color.White;
this.titleLabel.Location = new System.Drawing.Point(12, 9);
this.titleLabel.Margin = new System.Windows.Forms.Padding(3, 0, 3, 10);
this.titleLabel.Name = "titleLabel";
this.titleLabel.Size = new System.Drawing.Size(121, 40);
this.titleLabel.TabIndex = 2;
this.titleLabel.Text = "Updater";
//
// label13
//
this.label13.AutoSize = true;
this.label13.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label13.ForeColor = System.Drawing.Color.White;
this.label13.Location = new System.Drawing.Point(17, 67);
this.label13.Margin = new System.Windows.Forms.Padding(8, 8, 3, 0);
this.label13.Name = "label13";
this.label13.Size = new System.Drawing.Size(110, 16);
this.label13.TabIndex = 35;
this.label13.Text = "Installed Version:";
//
// updatePatreonBtn
//
this.updatePatreonBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.updatePatreonBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.updatePatreonBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.updatePatreonBtn.ForeColor = System.Drawing.Color.White;
this.updatePatreonBtn.Location = new System.Drawing.Point(12, 229);
this.updatePatreonBtn.Name = "updatePatreonBtn";
this.updatePatreonBtn.Size = new System.Drawing.Size(203, 40);
this.updatePatreonBtn.TabIndex = 36;
this.updatePatreonBtn.Text = "Download Patreon Version";
this.updatePatreonBtn.UseVisualStyleBackColor = true;
this.updatePatreonBtn.Click += new System.EventHandler(this.updatePatreonBtn_Click);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label1.ForeColor = System.Drawing.Color.White;
this.label1.Location = new System.Drawing.Point(16, 93);
this.label1.Margin = new System.Windows.Forms.Padding(8, 10, 3, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(96, 16);
this.label1.TabIndex = 37;
this.label1.Text = "Latest Version:";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label2.ForeColor = System.Drawing.Color.White;
this.label2.Location = new System.Drawing.Point(16, 119);
this.label2.Margin = new System.Windows.Forms.Padding(8, 10, 3, 0);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(48, 16);
this.label2.TabIndex = 38;
this.label2.Text = "Status:";
//
// installedLabel
//
this.installedLabel.AutoSize = true;
this.installedLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.installedLabel.ForeColor = System.Drawing.Color.White;
this.installedLabel.Location = new System.Drawing.Point(170, 67);
this.installedLabel.Margin = new System.Windows.Forms.Padding(8, 8, 3, 0);
this.installedLabel.Name = "installedLabel";
this.installedLabel.Size = new System.Drawing.Size(76, 16);
this.installedLabel.TabIndex = 39;
this.installedLabel.Text = "Loading...";
//
// latestLabel
//
this.latestLabel.AutoSize = true;
this.latestLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.latestLabel.ForeColor = System.Drawing.Color.White;
this.latestLabel.Location = new System.Drawing.Point(170, 93);
this.latestLabel.Margin = new System.Windows.Forms.Padding(8, 8, 3, 0);
this.latestLabel.Name = "latestLabel";
this.latestLabel.Size = new System.Drawing.Size(76, 16);
this.latestLabel.TabIndex = 40;
this.latestLabel.Text = "Loading...";
//
// statusLabel
//
this.statusLabel.AutoSize = true;
this.statusLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.statusLabel.ForeColor = System.Drawing.Color.White;
this.statusLabel.Location = new System.Drawing.Point(170, 119);
this.statusLabel.Margin = new System.Windows.Forms.Padding(8, 8, 3, 0);
this.statusLabel.Name = "statusLabel";
this.statusLabel.Size = new System.Drawing.Size(76, 16);
this.statusLabel.TabIndex = 41;
this.statusLabel.Text = "Loading...";
//
// downloadingLabel
//
this.downloadingLabel.AutoSize = true;
this.downloadingLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.downloadingLabel.ForeColor = System.Drawing.Color.White;
this.downloadingLabel.Location = new System.Drawing.Point(226, 241);
this.downloadingLabel.Margin = new System.Windows.Forms.Padding(8, 10, 3, 0);
this.downloadingLabel.Name = "downloadingLabel";
this.downloadingLabel.Size = new System.Drawing.Size(0, 16);
this.downloadingLabel.TabIndex = 42;
//
// updateFreeBtn
//
this.updateFreeBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.updateFreeBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.updateFreeBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.updateFreeBtn.ForeColor = System.Drawing.Color.White;
this.updateFreeBtn.Location = new System.Drawing.Point(221, 229);
this.updateFreeBtn.Name = "updateFreeBtn";
this.updateFreeBtn.Size = new System.Drawing.Size(203, 40);
this.updateFreeBtn.TabIndex = 43;
this.updateFreeBtn.Text = "Download Free Version";
this.updateFreeBtn.UseVisualStyleBackColor = true;
this.updateFreeBtn.Click += new System.EventHandler(this.updateFreeBtn_Click);
//
// UpdaterForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(32)))), ((int)(((byte)(32)))));
this.ClientSize = new System.Drawing.Size(624, 281);
this.Controls.Add(this.updateFreeBtn);
this.Controls.Add(this.downloadingLabel);
this.Controls.Add(this.statusLabel);
this.Controls.Add(this.latestLabel);
this.Controls.Add(this.installedLabel);
this.Controls.Add(this.label2);
this.Controls.Add(this.label1);
this.Controls.Add(this.updatePatreonBtn);
this.Controls.Add(this.label13);
this.Controls.Add(this.titleLabel);
this.ForeColor = System.Drawing.Color.White;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "UpdaterForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Updater";
this.Load += new System.EventHandler(this.UpdaterForm_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label titleLabel;
private System.Windows.Forms.Label label13;
private System.Windows.Forms.Button updatePatreonBtn;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label installedLabel;
private System.Windows.Forms.Label latestLabel;
private System.Windows.Forms.Label statusLabel;
private System.Windows.Forms.Label downloadingLabel;
private System.Windows.Forms.Button updateFreeBtn;
}
}

82
Code/Forms/UpdaterForm.cs Normal file
View File

@@ -0,0 +1,82 @@
using Flowframes.Data;
using Flowframes.Os;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Flowframes.Forms
{
public partial class UpdaterForm : Form
{
Version installed;
Version latestPat;
Version latestFree;
public UpdaterForm()
{
AutoScaleMode = AutoScaleMode.None;
InitializeComponent();
}
private async void UpdaterForm_Load(object sender, EventArgs e)
{
installed = Updater.GetInstalledVer();
latestPat = Updater.GetLatestVer(true);
latestFree = Updater.GetLatestVer(false);
installedLabel.Text = installed.ToString();
await Task.Delay(100);
latestLabel.Text = $"{latestPat} (Patreon/Beta) - {latestFree} (Free/Stable)";
if (Updater.CompareVersions(installed, latestFree) == Updater.VersionCompareResult.Equal)
{
statusLabel.Text = "Latest Free Version Is Installed.";
if (Updater.CompareVersions(installed, latestPat) == Updater.VersionCompareResult.Newer)
statusLabel.Text += "\nBeta Update Available On Patreon.";
return;
}
if (Updater.CompareVersions(installed, latestPat) == Updater.VersionCompareResult.Equal)
{
statusLabel.Text = "Latest Patreon/Beta Version Is Installed.";
return;
}
if (Updater.CompareVersions(installed, latestPat) == Updater.VersionCompareResult.Newer)
{
statusLabel.Text = "Update available on Patreon!";
if (Updater.CompareVersions(installed, latestFree) == Updater.VersionCompareResult.Newer)
statusLabel.Text = $"Beta Updates Available On Patreon and Itch.io.";
return;
}
}
float lastProg = -1f;
public void SetProgLabel (float prog, string str)
{
if (prog == lastProg) return;
lastProg = prog;
downloadingLabel.Text = str;
}
private void updatePatreonBtn_Click(object sender, EventArgs e)
{
string link = Updater.GetLatestVerLink(true);
if(!string.IsNullOrWhiteSpace(link))
Process.Start(link);
}
private void updateFreeBtn_Click(object sender, EventArgs e)
{
string link = Updater.GetLatestVerLink(false);
if (!string.IsNullOrWhiteSpace(link))
Process.Start(link);
}
}
}

2445
Code/Forms/UpdaterForm.resx Normal file

File diff suppressed because it is too large Load Diff

15
Code/IO/CfgStrings.cs Normal file
View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.IO
{
class CfgStrings
{
// public static string dedupMode = "dedupMode";
// public static string dedupThresh = "dedupThresh";
// public static string keepFrames = "keepFrames";
}
}

387
Code/IO/Config.cs Normal file
View File

@@ -0,0 +1,387 @@
using Flowframes.Forms;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
namespace Flowframes.IO
{
internal class Config
{
private static string configPath;
public static Dictionary<string, string> cachedValues = new Dictionary<string, string>();
public static void Init()
{
configPath = Path.Combine(Paths.GetDataPath(), "config.json");
IoUtils.CreateFileIfNotExists(configPath);
Reload();
}
public static async Task Reset(int retries = 3, SettingsForm settingsForm = null)
{
try
{
if (settingsForm != null)
settingsForm.Enabled = false;
File.Delete(configPath);
await Task.Delay(100);
cachedValues.Clear();
await Task.Delay(100);
if (settingsForm != null)
settingsForm.Enabled = true;
}
catch(Exception e)
{
retries -= 1;
Logger.Log($"Failed to reset config: {e.Message}. Retrying ({retries} attempts left).");
await Task.Delay(500);
await Reset(retries, settingsForm);
}
}
public static void Set(Key key, string value)
{
Set(key.ToString(), value);
}
public static void Set(string str, string value)
{
Reload();
cachedValues[str] = value;
WriteConfig();
}
public static void Set(Dictionary<string, string> keyValuePairs)
{
Reload();
foreach(KeyValuePair<string, string> entry in keyValuePairs)
cachedValues[entry.Key] = entry.Value;
WriteConfig();
}
private static void WriteConfig()
{
SortedDictionary<string, string> cachedValuesSorted = new SortedDictionary<string, string>(cachedValues);
File.WriteAllText(configPath, JsonConvert.SerializeObject(cachedValuesSorted, Formatting.Indented));
}
private static void Reload()
{
try
{
Dictionary<string, string> newDict = new Dictionary<string, string>();
Dictionary<string, string> deserializedConfig = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(configPath));
if (deserializedConfig == null)
deserializedConfig = new Dictionary<string, string>();
foreach (KeyValuePair<string, string> entry in deserializedConfig)
newDict.Add(entry.Key, entry.Value);
cachedValues = newDict; // Use temp dict and only copy it back if no exception was thrown
}
catch (Exception e)
{
Logger.Log($"Failed to reload config! {e.Message}", true);
}
}
// Get using fixed key
public static string Get(Key key, string defaultVal)
{
WriteIfDoesntExist(key.ToString(), defaultVal);
return Get(key);
}
// Get using string
public static string Get(string key, string defaultVal)
{
WriteIfDoesntExist(key, defaultVal);
return Get(key);
}
public static string Get(Key key, Type type = Type.String)
{
return Get(key.ToString(), type);
}
public static string Get(string key, Type type = Type.String)
{
string keyStr = key.ToString();
try
{
if (cachedValues.ContainsKey(keyStr))
return cachedValues[keyStr];
return WriteDefaultValIfExists(key.ToString(), type);
}
catch (Exception e)
{
Logger.Log($"Failed to get {keyStr.Wrap()} from config! {e.Message}");
}
return null;
}
#region Get Bool
public static bool GetBool(Key key)
{
return Get(key, Type.Bool).GetBool();
}
public static bool GetBool(Key key, bool defaultVal = false)
{
WriteIfDoesntExist(key.ToString(), (defaultVal ? "True" : "False"));
return Get(key, Type.Bool).GetBool();
}
public static bool GetBool(string key)
{
return Get(key, Type.Bool).GetBool();
}
public static bool GetBool(string key, bool defaultVal)
{
WriteIfDoesntExist(key.ToString(), (defaultVal ? "True" : "False"));
return bool.Parse(Get(key, Type.Bool));
}
#endregion
#region Get Int
public static int GetInt(Key key)
{
return Get(key, Type.Int).GetInt();
}
public static int GetInt(Key key, int defaultVal)
{
WriteIfDoesntExist(key.ToString(), defaultVal.ToString());
return GetInt(key);
}
public static int GetInt(string key)
{
return Get(key, Type.Int).GetInt();
}
public static int GetInt(string key, int defaultVal)
{
WriteIfDoesntExist(key.ToString(), defaultVal.ToString());
return GetInt(key);
}
#endregion
#region Get Float
public static float GetFloat(Key key)
{
return float.Parse(Get(key, Type.Float), CultureInfo.InvariantCulture);
}
public static float GetFloat(Key key, float defaultVal)
{
WriteIfDoesntExist(key.ToString(), defaultVal.ToStringDot());
return float.Parse(Get(key, Type.Float), CultureInfo.InvariantCulture);
}
public static float GetFloat(string key)
{
return float.Parse(Get(key, Type.Float), CultureInfo.InvariantCulture);
}
public static float GetFloat(string key, float defaultVal)
{
WriteIfDoesntExist(key.ToString(), defaultVal.ToStringDot());
return float.Parse(Get(key, Type.Float), CultureInfo.InvariantCulture);
}
public static string GetFloatString (Key key)
{
return Get(key, Type.Float).Replace(",", ".");
}
public static string GetFloatString(string key)
{
return Get(key, Type.Float).Replace(",", ".");
}
#endregion
static void WriteIfDoesntExist (string key, string val)
{
if (cachedValues.ContainsKey(key.ToString()))
return;
Set(key, val);
}
public enum Type { String, Int, Float, Bool }
private static string WriteDefaultValIfExists(string keyStr, Type type)
{
Key key;
try
{
key = (Key)Enum.Parse(typeof(Key), keyStr);
}
catch
{
return WriteDefault(keyStr, "");
}
if (key == Key.maxVidHeight) return WriteDefault(key, "2160");
if (key == Key.delLogsOnStartup) return WriteDefault(key, "True");
if (key == Key.clearLogOnInput) return WriteDefault(key, "True");
if (key == Key.tempDirCustom) return WriteDefault(key, "D:/");
if (key == Key.exportNamePattern) return WriteDefault(key, "[NAME]-[FACTOR]x-[AI]-[MODEL]-[FPS]fps");
if (key == Key.exportNamePatternLoop) return WriteDefault(key, "-Loop[LOOPS]");
// Interpolation
if (key == Key.dedupThresh) return WriteDefault(key, "2");
if (key == Key.keepAudio) return WriteDefault(key, "True");
if (key == Key.keepSubs) return WriteDefault(key, "True");
if (key == Key.keepMeta) return WriteDefault(key, "True");
if (key == Key.autoDedupFrames) return WriteDefault(key, "100");
if (key == Key.scnDetect) return WriteDefault(key, "True");
if (key == Key.scnDetectValue) return WriteDefault(key, "0.2");
if (key == Key.sceneChangeFillMode) return WriteDefault(key, "1");
if (key == Key.autoEncMode) return WriteDefault(key, "2");
if (key == Key.jpegFrames) return WriteDefault(key, "True");
// Video Export
if (key == Key.minOutVidLength) return WriteDefault(key, "5");
if (key == Key.h264Crf) return WriteDefault(key, "20");
if (key == Key.h265Crf) return WriteDefault(key, "24");
if (key == Key.av1Crf) return WriteDefault(key, "27");
if (key == Key.vp9Crf) return WriteDefault(key, "28");
if (key == Key.proResProfile) return WriteDefault(key, "2");
if (key == Key.aviCodec) return WriteDefault(key, "ffv1");
if (key == Key.imgSeqFormat) return WriteDefault(key, "PNG");
if (key == Key.aviColors) return WriteDefault(key, "yuv420p");
if (key == Key.gifColors) return WriteDefault(key, "128 (High)");
if (key == Key.gifDitherType) return WriteDefault(key, "bayer (Recommended)");
if (key == Key.minVidLength) return WriteDefault(key, "5");
// AI
if (key == Key.uhdThresh) return WriteDefault(key, "1600");
if (key == Key.torchGpus) return WriteDefault(key, "0");
if (key == Key.ncnnGpus) return WriteDefault(key, "0");
if (key == Key.ncnnThreads) return WriteDefault(key, "1");
if (key == Key.dainNcnnTilesize) return WriteDefault(key, "768");
// Debug / Other / Experimental
if (key == Key.mdlBaseUrl) return WriteDefault(key, "https://dl.nmkd-hz.de/flowframes/mdl/");
if (key == Key.ffEncPreset) return WriteDefault(key, "medium");
if (key == Key.sbsRunPreviousStepIfNeeded) return WriteDefault(key, "true");
if (type == Type.Int || type == Type.Float) return WriteDefault(key, "0"); // Write default int/float (0)
if (type == Type.Bool) return WriteDefault(key, "False"); // Write default bool (False)
return WriteDefault(key, "");
}
private static string WriteDefault(Key key, string def)
{
Set(key, def);
return def;
}
private static string WriteDefault(string key, string def)
{
Set(key, def);
return def;
}
public enum Key
{
aacBitrate,
askedForDevModeVersion,
aiCombox,
allowConsecutiveSceneChanges,
allowCustomInputRate,
allowSymlinkEncoding,
allowSymlinkImport,
alwaysWaitForAutoEnc,
autoDedupFrames,
autoEncBackupMode,
autoEncDebug,
autoEncMode,
autoEncSafeBufferFlavrCuda,
autoEncSafeBufferNcnn,
autoEncSafeBufferRifeCuda,
aviCodec,
aviColors,
clearLogOnInput,
cmdDebugMode,
compressedPyVersion,
dainNcnnTilesize,
dedupMode,
dedupThresh,
delLogsOnStartup,
disablePreview,
dupeScanDebug,
enableLoop,
exportNamePattern,
exportNamePatternLoop,
fetchModelsFromRepo,
ffEncArgs,
ffEncPreset,
ffEncThreads,
ffprobeFrameCount,
fixOutputDuration,
frameOrderDebug,
gifColors,
gifDitherType,
h264Crf,
h265Crf,
av1Crf,
imgSeqFormat,
jpegFrames,
jpegInterp,
keepAspectRatio,
keepAudio,
keepColorSpace,
keepMeta,
keepSubs,
keepTempFolder,
loopMode,
lowDiskSpaceCancelGb,
lowDiskSpacePauseGb,
maxFps,
maxFpsMode,
maxVidHeight,
mdlBaseUrl,
minOutVidLength,
minVidLength,
mp4Enc,
pixFmt,
mpdecimateMode,
ncnnGpus,
ncnnThreads,
opusBitrate,
processingMode,
proResProfile,
rifeCudaBufferSize,
rifeCudaFp16,
rifeNcnnUseTta,
sbsAllowAutoEnc,
sbsRunPreviousStepIfNeeded,
sceneChangeFillMode,
scnDetect,
scnDetectValue,
silentDevmodeCheck,
tempDirCustom,
tempFolderLoc,
torchGpus,
uhdThresh,
vp9Crf
}
}
}

80
Code/IO/ConfigParser.cs Normal file
View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Flowframes.IO
{
class ConfigParser
{
public enum StringMode { Any, Int, Float }
public static void SaveGuiElement(TextBox textbox, StringMode stringMode = StringMode.Any)
{
switch (stringMode)
{
case StringMode.Any: Config.Set(textbox.Name, textbox.Text); break;
case StringMode.Int: Config.Set(textbox.Name, textbox.Text.GetInt().ToString()); break;
case StringMode.Float: Config.Set(textbox.Name, textbox.Text.GetFloat().ToString()); break;
}
}
public static void SaveGuiElement(ComboBox comboBox, StringMode stringMode = StringMode.Any)
{
switch (stringMode)
{
case StringMode.Any: Config.Set(comboBox.Name, comboBox.Text); break;
case StringMode.Int: Config.Set(comboBox.Name, comboBox.Text.GetInt().ToString()); break;
case StringMode.Float: Config.Set(comboBox.Name, comboBox.Text.GetFloat().ToString().Replace(",", ".")); break;
}
}
public static void SaveGuiElement(CheckBox checkbox)
{
Config.Set(checkbox.Name, checkbox.Checked.ToString());
}
public static void SaveGuiElement(NumericUpDown upDown, StringMode stringMode = StringMode.Any)
{
switch (stringMode)
{
case StringMode.Any: Config.Set(upDown.Name, ((float)upDown.Value).ToString().Replace(",", ".")); break;
case StringMode.Int: Config.Set(upDown.Name, ((int)upDown.Value).ToString()); break;
case StringMode.Float: Config.Set(upDown.Name, ((float)upDown.Value).ToString().Replace(",", ".")); ; break;
}
}
public static void SaveComboxIndex(ComboBox comboBox)
{
Config.Set(comboBox.Name, comboBox.SelectedIndex.ToString());
}
public static void LoadGuiElement(ComboBox comboBox, string suffix = "")
{
comboBox.Text = Config.Get(comboBox.Name) + suffix;
}
public static void LoadGuiElement(TextBox textbox, string suffix = "")
{
textbox.Text = Config.Get(textbox.Name) + suffix; ;
}
public static void LoadGuiElement(CheckBox checkbox)
{
checkbox.Checked = Config.GetBool(checkbox.Name);
}
public static void LoadGuiElement(NumericUpDown upDown)
{
upDown.Value = Convert.ToDecimal(Config.GetFloat(upDown.Name));
}
public static void LoadComboxIndex(ComboBox comboBox)
{
comboBox.SelectedIndex = Config.GetInt(comboBox.Name);
}
}
}

951
Code/IO/IoUtils.cs Normal file
View File

@@ -0,0 +1,951 @@
using Flowframes.Data;
using Flowframes.Magick;
using Flowframes.Main;
using Flowframes.Media;
using Flowframes.MiscUtils;
using Flowframes.Ui;
using Force.Crc32;
using ImageMagick;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Flowframes.IO
{
class IoUtils
{
public static Image GetImage(string path, bool allowMagickFallback = true, bool log = true)
{
try
{
using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read))
return Image.FromStream(stream);
}
catch
{
try
{
MagickImage img = new MagickImage(path);
Bitmap bitmap = img.ToBitmap();
if(log)
Logger.Log($"GetImage: Native image reading for '{Path.GetFileName(path)}' failed - Using Magick.NET fallback instead.", true);
return bitmap;
}
catch (Exception e)
{
if (log)
Logger.Log($"GetImage failed: {e.Message}", true);
return null;
}
}
}
public static string[] ReadLines(string path)
{
List<string> lines = new List<string>();
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 0x1000, FileOptions.SequentialScan))
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
string line;
while ((line = reader.ReadLine()) != null)
lines.Add(line);
}
return lines.ToArray();
}
public static bool IsPathDirectory(string path)
{
if (path == null)
throw new ArgumentNullException("path");
path = path.Trim();
if (Directory.Exists(path))
return true;
if (File.Exists(path))
return false;
if (new string[2] {"\\", "/"}.Any((string x) => path.EndsWith(x)))
return true;
return string.IsNullOrWhiteSpace(Path.GetExtension(path));
}
public static bool IsFileValid(string path)
{
if (path == null)
return false;
if (!File.Exists(path))
return false;
return true;
}
public static bool IsDirValid(string path)
{
if (path == null)
return false;
if (!Directory.Exists(path))
return false;
return true;
}
public static bool IsPathValid(string path)
{
if (path == null)
return false;
if (IsPathDirectory(path))
return IsDirValid(path);
else
return IsFileValid(path);
}
public static void CopyDir(string sourceDirectoryName, string targetDirectoryName, bool move = false)
{
Directory.CreateDirectory(targetDirectoryName);
DirectoryInfo source = new DirectoryInfo(sourceDirectoryName);
DirectoryInfo target = new DirectoryInfo(targetDirectoryName);
CopyWork(source, target, move);
}
private static void CopyWork(DirectoryInfo source, DirectoryInfo target, bool move)
{
DirectoryInfo[] directories = source.GetDirectories();
foreach (DirectoryInfo directoryInfo in directories)
{
CopyWork(directoryInfo, target.CreateSubdirectory(directoryInfo.Name), move);
}
FileInfo[] files = source.GetFiles();
foreach (FileInfo fileInfo in files)
{
if (move)
fileInfo.MoveTo(Path.Combine(target.FullName, fileInfo.Name));
else
fileInfo.CopyTo(Path.Combine(target.FullName, fileInfo.Name), overwrite: true);
}
}
/// <summary>
/// Async version of DeleteContentsOfDir, won't block main thread.
/// </summary>
public static async Task<bool> DeleteContentsOfDirAsync(string path)
{
ulong taskId = BackgroundTaskManager.Add($"DeleteContentsOfDirAsync {path}");
bool returnVal = await Task.Run(async () => { return DeleteContentsOfDir(path); });
BackgroundTaskManager.Remove(taskId);
return returnVal;
}
/// <summary>
/// Delete everything inside a directory except the dir itself.
/// </summary>
public static bool DeleteContentsOfDir(string path)
{
try
{
DeleteIfExists(path);
Directory.CreateDirectory(path);
return true;
}
catch(Exception e)
{
Logger.Log("DeleteContentsOfDir Error: " + e.Message, true);
return false;
}
}
public static void ReplaceInFilenamesDir(string dir, string textToFind, string textToReplace, bool recursive = true, string wildcard = "*")
{
int counter = 1;
DirectoryInfo d = new DirectoryInfo(dir);
FileInfo[] files;
if (recursive)
files = d.GetFiles(wildcard, SearchOption.AllDirectories);
else
files = d.GetFiles(wildcard, SearchOption.TopDirectoryOnly);
foreach (FileInfo file in files)
{
ReplaceInFilename(file.FullName, textToFind, textToReplace);
counter++;
}
}
public static void ReplaceInFilename(string path, string textToFind, string textToReplace)
{
string ext = Path.GetExtension(path);
string newFilename = Path.GetFileNameWithoutExtension(path).Replace(textToFind, textToReplace);
string targetPath = Path.Combine(Path.GetDirectoryName(path), newFilename + ext);
if (File.Exists(targetPath))
{
//Program.Print("Skipped " + path + " because a file with the target name already exists.");
return;
}
File.Move(path, targetPath);
}
public static int GetAmountOfFiles (string path, bool recursive, string wildcard = "*")
{
try
{
DirectoryInfo d = new DirectoryInfo(path);
FileInfo[] files = null;
if (recursive)
files = d.GetFiles(wildcard, SearchOption.AllDirectories);
else
files = d.GetFiles(wildcard, SearchOption.TopDirectoryOnly);
return files.Length;
}
catch
{
return 0;
}
}
static bool TryCopy(string source, string target, bool overwrite = true, bool showLog = false)
{
try
{
File.Copy(source, target, overwrite);
}
catch (Exception e)
{
if(showLog)
Logger.Log($"Failed to move '{source}' to '{target}' (Overwrite: {overwrite}): {e.Message}, !showLog");
return false;
}
return true;
}
public static bool TryMove(string source, string target, bool overwrite = true, bool showLog = false)
{
try
{
if (overwrite && File.Exists(target))
File.Delete(target);
File.Move(source, target);
}
catch (Exception e)
{
Logger.Log($"Failed to move '{source}' to '{target}' (Overwrite: {overwrite}): {e.Message}", !showLog);
return false;
}
return true;
}
public static async Task RenameCounterDir(string path, int startAt = 0, int zPad = 8, bool inverse = false)
{
Stopwatch sw = new Stopwatch();
sw.Restart();
int counter = startAt;
DirectoryInfo d = new DirectoryInfo(path);
FileInfo[] files = d.GetFiles();
var filesSorted = files.OrderBy(n => n);
if (inverse)
filesSorted.Reverse();
foreach (FileInfo file in files)
{
string dir = new DirectoryInfo(file.FullName).Parent.FullName;
File.Move(file.FullName, Path.Combine(dir, counter.ToString().PadLeft(zPad, '0') + Path.GetExtension(file.FullName)));
counter++;
if (sw.ElapsedMilliseconds > 100)
{
await Task.Delay(1);
sw.Restart();
}
}
}
public static async Task ReverseRenaming(string basePath, Dictionary<string, string> oldNewMap) // Relative -> absolute paths
{
Dictionary<string, string> absPaths = oldNewMap.ToDictionary(x => Path.Combine(basePath, x.Key), x => Path.Combine(basePath, x.Value));
await ReverseRenaming(absPaths);
}
public static async Task ReverseRenaming(Dictionary<string, string> oldNewMap) // Takes absolute paths only
{
if (oldNewMap == null || oldNewMap.Count < 1) return;
int counter = 0;
int failCount = 0;
foreach (KeyValuePair<string, string> pair in oldNewMap)
{
bool success = TryMove(pair.Value, pair.Key);
if (!success)
failCount++;
if (failCount >= 100)
break;
counter++;
if (counter % 1000 == 0)
await Task.Delay(1);
}
}
public static async Task<Fraction> GetVideoFramerate (string path)
{
string[] preferFfmpegReadoutFormats = new string[] { ".gif", ".png", ".apng", ".webp" };
bool preferFfmpegReadout = preferFfmpegReadoutFormats.Contains(Path.GetExtension(path).ToLower());
Fraction fps = new Fraction();
try
{
fps = await FfmpegCommands.GetFramerate(path, preferFfmpegReadout);
Logger.Log("Detected FPS of " + Path.GetFileName(path) + " as " + fps + " FPS", true);
}
catch
{
Logger.Log("Failed to read FPS - Please enter it manually.");
}
return fps;
}
public static Fraction GetVideoFramerateForDir(string path)
{
Fraction fps = new Fraction();
try
{
string parentDir = path.GetParentDir();
string fpsFile = Path.Combine(parentDir, "fps.ini");
fps = new Fraction(float.Parse(ReadLines(fpsFile)[0]));
Logger.Log($"Got {fps} FPS from file: " + fpsFile);
Fraction guiFps = Program.mainForm.GetCurrentSettings().inFps;
DialogResult dialogResult = MessageBox.Show("A frame rate file has been found in the parent directory.\n\n" +
$"Click \"Yes\" to use frame rate from the file ({fps}) or \"No\" to use current FPS set in GUI ({guiFps})", "Load Frame Rate From fps.ini?", MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.Yes)
return fps;
else if (dialogResult == DialogResult.No)
return guiFps;
}
catch { }
return fps;
}
public static async Task<Size> GetVideoOrFramesRes (string path)
{
Size res = new Size();
try
{
if (!IsPathDirectory(path)) // If path is video
{
res = GetVideoRes(path);
}
else // Path is frame folder
{
Image thumb = await MainUiFunctions.GetThumbnail(path);
res = new Size(thumb.Width, thumb.Height);
}
}
catch (Exception e)
{
Logger.Log("GetVideoOrFramesRes Error: " + e.Message);
}
return res;
}
public static Size GetVideoRes (string path)
{
Size size = new Size(0, 0);
try
{
size = FfmpegCommands.GetSize(path);
Logger.Log($"Detected video size of {Path.GetFileName(path)} as {size.Width}x{size.Height}", true);
}
catch
{
Logger.Log("Failed to read video size!");
}
return size;
}
/// <summary>
/// Async (background thread) version of TryDeleteIfExists. Safe to run without awaiting.
/// </summary>
public static async Task<bool> TryDeleteIfExistsAsync(string path, int retries = 10)
{
string renamedPath = path;
try
{
if (IsPathDirectory(path))
{
while (Directory.Exists(renamedPath))
renamedPath += "_";
if (path != renamedPath)
Directory.Move(path, renamedPath);
}
else
{
while (File.Exists(renamedPath))
renamedPath += "_";
if (path != renamedPath)
File.Move(path, renamedPath);
}
path = renamedPath;
ulong taskId = BackgroundTaskManager.Add($"TryDeleteIfExistsAsync {path}");
bool returnVal = await Task.Run(async () => { return TryDeleteIfExists(path); });
BackgroundTaskManager.Remove(taskId);
return returnVal;
}
catch (Exception e)
{
Logger.Log($"TryDeleteIfExistsAsync Move Exception: {e.Message} [{retries} retries left]", true);
if (retries > 0)
{
await Task.Delay(2000);
retries -= 1;
return await TryDeleteIfExistsAsync(path, retries);
}
else
{
return false;
}
}
}
/// <summary>
/// Delete a path if it exists. Works for files and directories. Returns success status.
/// </summary>
public static bool TryDeleteIfExists(string path) // Returns true if no exception occurs
{
try
{
if (path == null)
return false;
DeleteIfExists(path);
return true;
}
catch (Exception e)
{
Logger.Log($"TryDeleteIfExists: Error trying to delete {path}: {e.Message}", true);
return false;
}
}
public static bool DeleteIfExists (string path, bool log = false) // Returns true if the file/dir exists
{
if(log)
Logger.Log($"DeleteIfExists({path})", true);
if (!IsPathDirectory(path) && File.Exists(path))
{
File.Delete(path);
return true;
}
if (IsPathDirectory(path) && Directory.Exists(path))
{
Directory.Delete(path, true);
return true;
}
return false;
}
/// <summary>
/// Add ".old" suffix to an existing file to avoid it getting overwritten. If one already exists, it will be ".old.old" etc.
/// </summary>
public static void RenameExistingFile(string path)
{
if (!File.Exists(path))
return;
try
{
string ext = Path.GetExtension(path);
string renamedPath = path;
while (File.Exists(renamedPath))
renamedPath = Path.ChangeExtension(renamedPath, null) + ".old" + ext;
File.Move(path, renamedPath);
}
catch(Exception e)
{
Logger.Log($"RenameExistingFile: Failed to rename '{path}': {e.Message}", true);
}
}
/// <summary>
/// Add ".old" suffix to an existing folder to avoid it getting overwritten. If one already exists, it will be ".old.old" etc.
/// </summary>
public static void RenameExistingFolder(string path)
{
if (!Directory.Exists(path))
return;
try
{
string renamedPath = path;
while (Directory.Exists(renamedPath))
renamedPath += ".old";
Directory.Move(path, renamedPath);
}
catch (Exception e)
{
Logger.Log($"RenameExistingFolder: Failed to rename '{path}': {e.Message}", true);
}
}
/// <summary>
/// Easily rename a file without needing to specify the full move path
/// </summary>
public static bool RenameFile (string path, string newName, bool alsoRenameExtension = false)
{
try
{
string dir = Path.GetDirectoryName(path);
string ext = Path.GetExtension(path);
string movePath = Path.Combine(dir, newName);
if (!alsoRenameExtension)
movePath += ext;
File.Move(path, movePath);
return true;
}
catch(Exception e)
{
Logger.Log($"Failed to rename '{path}' to '{newName}': {e.Message}", true);
return false;
}
}
public static async Task<string> GetCurrentExportFilename(bool fpsLimit, bool withExt)
{
InterpSettings curr = Interpolate.current;
string max = Config.Get(Config.Key.maxFps);
Fraction maxFps = max.Contains("/") ? new Fraction(max) : new Fraction(max.GetFloat());
float fps = fpsLimit ? maxFps.GetFloat() : curr.outFps.GetFloat();
if (curr.outMode == Interpolate.OutMode.VidGif && fps > 50f)
fps = 50f;
Size outRes = await InterpolateUtils.GetOutputResolution(curr.inPath, false, false);
string pattern = Config.Get(Config.Key.exportNamePattern);
string inName = Interpolate.current.inputIsFrames ? Path.GetFileName(curr.inPath) : Path.GetFileNameWithoutExtension(curr.inPath);
bool encodeBoth = Config.GetInt(Config.Key.maxFpsMode) == 0;
bool addSuffix = fpsLimit && (!pattern.Contains("[FPS]") && !pattern.Contains("[ROUNDFPS]")) && encodeBoth;
string filename = pattern;
filename = filename.Replace("[NAME]", inName);
filename = filename.Replace("[FULLNAME]", Path.GetFileName(curr.inPath));
filename = filename.Replace("[FACTOR]", curr.interpFactor.ToStringDot());
filename = filename.Replace("[AI]", curr.ai.aiNameShort.ToUpper());
filename = filename.Replace("[MODEL]", curr.model.name.Remove(" "));
filename = filename.Replace("[FPS]", fps.ToStringDot());
filename = filename.Replace("[ROUNDFPS]", fps.RoundToInt().ToString());
filename = filename.Replace("[RES]", $"{outRes.Width}x{outRes.Height}");
filename = filename.Replace("[H]", $"{outRes.Height}p");
if (addSuffix)
filename += Paths.fpsLimitSuffix;
if (withExt)
filename += FfmpegUtils.GetExt(curr.outMode);
return filename;
}
public static string GetHighestFrameNumPath (string path)
{
FileInfo highest = null;
int highestInt = -1;
foreach(FileInfo frame in new DirectoryInfo(path).GetFiles("*.*", SearchOption.TopDirectoryOnly))
{
int num = frame.Name.GetInt();
if (num > highestInt)
{
highest = frame;
highestInt = frame.Name.GetInt();
}
}
return highest.FullName;
}
public static string FilenameSuffix (string path, string suffix)
{
try
{
string ext = Path.GetExtension(path);
return Path.Combine(path.GetParentDir(), $"{Path.GetFileNameWithoutExtension(path)}{suffix}{ext}");
}
catch
{
return path;
}
}
public static async Task<Fraction> GetFpsFolderOrVideo(string path)
{
try
{
if (IsPathDirectory(path))
{
Fraction dirFps = GetVideoFramerateForDir(path);
if (dirFps.GetFloat() > 0)
return dirFps;
}
else
{
Fraction vidFps = await GetVideoFramerate(path);
if (vidFps.GetFloat() > 0)
return vidFps;
}
}
catch (Exception e)
{
Logger.Log("GetFpsFolderOrVideo() Error: " + e.Message);
}
return new Fraction();
}
public enum ErrorMode { HiddenLog, VisibleLog, Messagebox }
public static bool CanWriteToDir (string dir, ErrorMode errMode)
{
string tempFile = Path.Combine(dir, "flowframes-testfile.tmp");
try
{
File.Create(tempFile);
File.Delete(tempFile);
return true;
}
catch (Exception e)
{
Logger.Log($"Can't write to {dir}! {e.Message}", errMode == ErrorMode.HiddenLog);
if (errMode == ErrorMode.Messagebox && !BatchProcessing.busy)
MessageBox.Show($"Can't write to {dir}!\n\n{e.Message}", "Error");
return false;
}
}
public static bool CopyTo (string file, string targetFolder, bool overwrite = true)
{
string targetPath = Path.Combine(targetFolder, Path.GetFileName(file));
try
{
if (!Directory.Exists(targetFolder))
Directory.CreateDirectory(targetFolder);
File.Copy(file, targetPath, overwrite);
return true;
}
catch (Exception e)
{
Logger.Log($"Failed to copy {file} to {targetFolder}: {e.Message}");
return false;
}
}
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 path, Hash hashType, bool log = true)
{
string hashStr = "";
NmkdStopwatch sw = new NmkdStopwatch();
if (IsPathDirectory(path))
{
Logger.Log($"Path '{path}' is directory! Returning empty hash.", true);
return hashStr;
}
try
{
var stream = File.OpenRead(path);
if (hashType == Hash.MD5)
{
MD5 md5 = MD5.Create();
var hash = md5.ComputeHash(stream);
hashStr = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
if (hashType == Hash.CRC32)
{
var crc = new Crc32Algorithm();
var crc32bytes = crc.ComputeHash(stream);
hashStr = BitConverter.ToUInt32(crc32bytes, 0).ToString();
}
stream.Close();
}
catch (Exception e)
{
Logger.Log($"Error getting file hash for {Path.GetFileName(path)}: {e.Message}", true);
return "";
}
if (log)
Logger.Log($"Computed {hashType} for '{Path.GetFileNameWithoutExtension(path).Trunc(40) + Path.GetExtension(path)}' ({GetFilesizeStr(path)}): {hashStr} ({sw.GetElapsedStr()})", true);
return hashStr;
}
public static async Task<string> GetHashAsync(string path, Hash hashType, bool log = true)
{
await Task.Delay(1);
return GetHash(path, hashType, log);
}
public static bool CreateDir (string path) // Returns whether the dir already existed
{
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
return false;
}
return true;
}
public static void ZeroPadDir(string path, string ext, int targetLength, bool recursive = false)
{
FileInfo[] files;
if (recursive)
files = new DirectoryInfo(path).GetFiles($"*.{ext}", SearchOption.AllDirectories);
else
files = new DirectoryInfo(path).GetFiles($"*.{ext}", SearchOption.TopDirectoryOnly);
ZeroPadDir(files.Select(x => x.FullName).ToList(), targetLength);
}
public static void ZeroPadDir(List<string> files, int targetLength, List<string> exclude = null, bool noLog = true)
{
if(exclude != null)
files = files.Except(exclude).ToList();
foreach (string file in files)
{
string fname = Path.GetFileNameWithoutExtension(file);
string targetFilename = Path.Combine(Path.GetDirectoryName(file), fname.PadLeft(targetLength, '0') + Path.GetExtension(file));
try
{
if (targetFilename != file)
File.Move(file, targetFilename);
}
catch (Exception e)
{
if(!noLog)
Logger.Log($"Failed to zero-pad {file} => {targetFilename}: {e.Message}", true);
}
}
}
public static bool CheckImageValid (string path)
{
try
{
Image img = GetImage(path);
return (img.Width > 0 && img.Height > 0);
}
catch
{
return false;
}
}
public static string[] GetFilesSorted (string path, bool recursive = false, string pattern = "*")
{
SearchOption opt = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
return Directory.GetFiles(path, pattern, opt).OrderBy(x => Path.GetFileName(x)).ToArray();
}
public static string[] GetFilesSorted(string path, string pattern = "*")
{
return GetFilesSorted(path, false, pattern);
}
public static string[] GetFilesSorted(string path)
{
return GetFilesSorted(path, false, "*");
}
public static FileInfo[] GetFileInfosSorted(string path, bool recursive = false, string pattern = "*")
{
SearchOption opt = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
DirectoryInfo dir = new DirectoryInfo(path);
return dir.GetFiles(pattern, opt).OrderBy(x => x.Name).ToArray();
}
public static bool CreateFileIfNotExists (string path)
{
if (File.Exists(path))
return false;
try
{
File.Create(path).Close();
return true;
}
catch (Exception e)
{
Logger.Log($"Failed to create file at '{path}': {e.Message}");
return false;
}
}
public static long GetDirSize(string path, bool recursive, string[] includedExtensions = null)
{
long size = 0;
try
{
string[] files;
StringComparison ignCase = StringComparison.OrdinalIgnoreCase;
if (includedExtensions == null)
files = Directory.GetFiles(path);
else
files = Directory.GetFiles(path).Where(file => includedExtensions.Any(x => file.EndsWith(x, ignCase))).ToArray();
foreach (string file in files)
{
try { size += new FileInfo(file).Length; } catch { size += 0; }
}
if (!recursive)
return size;
// Add subdirectory sizes.
DirectoryInfo[] dis = new DirectoryInfo(path).GetDirectories();
foreach (DirectoryInfo di in dis)
size += GetDirSize(di.FullName, true, includedExtensions);
}
catch(Exception e)
{
Logger.Log($"GetDirSize Error: {e.Message}\n{e.StackTrace}", true);
}
return size;
}
public static long GetFilesize(string path)
{
try
{
return new FileInfo(path).Length;
}
catch
{
return -1;
}
}
public static string GetFilesizeStr (string path)
{
try
{
return FormatUtils.Bytes(GetFilesize(path));
}
catch
{
return "?";
}
}
public static void OverwriteFileWithText (string path, string text = "THIS IS A DUMMY FILE - DO NOT DELETE ME")
{
try
{
File.WriteAllText(path, text);
}
catch (Exception e)
{
Logger.Log($"OverwriteWithText failed for '{path}': {e.Message}", true);
}
}
public static long GetDiskSpace(string path, bool mbytes = true)
{
try
{
string driveLetter = path.Substring(0, 2); // Make 'C:/some/random/path' => 'C:' etc
DriveInfo[] allDrives = DriveInfo.GetDrives();
foreach (DriveInfo d in allDrives)
{
if (d.IsReady && d.Name.StartsWith(driveLetter))
{
if (mbytes)
return (long)(d.AvailableFreeSpace / 1024f / 1000f);
else
return d.AvailableFreeSpace;
}
}
}
catch (Exception e)
{
Logger.Log("Error trying to get disk space: " + e.Message, true);
}
return 0;
}
}
}

162
Code/IO/Logger.cs Normal file
View File

@@ -0,0 +1,162 @@
using Flowframes.IO;
using Flowframes.Ui;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using DT = System.DateTime;
namespace Flowframes
{
class Logger
{
public static TextBox textbox;
static string file;
public const string defaultLogName = "sessionlog";
public static long id;
public struct LogEntry
{
public string logMessage;
public bool hidden;
public bool replaceLastLine;
public string filename;
public LogEntry(string logMessageArg, bool hiddenArg = false, bool replaceLastLineArg = false, string filenameArg = "")
{
logMessage = logMessageArg;
hidden = hiddenArg;
replaceLastLine = replaceLastLineArg;
filename = filenameArg;
}
}
private static ConcurrentQueue<LogEntry> logQueue = new ConcurrentQueue<LogEntry>();
public static void Log(string msg, bool hidden = false, bool replaceLastLine = false, string filename = "")
{
logQueue.Enqueue(new LogEntry(msg, hidden, replaceLastLine, filename));
ShowNext();
}
public static void ShowNext ()
{
LogEntry entry;
if (logQueue.TryDequeue(out entry))
Show(entry);
}
public static void Show(LogEntry entry)
{
if (string.IsNullOrWhiteSpace(entry.logMessage))
return;
Console.WriteLine(entry.logMessage);
try
{
if (!entry.hidden && entry.replaceLastLine)
{
textbox.Suspend();
string[] lines = textbox.Text.SplitIntoLines();
textbox.Text = string.Join(Environment.NewLine, lines.Take(lines.Count() - 1).ToArray());
}
}
catch { }
entry.logMessage = entry.logMessage.Replace("\n", Environment.NewLine);
if (!entry.hidden && textbox != null)
textbox.AppendText((textbox.Text.Length > 1 ? Environment.NewLine : "") + entry.logMessage);
if (entry.replaceLastLine)
{
textbox.Resume();
entry.logMessage = "[REPL] " + entry.logMessage;
}
if (!entry.hidden)
entry.logMessage = "[UI] " + entry.logMessage;
LogToFile(entry.logMessage, false, entry.filename);
}
public static void LogToFile(string logStr, bool noLineBreak, string filename)
{
if (string.IsNullOrWhiteSpace(filename))
filename = defaultLogName;
if (Path.GetExtension(filename) != ".txt")
filename = Path.ChangeExtension(filename, "txt");
file = Path.Combine(Paths.GetLogPath(), filename);
logStr = logStr.Replace(Environment.NewLine, " ").TrimWhitespaces();
string time = DT.Now.Month + "-" + DT.Now.Day + "-" + DT.Now.Year + " " + DT.Now.Hour + ":" + DT.Now.Minute + ":" + DT.Now.Second;
try
{
if (!noLineBreak)
File.AppendAllText(file, $"{Environment.NewLine}[{id}] [{time}]: {logStr}");
else
File.AppendAllText(file, " " + logStr);
id++;
}
catch
{
// this if fine, i forgot why
}
}
public static void LogIfLastLineDoesNotContainMsg (string s, bool hidden = false, bool replaceLastLine = false, string filename = "")
{
if (!GetLastLine().Contains(s))
Log(s, hidden, replaceLastLine, filename);
}
public static void WriteToFile (string content, bool append, string filename)
{
if (string.IsNullOrWhiteSpace(filename))
filename = defaultLogName;
if (Path.GetExtension(filename) != ".txt")
filename = Path.ChangeExtension(filename, "txt");
file = Path.Combine(Paths.GetLogPath(), filename);
string time = DT.Now.Month + "-" + DT.Now.Day + "-" + DT.Now.Year + " " + DT.Now.Hour + ":" + DT.Now.Minute + ":" + DT.Now.Second;
try
{
if (append)
File.AppendAllText(file, Environment.NewLine + time + ":" + Environment.NewLine + content);
else
File.WriteAllText(file, Environment.NewLine + time + ":" + Environment.NewLine + content);
}
catch
{
}
}
public static void ClearLogBox ()
{
textbox.Text = "";
}
public static string GetLastLine ()
{
string[] lines = textbox.Text.SplitIntoLines();
if (lines.Length < 1)
return "";
return lines.Last();
}
public static void RemoveLastLine ()
{
textbox.Text = textbox.Text.Remove(textbox.Text.LastIndexOf(Environment.NewLine));
}
}
}

280
Code/IO/ModelDownloader.cs Normal file
View File

@@ -0,0 +1,280 @@
using Flowframes.Data;
using Flowframes.Main;
using Flowframes.MiscUtils;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace Flowframes.IO
{
class ModelDownloader
{
public static bool canceled = false;
static string GetMdlUrl (string ai, string relPath)
{
string baseUrl = Config.Get(Config.Key.mdlBaseUrl);
return Path.Combine(baseUrl, ai.ToLower(), relPath);
}
static string GetMdlFileUrl(string ai, string model, string relPath)
{
return Path.Combine(GetMdlUrl(ai, model), relPath);
}
static string GetLocalPath(string ai, string model)
{
return Path.Combine(Paths.GetPkgPath(), ai, model);
}
static async Task DownloadTo (string url, string saveDirOrPath, bool log = true, int retries = 3)
{
canceled = false;
string savePath = saveDirOrPath;
if (IoUtils.IsPathDirectory(saveDirOrPath))
savePath = Path.Combine(saveDirOrPath, Path.GetFileName(url));
IoUtils.TryDeleteIfExists(savePath);
Directory.CreateDirectory(Path.GetDirectoryName(savePath));
Logger.Log($"Downloading '{url}' to '{savePath}'", true);
Stopwatch sw = new Stopwatch();
sw.Restart();
bool completed = false;
int lastProgPercentage = -1;
var client = new WebClient();
client.DownloadProgressChanged += (sender, args) =>
{
if (sw.ElapsedMilliseconds > 200 && args.ProgressPercentage != lastProgPercentage)
{
sw.Restart();
lastProgPercentage = args.ProgressPercentage;
Logger.Log($"Downloading model file '{Path.GetFileName(url)}'... {args.ProgressPercentage}%", !log, true);
}
};
client.DownloadFileCompleted += (sender, args) =>
{
if (args.Error != null)
Logger.Log("Download failed: " + args.Error.Message, !log);
completed = true;
};
client.DownloadFileTaskAsync(url, savePath).ConfigureAwait(false);
while (!completed)
{
if (canceled || Interpolate.canceled)
{
client.CancelAsync();
client.Dispose();
return;
}
if (sw.ElapsedMilliseconds > 6000)
{
client.CancelAsync();
if(retries > 0)
{
await DownloadTo(url, saveDirOrPath, log, retries--);
}
else
{
Interpolate.Cancel("Model download failed.");
return;
}
}
await Task.Delay(500);
}
Logger.Log($"Downloaded '{Path.GetFileName(url)}' ({IoUtils.GetFilesize(savePath) / 1024} KB)", true);
}
class ModelFile
{
public string filename;
public string dir;
public long size;
public string crc32;
}
static List<ModelFile> GetModelFilesFromJson (string json)
{
List<ModelFile> modelFiles = new List<ModelFile>();
try
{
dynamic data = JsonConvert.DeserializeObject(json);
foreach (var item in data)
{
string dirString = ((string)item.dir).Replace(@"\", @"/");
if (dirString.Length > 0 && dirString[0] == '/') dirString = dirString.Remove(0, 1);
long sizeLong = long.Parse((string)item.size);
modelFiles.Add(new ModelFile { filename = item.filename, dir = dirString, size = sizeLong, crc32 = item.crc32 });
}
}
catch (Exception e)
{
Logger.Log($"Failed to parse model file list from JSON: {e.Message}", true);
}
return modelFiles;
}
public static async Task DownloadModelFiles (AI ai, string modelDir, bool log = true)
{
string aiDir = ai.pkgDir;
Logger.Log($"DownloadModelFiles(string ai = {ai}, string model = {modelDir}, bool log = {log})", true);
try
{
string mdlDir = GetLocalPath(aiDir, modelDir);
if (modelDir.EndsWith("_custom") || await AreFilesValid(aiDir, modelDir))
return;
Logger.Log($"Downloading '{modelDir}' model files...", !log);
Directory.CreateDirectory(mdlDir);
await DownloadTo(GetMdlFileUrl(aiDir, modelDir, "files.json"), mdlDir, false);
string jsonPath = Path.Combine(mdlDir, "files.json");
List<ModelFile> modelFiles = GetModelFilesFromJson(File.ReadAllText(jsonPath));
if (!File.Exists(jsonPath) || IoUtils.GetFilesize(jsonPath) < 32)
{
Interpolate.Cancel($"Error: Failed to download index file. Please try again.");
return;
}
if (modelFiles.Count < 1)
{
Interpolate.Cancel($"Error: Can't download model files because no entries were loaded from the index file. Please try again.");
return;
}
foreach (ModelFile mf in modelFiles)
{
string relPath = Path.Combine(mf.dir, mf.filename).Replace("\\", "/");
await DownloadTo(GetMdlFileUrl(aiDir, modelDir, relPath), Path.Combine(mdlDir, relPath), log);
}
Logger.Log($"Downloaded \"{modelDir}\" model files.", !log, true);
if (!(await AreFilesValid(aiDir, modelDir)))
Interpolate.Cancel($"Model files are invalid! Please try again.");
}
catch (Exception e)
{
Logger.Log($"DownloadModelFiles Error: {e.Message}\nStack Trace:\n{e.StackTrace}", !log);
Interpolate.Cancel($"Error downloading model files: {e.Message}");
}
}
public static void DeleteAllModels ()
{
foreach(string modelFolder in GetAllModelFolders())
{
string size = FormatUtils.Bytes(IoUtils.GetDirSize(modelFolder, true));
if (IoUtils.TryDeleteIfExists(modelFolder))
Logger.Log($"Deleted cached model '{Path.GetFileName(modelFolder.GetParentDir())}/{Path.GetFileName(modelFolder)}' ({size})");
}
}
public static List<string> GetAllModelFolders()
{
List<string> modelPaths = new List<string>();
foreach (AI ai in Implementations.networks)
{
string aiPkgFolder = Path.Combine(Paths.GetPkgPath(), ai.pkgDir);
ModelCollection aiModels = AiModels.GetModels(ai);
foreach(ModelCollection.ModelInfo model in aiModels.models)
{
string mdlFolder = Path.Combine(aiPkgFolder, model.dir);
if (Directory.Exists(mdlFolder))
modelPaths.Add(mdlFolder);
}
}
return modelPaths;
}
public static async Task<bool> AreFilesValid (string ai, string model)
{
string mdlDir = GetLocalPath(ai, model);
if (!Directory.Exists(mdlDir))
{
Logger.Log($"Files for model {model} not valid: {mdlDir} does not exist.", true);
return false;
}
string md5FilePath = Path.Combine(mdlDir, "files.json");
if (!File.Exists(md5FilePath) || IoUtils.GetFilesize(md5FilePath) < 32)
{
Logger.Log($"Files for model {model} not valid: {mdlDir} does not exist or is incomplete.", true);
return false;
}
List<ModelFile> modelFiles = GetModelFilesFromJson(File.ReadAllText(Path.Combine(mdlDir, "files.json")));
if (modelFiles.Count < 1)
{
Logger.Log($"Files for model {model} not valid: JSON contains {modelFiles.Count} entries.", true);
return false;
}
foreach (ModelFile mf in modelFiles)
{
string filePath = Path.Combine(mdlDir, mf.dir, mf.filename);
long fileSize = IoUtils.GetFilesize(filePath);
if (fileSize != mf.size)
{
Logger.Log($"Files for model {model} not valid: Filesize of {mf.filename} ({fileSize}) does not equal validation size ({mf.size}).", true);
return false;
}
if(fileSize > 100 * 1024 * 1024) // Skip hash calculation if file is very large, filesize check should be enough anyway
{
Logger.Log($"Skipped CRC32 check for {mf.filename} because it's too big ({fileSize / 1024 / 1024} MB)", true);
return true;
}
string crc = await IoUtils.GetHashAsync(Path.Combine(mdlDir, mf.dir, mf.filename), IoUtils.Hash.CRC32);
if (crc.Trim() != mf.crc32.Trim())
{
Logger.Log($"Files for model {model} not valid: CRC32 of {mf.filename} ({crc.Trim()}) does not equal validation CRC32 ({mf.crc32.Trim()}).", true);
return false;
}
}
return true;
}
static Dictionary<string, string> GetDict (string[] lines, char sep = ':')
{
Dictionary<string, string> dict = new Dictionary<string, string>();
foreach (string line in lines)
{
if (line.Length < 3) continue;
string[] keyValuePair = line.Split(':');
dict.Add(keyValuePair[0], keyValuePair[1]);
}
return dict;
}
}
}

98
Code/IO/Symlinks.cs Normal file
View File

@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Flowframes.MiscUtils;
namespace Flowframes.IO
{
class Symlinks
{
public enum Flag { File = 0, Directory = 1, Unprivileged = 2 }
[DllImport("kernel32.dll")]
public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, Flag dwFlags);
public static bool SymlinksAllowed()
{
string origFile = Paths.GetExe();
string linkPath = Paths.GetExe() + "linktest";
bool success = CreateSymbolicLink(linkPath, origFile, Flag.Unprivileged);
if (success)
{
File.Delete(linkPath);
return true;
}
return false;
}
public static async Task CreateSymlinksParallel(Dictionary<string, string> pathsLinkTarget, bool debug = false, int maxThreads = 150)
{
Stopwatch sw = new Stopwatch();
sw.Restart();
ParallelOptions opts = new ParallelOptions() {MaxDegreeOfParallelism = maxThreads};
Task forEach = Task.Run(async () => Parallel.ForEach(pathsLinkTarget, opts, pair =>
{
bool success = CreateSymbolicLink(pair.Key, pair.Value, Flag.Unprivileged);
if (debug)
Logger.Log($"Created Symlink - Source: '{pair.Key}' - Target: '{pair.Value}' - Sucess: {success}", true);
}));
while (!forEach.IsCompleted) await Task.Delay(1);
Logger.Log($"Created {pathsLinkTarget.Count} symlinks in {FormatUtils.TimeSw(sw)}", true);
}
public static async Task<bool> MakeSymlinksForEncode(string framesFile, string linksDir, int zPad = 8)
{
try
{
IoUtils.DeleteIfExists(linksDir);
Directory.CreateDirectory(linksDir);
Stopwatch sw = new Stopwatch();
sw.Restart();
Logger.Log($"Creating symlinks for '{framesFile}' in '{linksDir} with zPadding {zPad}'", true);
int counter = 0;
Dictionary<string, string> pathsLinkTarget = new Dictionary<string, string>();
foreach (string line in File.ReadAllLines(framesFile))
{
string relTargetPath =
line.Remove("file '").Split('\'').FirstOrDefault(); // Relative path in frames file
string absTargetPath = Path.Combine(framesFile.GetParentDir(), relTargetPath); // Full path to frame
string linkPath = Path.Combine(linksDir,
counter.ToString().PadLeft(zPad, '0') + Path.GetExtension(relTargetPath));
pathsLinkTarget.Add(linkPath, absTargetPath);
counter++;
}
await CreateSymlinksParallel(pathsLinkTarget);
if (IoUtils.GetAmountOfFiles(linksDir, false) > 1)
{
return true;
}
else
{
Logger.Log("Symlink creation seems to have failed even though SymlinksAllowed was true! Encoding ini with concat demuxer instead.", true);
return false;
}
}
catch (Exception e)
{
Logger.Log("MakeSymlinks Exception: " + e.Message);
}
return false;
}
}
}

166
Code/Magick/Blend.cs Normal file
View File

@@ -0,0 +1,166 @@
using Flowframes.Data;
using Flowframes.MiscUtils;
using ImageMagick;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Flowframes.IO;
namespace Flowframes.Magick
{
class Blend
{
public static async Task BlendSceneChanges(string framesFilePath, bool setStatus = true)
{
Stopwatch sw = new Stopwatch();
sw.Restart();
int totalFrames = 0;
string keyword = "SCN:";
string fileContent = File.ReadAllText(framesFilePath);
if (!fileContent.Contains(keyword))
{
Logger.Log("Skipping BlendSceneChanges as there are no scene changes in this frames file.", true);
return;
}
string[] framesLines = fileContent.SplitIntoLines(); // Array with frame filenames
string oldStatus = Program.mainForm.GetStatus();
if (setStatus)
Program.mainForm.SetStatus("Blending scene transitions...");
int amountOfBlendFrames = (int)Interpolate.current.interpFactor - 1;
string[] frames = FrameRename.framesAreRenamed ? new string[0] : IoUtils.GetFilesSorted(Interpolate.current.framesFolder);
List<Task> runningTasks = new List<Task>();
int maxThreads = Environment.ProcessorCount * 2;
foreach (string line in framesLines)
{
try
{
if (line.Contains(keyword))
{
string trimmedLine = line.Split(keyword).Last();
string[] inputFrameNames = trimmedLine.Split('>');
string frameFrom = FrameRename.framesAreRenamed ? inputFrameNames[0] : frames[inputFrameNames[0].GetInt()];
string frameTo = FrameRename.framesAreRenamed ? inputFrameNames[1] : frames[inputFrameNames[1].GetInt()];
string img1 = Path.Combine(Interpolate.current.framesFolder, frameFrom);
string img2 = Path.Combine(Interpolate.current.framesFolder, frameTo);
string firstOutputFrameName = line.Split('/').Last().Remove("'").Split('#').First();
string ext = Path.GetExtension(firstOutputFrameName);
int firstOutputFrameNum = firstOutputFrameName.GetInt();
List<string> outputFilenames = new List<string>();
for (int blendFrameNum = 1; blendFrameNum <= amountOfBlendFrames; blendFrameNum++)
{
int outputNum = firstOutputFrameNum + blendFrameNum;
string outputPath = Path.Combine(Interpolate.current.interpFolder, outputNum.ToString().PadLeft(Padding.interpFrames, '0'));
outputPath = Path.ChangeExtension(outputPath, ext);
outputFilenames.Add(outputPath);
}
if (runningTasks.Count >= maxThreads)
{
do
{
await Task.Delay(10);
RemoveCompletedTasks(runningTasks);
} while (runningTasks.Count >= maxThreads);
}
Logger.Log($"Starting task for transition {inputFrameNames[0]} > {inputFrameNames[1]} ({runningTasks.Count}/{maxThreads} running)", true);
Task newTask = Task.Run(() => BlendImages(img1, img2, outputFilenames.ToArray()));
runningTasks.Add(newTask);
totalFrames += outputFilenames.Count;
await Task.Delay(1);
}
}
catch (Exception e)
{
Logger.Log("Failed to blend scene changes: " + e.Message, true);
}
}
while (true)
{
RemoveCompletedTasks(runningTasks);
if (runningTasks.Count < 1)
break;
await Task.Delay(10);
}
Logger.Log($"Created {totalFrames} blend frames in {FormatUtils.TimeSw(sw)} ({(totalFrames / (sw.ElapsedMilliseconds / 1000f)).ToString("0.00")} FPS)", true);
if (setStatus)
Program.mainForm.SetStatus(oldStatus);
}
static void RemoveCompletedTasks(List<Task> runningTasks)
{
foreach (Task task in new List<Task>(runningTasks))
{
if (task.IsCompleted)
runningTasks.Remove(task);
}
}
public static void BlendImages(string img1Path, string img2Path, string imgOutPath)
{
MagickImage img1 = new MagickImage(img1Path);
MagickImage img2 = new MagickImage(img2Path);
img2.Alpha(AlphaOption.Opaque);
img2.Evaluate(Channels.Alpha, EvaluateOperator.Set, new Percentage(50));
img1.Composite(img2, Gravity.Center, CompositeOperator.Over);
img1.Format = MagickFormat.Png24;
img1.Quality = 10;
img1.Write(imgOutPath);
}
public static async Task BlendImages(string img1Path, string img2Path, string[] imgOutPaths)
{
try
{
MagickImage img1 = new MagickImage(img1Path);
MagickImage img2 = new MagickImage(img2Path);
int alphaFraction = (100f / (imgOutPaths.Length + 1)).RoundToInt(); // Alpha percentage per image
int currentAlpha = alphaFraction;
foreach (string imgOutPath in imgOutPaths)
{
string outPath = imgOutPath.Trim();
MagickImage img1Inst = new MagickImage(img1);
MagickImage img2Inst = new MagickImage(img2);
img2Inst.Alpha(AlphaOption.Opaque);
img2Inst.Evaluate(Channels.Alpha, EvaluateOperator.Set, new Percentage(currentAlpha));
currentAlpha += alphaFraction;
img1Inst.Composite(img2Inst, Gravity.Center, CompositeOperator.Over);
img1Inst.Format = MagickFormat.Png24;
img1Inst.Quality = 10;
img1Inst.Write(outPath);
await Task.Delay(1);
}
}
catch (Exception e)
{
Logger.Log("BlendImages Error: " + e.Message);
}
}
}
}

145
Code/Magick/Converter.cs Normal file
View File

@@ -0,0 +1,145 @@
using Flowframes;
using Flowframes.IO;
using Flowframes.Ui;
using ImageMagick;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Magick
{
class Converter
{
public static async Task Convert (string dir, MagickFormat format, int quality, string ext = "", bool print = true, bool setProgress = true)
{
var files = IoUtils.GetFilesSorted(dir);
if(print) Logger.Log($"Converting {files.Length} files in {dir}");
int counter = 0;
foreach (string file in files)
{
if (print) Logger.Log("Converting " + Path.GetFileName(file) + " to " + format.ToString().StripNumbers().ToUpper(), false, true);
MagickImage img = new MagickImage(file);
img.Format = format;
img.Quality = quality;
string outpath = file;
if (!string.IsNullOrWhiteSpace(ext)) outpath = Path.ChangeExtension(outpath, ext);
img.Write(outpath);
counter++;
if(setProgress)
Program.mainForm.SetProgress((int)Math.Round(((float)counter / files.Length) * 100f));
await Task.Delay(1);
}
}
public static async Task MakeBinary (string inputDir, string outputDir, bool print = true, bool setProgress = true)
{
try
{
var files = IoUtils.GetFilesSorted(inputDir);
if (print) Logger.Log($"Processing alpha channel...");
Directory.CreateDirectory(outputDir);
Stopwatch sw = new Stopwatch();
sw.Restart();
int counter = 0;
foreach (string file in files)
{
MagickImage img = new MagickImage(file);
img.Format = MagickFormat.Png24;
img.Quality = 10;
img.Threshold(new Percentage(75));
string outPath = Path.Combine(outputDir, Path.GetFileName(file));
img.Write(outPath);
counter++;
if (sw.ElapsedMilliseconds > 250)
{
if (setProgress)
Program.mainForm.SetProgress((int)Math.Round(((float)counter / files.Length) * 100f));
await Task.Delay(1);
sw.Restart();
}
}
}
catch (Exception e)
{
Logger.Log("MakeBinary Error: " + e.Message);
}
}
public static async Task ExtractAlpha (string inputDir, string outputDir, bool print = true, bool setProgress = true, bool removeInputAlpha = true)
{
try
{
var files = IoUtils.GetFilesSorted(inputDir);
if (print) Logger.Log($"Extracting alpha channel from images...");
Directory.CreateDirectory(outputDir);
Stopwatch sw = new Stopwatch();
sw.Restart();
int counter = 0;
foreach (string file in files)
{
MagickImage alphaImg = new MagickImage(file);
if (removeInputAlpha)
{
MagickImage rgbImg = alphaImg;
rgbImg.Format = MagickFormat.Png24;
rgbImg.Quality = 10;
MagickImage bg = new MagickImage(MagickColors.Black, rgbImg.Width, rgbImg.Height);
bg.Composite(rgbImg, CompositeOperator.Over);
rgbImg = bg;
rgbImg.Write(file);
}
alphaImg.Format = MagickFormat.Png24;
alphaImg.Quality = 10;
alphaImg.FloodFill(MagickColors.None, 0, 0); // Fill the image with a transparent background
alphaImg.InverseOpaque(MagickColors.None, MagickColors.White); // Change all the pixels that are not transparent to white.
alphaImg.ColorAlpha(MagickColors.Black); // Change the transparent pixels to black.
string outPath = Path.Combine(outputDir, Path.GetFileName(file));
alphaImg.Write(outPath);
counter++;
if (sw.ElapsedMilliseconds > 250)
{
if (setProgress)
Program.mainForm.SetProgress((int)Math.Round(((float)counter / files.Length) * 100f));
await Task.Delay(1);
sw.Restart();
}
}
}
catch (Exception e)
{
Logger.Log("ExtractAlpha Error: " + e.Message);
}
}
public static async Task Preprocess (string dir, bool setProgress = true)
{
var files = IoUtils.GetFilesSorted(dir);
Logger.Log($"Preprocessing {files} files in {dir}");
int counter = 0;
foreach (string file in files)
{
//Logger.Log("Converting " + Path.GetFileName(file) + " to " + format, false, true);
MagickImage img = new MagickImage(file);
//img.Format = MagickFormat.Bmp;
//img.Write(file);
//img = new MagickImage(file);
img.Format = MagickFormat.Png24;
img.Quality = 10;
counter++;
if (setProgress)
Program.mainForm.SetProgress((int)Math.Round(((float)counter / files.Length) * 100f));
await Task.Delay(1);
}
}
}
}

238
Code/Magick/Dedupe.cs Normal file
View File

@@ -0,0 +1,238 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Flowframes.IO;
using ImageMagick;
using Flowframes.Os;
using Flowframes.Data;
using System.Drawing;
using Paths = Flowframes.IO.Paths;
namespace Flowframes.Magick
{
class Dedupe
{
public enum Mode { None, Info, Enabled, Auto }
public static Mode currentMode;
public static float currentThreshold;
public static async Task Run(string path, bool testRun = false, bool setStatus = true)
{
if (path == null || !Directory.Exists(path) || Interpolate.canceled)
return;
currentMode = Mode.Auto;
if(setStatus)
Program.mainForm.SetStatus("Running frame de-duplication");
currentThreshold = Config.GetFloat(Config.Key.dedupThresh);
Logger.Log("Running accurate frame de-duplication...");
if (currentMode == Mode.Enabled || currentMode == Mode.Auto)
await RemoveDupeFrames(path, currentThreshold, "*", testRun, false, (currentMode == Mode.Auto));
}
public static Dictionary<string, MagickImage> imageCache = new Dictionary<string, MagickImage>();
static MagickImage GetImage(string path)
{
bool allowCaching = true;
if (!allowCaching)
return new MagickImage(path);
if (!imageCache.ContainsKey(path))
imageCache.Add(path, new MagickImage(path));
return imageCache[path];
}
public static void ClearCache ()
{
imageCache.Clear();
}
public static async Task RemoveDupeFrames(string path, float threshold, string ext, bool testRun = false, bool debugLog = false, bool skipIfNoDupes = false)
{
Stopwatch sw = new Stopwatch();
sw.Restart();
Logger.Log("Removing duplicate frames - Threshold: " + threshold.ToString("0.00"));
FileInfo[] framePaths = IoUtils.GetFileInfosSorted(path, false, "*." + ext);
List<string> framesToDelete = new List<string>();
int bufferSize = await GetBufferSize();
int currentOutFrame = 1;
int currentDupeCount = 0;
int statsFramesKept = 0;
int statsFramesDeleted = 0;
int skipAfterNoDupesFrames = Config.GetInt(Config.Key.autoDedupFrames);
bool hasEncounteredAnyDupes = false;
bool skipped = false;
bool hasReachedEnd = false;
string fileContent = "";
for (int i = 0; i < framePaths.Length; i++) // Loop through frames
{
if (hasReachedEnd)
break;
string frame1 = framePaths[i].FullName;
int compareWithIndex = i + 1;
while (true) // Loop dupes
{
//compareWithIndex++;
if (compareWithIndex >= framePaths.Length)
{
hasReachedEnd = true;
break;
}
if (framesToDelete.Contains(framePaths[compareWithIndex].FullName) || !File.Exists(framePaths[compareWithIndex].FullName))
{
//Logger.Log($"Frame {compareWithIndex} was already deleted - skipping");
compareWithIndex++;
}
else
{
string frame2 = framePaths[compareWithIndex].FullName;
float diff = GetDifference(frame1, frame2);
if (diff < threshold) // Is a duped frame.
{
if (!testRun)
{
framesToDelete.Add(frame2);
if (debugLog) Logger.Log("Deduplication: Deleted " + Path.GetFileName(frame2));
hasEncounteredAnyDupes = true;
}
statsFramesDeleted++;
currentDupeCount++;
}
else
{
fileContent += $"{Path.GetFileNameWithoutExtension(framePaths[i].Name)}:{currentDupeCount}\n";
statsFramesKept++;
currentOutFrame++;
currentDupeCount = 0;
break;
}
}
}
if (sw.ElapsedMilliseconds >= 500 || (i + 1) == framePaths.Length) // Print every 0.5s (or when done)
{
sw.Restart();
Logger.Log($"Deduplication: Running de-duplication ({i}/{framePaths.Length}), deleted {statsFramesDeleted} ({(((float)statsFramesDeleted / framePaths.Length) * 100f).ToString("0")}%) duplicate frames so far...", false, true);
Program.mainForm.SetProgress((int)Math.Round(((float)i / framePaths.Length) * 100f));
if (imageCache.Count > bufferSize || (imageCache.Count > 50 && OsUtils.GetFreeRamMb() < 3500))
ClearCache();
}
// int oldIndex = -1; // TODO: Compare with 1st to fix loops?
// if (i >= framePaths.Length) // If this is the last frame, compare with 1st to avoid OutOfRange error
// {
// oldIndex = i;
// i = 0;
// }
if (i % 3 == 0)
await Task.Delay(1);
if (Interpolate.canceled) return;
if (!testRun && skipIfNoDupes && !hasEncounteredAnyDupes && skipAfterNoDupesFrames > 0 && i >= skipAfterNoDupesFrames)
{
skipped = true;
break;
}
}
foreach (string frame in framesToDelete)
IoUtils.TryDeleteIfExists(frame);
string testStr = testRun ? " [TestRun]" : "";
if (Interpolate.canceled) return;
int framesLeft = IoUtils.GetAmountOfFiles(path, false, "*" + Interpolate.current.framesExt);
int framesDeleted = framePaths.Length - framesLeft;
float percentDeleted = ((float)framesDeleted / framePaths.Length) * 100f;
string keptPercent = $"{(100f - percentDeleted).ToString("0.0")}%";
if (skipped)
Logger.Log($"Deduplication: First {skipAfterNoDupesFrames} frames did not have any duplicates - Skipping the rest!", false, true);
else
Logger.Log($"[Deduplication]{testStr} Done. Kept {framesLeft} ({keptPercent}) frames, deleted {framesDeleted} frames.", false, true);
if (statsFramesKept <= 0)
Interpolate.Cancel("No frames were left after de-duplication!\n\nTry decreasing the de-duplication threshold.");
}
static float GetDifference (string img1Path, string img2Path)
{
MagickImage img2 = GetImage(img2Path);
MagickImage img1 = GetImage(img1Path);
double err = img1.Compare(img2, ErrorMetric.Fuzz);
float errPercent = (float)err * 100f;
return errPercent;
}
static async Task<int> GetBufferSize ()
{
Size res = await Interpolate.current.GetScaledRes();
long pixels = res.Width * res.Height; // 4K = 8294400, 1440p = 3686400, 1080p = 2073600, 720p = 921600, 540p = 518400, 360p = 230400
int bufferSize = 100;
if (pixels < 518400) bufferSize = 1800;
if (pixels >= 518400) bufferSize = 1400;
if (pixels >= 921600) bufferSize = 800;
if (pixels >= 2073600) bufferSize = 400;
if (pixels >= 3686400) bufferSize = 200;
if (pixels >= 8294400) bufferSize = 100;
if (pixels == 0) bufferSize = 100;
Logger.Log($"Using magick dedupe buffer size {bufferSize} for frame resolution {res.Width}x{res.Height}", true);
return bufferSize;
}
public static async Task CreateDupesFile (string framesPath, int lastFrameNum, string ext)
{
bool debug = Config.GetBool("dupeScanDebug", false);
string infoFile = Path.Combine(framesPath.GetParentDir(), "dupes.ini");
string fileContent = "";
FileInfo[] frameFiles = IoUtils.GetFileInfosSorted(framesPath, false, "*" + ext);
if (debug)
Logger.Log($"Running CreateDupesFile for '{framesPath}' ({frameFiles.Length} files), lastFrameNum = {lastFrameNum}, ext = {ext}.", true, false, "dupes");
for(int i = 0; i < frameFiles.Length; i++)
{
bool isLastItem = (i + 1) == frameFiles.Length;
int frameNum1 = frameFiles[i].Name.GetInt();
int frameNum2 = isLastItem ? lastFrameNum : frameFiles[i+1].Name.GetInt();
int diff = frameNum2 - frameNum1;
int dupes = diff - 1;
if(debug)
Logger.Log($"{(isLastItem ? "[isLastItem] " : "")}frameNum1 (frameFiles[{i}]) = {frameNum1}, frameNum2 (frameFiles[{i+1}]) = {frameNum2} => dupes = {dupes}", true, false, "dupes");
fileContent += $"{Path.GetFileNameWithoutExtension(frameFiles[i].Name)}:{dupes}\n";
}
File.WriteAllText(infoFile, fileContent);
}
}
}

View File

@@ -0,0 +1,164 @@
using Flowframes.MiscUtils;
using ImageMagick;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
namespace Flowframes.Magick
{
public enum BitmapDensity
{
/// <summary>
/// Ignore the density of the image when creating the bitmap.
/// </summary>
Ignore,
/// <summary>
/// Use the density of the image when creating the bitmap.
/// </summary>
Use,
}
public static class MagickExtensions
{
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive.")]
public static Bitmap ToBitmap(this MagickImage magickImg, BitmapDensity density)
{
string mapping = "BGR";
var format = PixelFormat.Format24bppRgb;
var image = magickImg;
try
{
if (image.ColorSpace != ColorSpace.sRGB)
{
image = (MagickImage)magickImg.Clone();
image.ColorSpace = ColorSpace.sRGB;
}
if (image.HasAlpha)
{
mapping = "BGRA";
format = PixelFormat.Format32bppArgb;
}
using (var pixels = image.GetPixelsUnsafe())
{
var bitmap = new Bitmap(image.Width, image.Height, format);
var data = bitmap.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadWrite, format);
var destination = data.Scan0;
for (int y = 0; y < image.Height; y++)
{
byte[] bytes = pixels.ToByteArray(0, y, image.Width, 1, mapping);
Marshal.Copy(bytes, 0, destination, bytes.Length);
destination = new IntPtr(destination.ToInt64() + data.Stride);
}
bitmap.UnlockBits(data);
SetBitmapDensity(magickImg, bitmap, density);
return bitmap;
}
}
finally
{
if (!ReferenceEquals(image, magickImg))
image.Dispose();
}
}
public static Bitmap ToBitmap(this MagickImage imageMagick) => ToBitmap(imageMagick, BitmapDensity.Ignore);
public static Bitmap ToBitmap(this MagickImage imageMagick, ImageFormat imageFormat) => ToBitmap(imageMagick, imageFormat, BitmapDensity.Ignore);
public static Bitmap ToBitmap(this MagickImage imageMagick, ImageFormat imageFormat, BitmapDensity bitmapDensity)
{
imageMagick.Format = InternalMagickFormatInfo.GetFormat(imageFormat);
MemoryStream memStream = new MemoryStream();
imageMagick.Write(memStream);
memStream.Position = 0;
/* Do not dispose the memStream, the bitmap owns it. */
var bitmap = new Bitmap(memStream);
SetBitmapDensity(imageMagick, bitmap, bitmapDensity);
return bitmap;
}
public static void FromBitmap(this MagickImage imageMagick, Bitmap bitmap)
{
using (MemoryStream memStream = new MemoryStream())
{
if (IsSupportedImageFormat(bitmap.RawFormat))
bitmap.Save(memStream, bitmap.RawFormat);
else
bitmap.Save(memStream, ImageFormat.Bmp);
memStream.Position = 0;
imageMagick.Read(memStream);
}
}
private static bool IsSupportedImageFormat(ImageFormat format)
{
return
format.Guid.Equals(ImageFormat.Bmp.Guid) ||
format.Guid.Equals(ImageFormat.Gif.Guid) ||
format.Guid.Equals(ImageFormat.Icon.Guid) ||
format.Guid.Equals(ImageFormat.Jpeg.Guid) ||
format.Guid.Equals(ImageFormat.Png.Guid) ||
format.Guid.Equals(ImageFormat.Tiff.Guid);
}
private static void SetBitmapDensity(MagickImage imageMagick, Bitmap bitmap, BitmapDensity bitmapDensity)
{
if (bitmapDensity == BitmapDensity.Use)
{
var dpi = GetDpi(imageMagick, bitmapDensity);
bitmap.SetResolution((float)dpi.X, (float)dpi.Y);
}
}
private static Density GetDpi(MagickImage imageMagick, BitmapDensity bitmapDensity)
{
if (bitmapDensity == BitmapDensity.Ignore || (imageMagick.Density.Units == DensityUnit.Undefined && imageMagick.Density.X == 0 && imageMagick.Density.Y == 0))
return new Density(96);
return imageMagick.Density.ChangeUnits(DensityUnit.PixelsPerInch);
}
}
public class InternalMagickFormatInfo
{
internal static MagickFormat GetFormat(ImageFormat format)
{
if (format == ImageFormat.Bmp || format == ImageFormat.MemoryBmp)
return MagickFormat.Bmp;
else if (format == ImageFormat.Gif)
return MagickFormat.Gif;
else if (format == ImageFormat.Icon)
return MagickFormat.Icon;
else if (format == ImageFormat.Jpeg)
return MagickFormat.Jpeg;
else if (format == ImageFormat.Png)
return MagickFormat.Png;
else if (format == ImageFormat.Tiff)
return MagickFormat.Tiff;
else
throw new NotSupportedException("Unsupported image format: " + format.ToString());
}
}
}

View File

@@ -0,0 +1,94 @@
using Flowframes.IO;
using ImageMagick;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Magick
{
class SceneDetect
{
public static async Task RunSceneDetection (string path)
{
string outFolder = path + "-analyzed";
Directory.CreateDirectory(outFolder);
string ext = "png";
FileInfo[] frames = IoUtils.GetFileInfosSorted(path, false, "*." + ext);
for (int i = 1; i < frames.Length; i++)
{
FileInfo frame = frames[i];
FileInfo lastFrame = frames[i - 1];
Task.Run(() => ProcessFrame(frame, lastFrame, outFolder));
}
}
public static Dictionary<string, MagickImage> imageCache = new Dictionary<string, MagickImage>();
static MagickImage GetImage(string path, bool allowCaching = true)
{
if (!allowCaching)
return new MagickImage(path);
if (imageCache.Count >= 30)
ClearCache();
if (!imageCache.ContainsKey(path))
imageCache.Add(path, new MagickImage(path));
return imageCache[path];
}
public static void ClearCache()
{
imageCache.Clear();
}
static async Task ProcessFrame (FileInfo frame, FileInfo lastFrame, string outFolder)
{
MagickImage prevFrame = GetImage(lastFrame.FullName, false);
MagickImage currFrame = GetImage(frame.FullName, false);
Size originalSize = new Size(currFrame.Width, currFrame.Height);
int downscaleHeight = 144;
prevFrame.Scale(currFrame.Width / (currFrame.Height / downscaleHeight), downscaleHeight);
currFrame.Scale(currFrame.Width / (currFrame.Height / downscaleHeight), downscaleHeight);
double errNormalizedCrossCorrelation = currFrame.Compare(prevFrame, ErrorMetric.NormalizedCrossCorrelation);
double errRootMeanSquared = currFrame.Compare(prevFrame, ErrorMetric.RootMeanSquared);
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 (rmsNccTrigger) str += "\n\nRMS -> NCC DOUBLE SCENE CHANGE TRIGGER!";
if (nccRmsTrigger) str += "\n\nNCC -> RMS DOUBLE SCENE CHANGE TRIGGER!";
currFrame.Scale(originalSize.Width / 2, originalSize.Height / 2);
new Drawables()
.FontPointSize(12)
.Font("Consolas", FontStyleType.Normal, FontWeight.Bold, FontStretch.Normal)
.FillColor(MagickColors.Red)
.TextAlignment(TextAlignment.Left)
.Text(1, 10, str)
.Draw(currFrame);
currFrame.Write(Path.Combine(outFolder, frame.Name));
prevFrame.Dispose();
currFrame.Dispose();
await Task.Delay(1);
}
}
}

71
Code/Main/AiModels.cs Normal file
View File

@@ -0,0 +1,71 @@
using Flowframes.Data;
using Flowframes.IO;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Flowframes.Main
{
class AiModels
{
public static ModelCollection GetModels (AI ai)
{
string pkgPath = Path.Combine(Paths.GetPkgPath(), ai.pkgDir);
string modelsFile = Path.Combine(pkgPath, "models.json");
ModelCollection modelCollection = new ModelCollection(ai, modelsFile);
foreach (string customModel in GetCustomModels(ai))
{
string name = customModel.Remove("_alpha").Remove("_custom");
bool alpha = customModel.Contains("_alpha");
modelCollection.models.Add(new ModelCollection.ModelInfo(ai, name, "Custom Model", customModel, alpha, false));
}
return modelCollection;
}
public static List<string> GetCustomModels(AI ai)
{
string pkgPath = Path.Combine(Paths.GetPkgPath(), ai.pkgDir);
List<string> custModels = new List<string>();
foreach (DirectoryInfo dir in new DirectoryInfo(pkgPath).GetDirectories())
{
if (dir.Name.EndsWith("_custom") && Regex.IsMatch(dir.Name, @"^[a-zA-Z0-9_]+$"))
custModels.Add(dir.Name);
}
return custModels;
}
public static ModelCollection.ModelInfo GetModelByName(AI ai, string modelName)
{
ModelCollection modelCollection = GetModels(ai);
foreach(ModelCollection.ModelInfo model in modelCollection.models)
{
if (model.name == modelName)
return model;
}
return null;
}
public static ModelCollection.ModelInfo GetModelByDir(AI ai, string dirName)
{
ModelCollection modelCollection = GetModels(ai);
foreach (ModelCollection.ModelInfo model in modelCollection.models)
{
if (model.dir == dirName)
return model;
}
return null;
}
}
}

241
Code/Main/AutoEncode.cs Normal file
View File

@@ -0,0 +1,241 @@
using Flowframes.Media;
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.MiscUtils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Flowframes.Ui;
using Flowframes.Os;
namespace Flowframes.Main
{
class AutoEncode
{
static string interpFramesFolder;
static string videoChunksFolder;
public static int chunkSize; // Encode every n frames
public static int safetyBufferFrames; // Ignore latest n frames to avoid using images that haven't been fully encoded yet
public static string[] interpFramesLines;
public static List<int> encodedFrameLines = new List<int>();
public static List<int> unencodedFrameLines = new List<int>();
public static bool debug;
public static bool busy;
public static bool paused;
public static Task currentMuxTask;
public static void UpdateChunkAndBufferSizes ()
{
chunkSize = GetChunkSize((IoUtils.GetAmountOfFiles(Interpolate.current.framesFolder, false, "*" + Interpolate.current.framesExt) * Interpolate.current.interpFactor).RoundToInt());
safetyBufferFrames = 90;
if (Interpolate.current.ai.aiName.ToUpper().Contains("NCNN"))
safetyBufferFrames = Config.GetInt(Config.Key.autoEncSafeBufferNcnn, 150);
if (Interpolate.current.ai.aiName == Implementations.rifeCuda.aiName)
safetyBufferFrames = Config.GetInt(Config.Key.autoEncSafeBufferRifeCuda, 90);
if (Interpolate.current.ai.aiName == Implementations.flavrCuda.aiName)
safetyBufferFrames = Config.GetInt(Config.Key.autoEncSafeBufferFlavrCuda, 90);
}
public static async Task MainLoop(string interpFramesPath)
{
debug = Config.GetBool("autoEncDebug", false);
try
{
UpdateChunkAndBufferSizes();
interpFramesFolder = interpFramesPath;
videoChunksFolder = Path.Combine(interpFramesPath.GetParentDir(), Paths.chunksDir);
if (Interpolate.currentlyUsingAutoEnc)
Directory.CreateDirectory(videoChunksFolder);
encodedFrameLines.Clear();
unencodedFrameLines.Clear();
Logger.Log($"[AE] Starting AutoEncode MainLoop - Chunk Size: {chunkSize} Frames - Safety Buffer: {safetyBufferFrames} Frames", true);
int videoIndex = 1;
string encFile = Path.Combine(interpFramesPath.GetParentDir(), Paths.GetFrameOrderFilename(Interpolate.current.interpFactor));
interpFramesLines = IoUtils.ReadLines(encFile).Select(x => x.Split('/').Last().Remove("'").Split('#').First()).ToArray(); // Array with frame filenames
while (!Interpolate.canceled && GetInterpFramesAmount() < 2)
await Task.Delay(2000);
int lastEncodedFrameNum = 0;
while (HasWorkToDo()) // Loop while proc is running and not all frames have been encoded
{
if (Interpolate.canceled) return;
if (paused)
{
await Task.Delay(200);
continue;
}
unencodedFrameLines.Clear();
bool aiRunning = !AiProcess.lastAiProcess.HasExited;
for (int frameLineNum = lastEncodedFrameNum; frameLineNum < interpFramesLines.Length; frameLineNum++)
{
if (aiRunning && interpFramesLines[frameLineNum].Contains(InterpolationProgress.lastFrame.ToString().PadLeft(Padding.interpFrames, '0')))
break;
unencodedFrameLines.Add(frameLineNum);
}
if (Config.GetBool(Config.Key.alwaysWaitForAutoEnc))
{
int maxFrames = chunkSize + (0.5f * chunkSize).RoundToInt() + safetyBufferFrames;
bool overwhelmed = unencodedFrameLines.Count > maxFrames;
if(overwhelmed && !AiProcessSuspend.aiProcFrozen)
{
string dirSize = FormatUtils.Bytes(IoUtils.GetDirSize(Interpolate.current.interpFolder, true));
Logger.Log($"AutoEnc is overwhelmed! ({unencodedFrameLines.Count} unencoded frames > {maxFrames}) - Pausing.", true);
AiProcessSuspend.SuspendResumeAi(true);
}
else if (!overwhelmed && AiProcessSuspend.aiProcFrozen)
{
AiProcessSuspend.SuspendResumeAi(false);
}
}
if (unencodedFrameLines.Count > 0 && (unencodedFrameLines.Count >= (chunkSize + safetyBufferFrames) || !aiRunning)) // Encode every n frames, or after process has exited
{
try
{
List<int> frameLinesToEncode = aiRunning ? unencodedFrameLines.Take(chunkSize).ToList() : unencodedFrameLines; // Take all remaining frames if process is done
string lastOfChunk = Path.Combine(interpFramesPath, interpFramesLines[frameLinesToEncode.Last()]);
if (!File.Exists(lastOfChunk))
{
if(debug)
Logger.Log($"[AE] Last frame of chunk doesn't exist; skipping loop iteration ({lastOfChunk})", true);
await Task.Delay(500);
continue;
}
busy = true;
string outpath = Path.Combine(videoChunksFolder, "chunks", $"{videoIndex.ToString().PadLeft(4, '0')}{FfmpegUtils.GetExt(Interpolate.current.outMode)}");
int firstLineNum = frameLinesToEncode.First();
int lastLineNum = frameLinesToEncode.Last();
Logger.Log($"[AE] Encoding Chunk #{videoIndex} to '{outpath}' using line {firstLineNum} ({Path.GetFileName(interpFramesLines[firstLineNum])}) through {lastLineNum} ({Path.GetFileName(Path.GetFileName(interpFramesLines[frameLinesToEncode.Last()]))})", true, false, "ffmpeg");
await CreateVideo.EncodeChunk(outpath, Interpolate.current.outMode, firstLineNum, frameLinesToEncode.Count);
if (Interpolate.canceled) return;
if (aiRunning && Config.GetInt(Config.Key.autoEncMode) == 2)
Task.Run(() => DeleteOldFramesAsync(interpFramesPath, frameLinesToEncode));
if (Interpolate.canceled) return;
encodedFrameLines.AddRange(frameLinesToEncode);
Logger.Log("[AE] Done Encoding Chunk #" + videoIndex, true, false, "ffmpeg");
lastEncodedFrameNum = (frameLinesToEncode.Last() + 1);
videoIndex++;
if(Config.GetInt(Config.Key.autoEncBackupMode) > 0)
{
if (aiRunning && (currentMuxTask == null || (currentMuxTask != null && currentMuxTask.IsCompleted)))
currentMuxTask = Task.Run(() => CreateVideo.ChunksToVideos(Interpolate.current.tempFolder, videoChunksFolder, Interpolate.current.outPath, true));
else
Logger.Log($"[AE] Skipping backup because {(!aiRunning ? "this is the final chunk" : "previous mux task has not finished yet")}!", true, false, "ffmpeg");
}
busy = false;
}
catch (Exception e)
{
Logger.Log($"AutoEnc Chunk Encoding Error: {e.Message}. Stack Trace:\n{e.StackTrace}");
Interpolate.Cancel("Auto-Encode encountered an error.");
}
}
await Task.Delay(50);
}
if (Interpolate.canceled) return;
while (currentMuxTask != null && !currentMuxTask.IsCompleted)
await Task.Delay(100);
await CreateVideo.ChunksToVideos(Interpolate.current.tempFolder, videoChunksFolder, Interpolate.current.outPath);
}
catch (Exception e)
{
Logger.Log($"AutoEnc Error: {e.Message}. Stack Trace:\n{e.StackTrace}");
Interpolate.Cancel("Auto-Encode encountered an error.");
}
}
static async Task DeleteOldFramesAsync (string interpFramesPath, List<int> frameLinesToEncode)
{
if(debug)
Logger.Log("[AE] Starting DeleteOldFramesAsync.", true, false, "ffmpeg");
Stopwatch sw = new Stopwatch();
sw.Restart();
foreach (int frame in frameLinesToEncode)
{
if (!FrameIsStillNeeded(interpFramesLines[frame], frame)) // Make sure frames are no longer needed (for dupes) before deleting!
{
string framePath = Path.Combine(interpFramesPath, interpFramesLines[frame]);
//IOUtils.OverwriteFileWithText(framePath); // Overwrite to save space without breaking progress counter
IoUtils.TryDeleteIfExists(framePath);
InterpolationProgress.deletedFramesCount++;
}
}
if (debug)
Logger.Log("[AE] DeleteOldFramesAsync finished in " + FormatUtils.TimeSw(sw), true, false, "ffmpeg");
}
static bool FrameIsStillNeeded (string frameName, int frameIndex)
{
if ((frameIndex + 1) < interpFramesLines.Length && interpFramesLines[frameIndex+1].Contains(frameName))
return true;
return false;
}
public static bool HasWorkToDo ()
{
if (Interpolate.canceled || interpFramesFolder == null) return false;
if(debug)
Logger.Log($"[AE] HasWorkToDo - Process Running: {(AiProcess.lastAiProcess != null && !AiProcess.lastAiProcess.HasExited)} - encodedFrameLines.Count: {encodedFrameLines.Count} - interpFramesLines.Length: {interpFramesLines.Length}", true);
return ((AiProcess.lastAiProcess != null && !AiProcess.lastAiProcess.HasExited) || encodedFrameLines.Count < interpFramesLines.Length);
}
static int GetChunkSize(int targetFramesAmount)
{
if (targetFramesAmount > 100000) return 4800;
if (targetFramesAmount > 50000) return 2400;
if (targetFramesAmount > 20000) return 1200;
if (targetFramesAmount > 5000) return 600;
if (targetFramesAmount > 1000) return 300;
return 150;
}
static int GetInterpFramesAmount()
{
return IoUtils.GetAmountOfFiles(interpFramesFolder, false, "*" + Interpolate.current.interpExt);
}
}
}

View File

@@ -0,0 +1,121 @@
using Flowframes.Forms;
using Flowframes.IO;
using Flowframes.Os;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Main
{
class BatchProcessing
{
public static bool stopped = false;
public static BatchForm currentBatchForm;
public static bool busy = false;
public static async void Start()
{
if (Config.GetBool(Config.Key.clearLogOnInput))
Logger.ClearLogBox();
stopped = false;
Program.mainForm.SetTab("preview");
int initTaskCount = Program.batchQueue.Count;
for (int i = 0; i < initTaskCount; i++)
{
if (!stopped && Program.batchQueue.Count > 0)
{
try
{
Logger.Log($"Queue: Running queue task {i + 1}/{initTaskCount}, {Program.batchQueue.Count} tasks left.");
await RunEntry(Program.batchQueue.Peek());
if (currentBatchForm != null)
currentBatchForm.RefreshGui();
}
catch (Exception e)
{
Logger.Log($"Failed to run batch queue entry. If this happened after force stopping the queue, it's non-critical. {e.Message}", true);
}
}
await Task.Delay(1000);
}
Logger.Log("Queue: Finished queue processing.");
OsUtils.ShowNotificationIfInBackground("Flowframes Queue", "Finished queue processing.");
SetBusy(false);
Program.mainForm.SetTab("interpolation");
Program.mainForm.CompletionAction();
}
public static void Stop()
{
stopped = true;
}
static async Task RunEntry(InterpSettings entry)
{
if (!EntryIsValid(entry))
{
Logger.Log("Queue: Skipping entry because it's invalid.");
Program.batchQueue.Dequeue();
return;
}
string fname = Path.GetFileName(entry.inPath);
if (IoUtils.IsPathDirectory(entry.inPath)) fname = Path.GetDirectoryName(entry.inPath);
Logger.Log($"Queue: Processing {fname} ({entry.interpFactor}x {entry.ai.aiNameShort}).");
SetBusy(true);
Program.mainForm.LoadBatchEntry(entry); // Load entry into GUI
Interpolate.current = entry;
Program.mainForm.runBtn_Click(null, null);
await Task.Delay(2000);
while (Program.busy)
await Task.Delay(1000);
SetBusy(false);
Program.batchQueue.Dequeue();
Logger.Log($"Queue: Done processing {fname} ({entry.interpFactor}x {entry.ai.aiNameShort}).");
}
static void SetBusy(bool state)
{
busy = state;
if (currentBatchForm != null)
currentBatchForm.SetWorking(state);
Program.mainForm.SetWorking(state);
Program.mainForm.GetMainTabControl().Enabled = !state; // Lock GUI
}
static bool EntryIsValid(InterpSettings entry)
{
if (entry.inPath == null || (IoUtils.IsPathDirectory(entry.inPath) && !Directory.Exists(entry.inPath)) || (!IoUtils.IsPathDirectory(entry.inPath) && !File.Exists(entry.inPath)))
{
Logger.Log("Queue: Can't process queue entry: Input path is invalid.");
return false;
}
if (entry.outPath == null || !Directory.Exists(entry.outPath))
{
Logger.Log("Queue: Can't process queue entry: Output path is invalid.");
return false;
}
if (IoUtils.GetAmountOfFiles(Path.Combine(Paths.GetPkgPath(), entry.ai.pkgDir), true) < 1)
{
Logger.Log("Queue: Can't process queue entry: Selected AI is not available.");
return false;
}
return true;
}
}
}

306
Code/Main/CreateVideo.cs Normal file
View File

@@ -0,0 +1,306 @@
using Flowframes.IO;
using Flowframes.Magick;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using Padding = Flowframes.Data.Padding;
using I = Flowframes.Interpolate;
using System.Diagnostics;
using Flowframes.Data;
using Flowframes.Media;
using Microsoft.VisualBasic.Logging;
using Flowframes.MiscUtils;
using Flowframes.Os;
namespace Flowframes.Main
{
class CreateVideo
{
public static async Task Export(string path, string outFolder, I.OutMode mode, bool stepByStep)
{
if(Config.GetInt(Config.Key.sceneChangeFillMode) == 1)
{
string frameFile = Path.Combine(I.current.tempFolder, Paths.GetFrameOrderFilename(I.current.interpFactor));
await Blend.BlendSceneChanges(frameFile);
}
if (!mode.ToString().ToLower().Contains("vid")) // Copy interp frames out of temp folder and skip video export for image seq export
{
try
{
await ExportFrames(path, stepByStep);
}
catch (Exception e)
{
Logger.Log("Failed to move interpolated frames: " + e.Message);
Logger.Log("Stack Trace:\n " + e.StackTrace, true);
}
return;
}
if (IoUtils.GetAmountOfFiles(path, false, "*" + I.current.interpExt) <= 1)
{
I.Cancel("Output folder does not contain frames - An error must have occured during interpolation!", AiProcess.hasShownError);
return;
}
await Task.Delay(10);
Program.mainForm.SetStatus("Creating output video from frames...");
try
{
string max = Config.Get(Config.Key.maxFps);
Fraction maxFps = max.Contains("/") ? new Fraction(max) : new Fraction(max.GetFloat());
bool fpsLimit = maxFps.GetFloat() > 0f && I.current.outFps.GetFloat() > maxFps.GetFloat();
bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt(Config.Key.maxFpsMode) == 0;
if (!dontEncodeFullFpsVid)
await Encode(mode, path, Path.Combine(outFolder, await IoUtils.GetCurrentExportFilename(false, true)), I.current.outFps, new Fraction());
if (fpsLimit)
await Encode(mode, path, Path.Combine(outFolder, await IoUtils.GetCurrentExportFilename(true, true)), I.current.outFps, maxFps);
}
catch (Exception e)
{
Logger.Log("FramesToVideo Error: " + e.Message, false);
MessageBox.Show("An error occured while trying to convert the interpolated frames to a video.\nCheck the log for details.");
}
}
static async Task ExportFrames (string framesPath, bool stepByStep)
{
Program.mainForm.SetStatus("Copying output frames...");
string desiredFormat = Config.Get(Config.Key.imgSeqFormat).ToUpper();
string availableFormat = Path.GetExtension(IoUtils.GetFilesSorted(framesPath)[0]).Remove(".").ToUpper();
string max = Config.Get(Config.Key.maxFps);
Fraction maxFps = max.Contains("/") ? new Fraction(max) : new Fraction(max.GetFloat());
bool fpsLimit = maxFps.GetFloat() > 0f && I.current.outFps.GetFloat() > maxFps.GetFloat();
bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt(Config.Key.maxFpsMode) == 0;
string framesFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(I.current.interpFactor));
if (!dontEncodeFullFpsVid)
{
string outputFolderPath = Path.Combine(I.current.outPath, await IoUtils.GetCurrentExportFilename(false, false));
IoUtils.RenameExistingFolder(outputFolderPath);
Logger.Log($"Exporting {desiredFormat.ToUpper()} frames to '{Path.GetFileName(outputFolderPath)}'...");
if (desiredFormat.ToUpper() == availableFormat.ToUpper()) // Move as the frames are already in the desired format
await CopyOutputFrames(framesPath, framesFile, outputFolderPath, fpsLimit);
else // Encode with ffmpeg
await FfmpegEncode.FramesToFrames(framesFile, outputFolderPath, I.current.outFps, new Fraction(), desiredFormat);
}
if (fpsLimit)
{
string outputFolderPath = Path.Combine(I.current.outPath, await IoUtils.GetCurrentExportFilename(true, false));
Logger.Log($"Exporting {desiredFormat.ToUpper()} frames to '{Path.GetFileName(outputFolderPath)}' (Resampled to {maxFps} FPS)...");
await FfmpegEncode.FramesToFrames(framesFile, outputFolderPath, I.current.outFps, maxFps, desiredFormat);
}
if (!stepByStep)
await IoUtils.DeleteContentsOfDirAsync(I.current.interpFolder);
}
static async Task CopyOutputFrames(string framesPath, string framesFile, string outputFolderPath, bool dontMove)
{
await IoUtils.TryDeleteIfExistsAsync(outputFolderPath);
IoUtils.CreateDir(outputFolderPath);
Stopwatch sw = new Stopwatch();
sw.Restart();
string[] framesLines = IoUtils.ReadLines(framesFile);
for (int idx = 1; idx <= framesLines.Length; idx++)
{
string line = framesLines[idx - 1];
string inFilename = line.RemoveComments().Split('/').Last().Remove("'").Trim();
string framePath = Path.Combine(framesPath, inFilename);
string outFilename = Path.Combine(outputFolderPath, idx.ToString().PadLeft(Padding.interpFrames, '0')) + Path.GetExtension(framePath);
if (dontMove || ((idx < framesLines.Length) && framesLines[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 >= 500 || idx == framesLines.Length)
{
sw.Restart();
Logger.Log($"Moving output frames... {idx}/{framesLines.Length}", false, true);
await Task.Delay(1);
}
}
}
static async Task Encode(I.OutMode mode, string framesPath, string outPath, Fraction fps, Fraction resampleFps)
{
string framesFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(I.current.interpFactor));
if (!File.Exists(framesFile))
{
bool sbs = Config.GetInt(Config.Key.processingMode) == 1;
I.Cancel($"Frame order file for this interpolation factor not found!{(sbs ? "\n\nDid you run the interpolation step with the current factor?" : "")}");
return;
}
if (mode == I.OutMode.VidGif)
{
await FfmpegEncode.FramesToGifConcat(framesFile, outPath, fps, true, Config.GetInt(Config.Key.gifColors), resampleFps);
}
else
{
VidExtraData extraData = await FfmpegCommands.GetVidExtraInfo(I.current.inPath);
await FfmpegEncode.FramesToVideo(framesFile, outPath, mode, fps, resampleFps, extraData);
await MuxOutputVideo(I.current.inPath, outPath);
await Loop(outPath, await GetLoopTimes());
}
}
public static async Task ChunksToVideos(string tempFolder, string chunksFolder, string baseOutPath, bool isBackup = false)
{
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;
}
NmkdStopwatch sw = new NmkdStopwatch();
if(!isBackup)
Program.mainForm.SetStatus("Merging video chunks...");
try
{
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);
Logger.Log($"CreateVideo: Running MergeChunks() for frames file '{Path.GetFileName(tempConcatFile)}'", true);
bool fpsLimit = dir.Name.Contains(Paths.fpsLimitSuffix);
string outPath = Path.Combine(baseOutPath, await IoUtils.GetCurrentExportFilename(fpsLimit, true));
await MergeChunks(tempConcatFile, outPath, isBackup);
if (!isBackup)
Task.Run(async () => { await IoUtils.TryDeleteIfExistsAsync(IoUtils.FilenameSuffix(outPath, Paths.backupSuffix)); });
}
}
catch (Exception e)
{
Logger.Log("ChunksToVideo Error: " + e.Message, isBackup);
if (!isBackup)
MessageBox.Show("An error occured while trying to merge the video chunks.\nCheck the log for details.");
}
Logger.Log($"Merged video chunks in {sw.GetElapsedStr()}", true);
}
static async Task MergeChunks(string framesFile, string outPath, bool isBackup = false)
{
if (isBackup)
{
outPath = IoUtils.FilenameSuffix(outPath, Paths.backupSuffix);
await IoUtils.TryDeleteIfExistsAsync(outPath);
}
await FfmpegCommands.ConcatVideos(framesFile, outPath, -1, !isBackup);
if(!isBackup || (isBackup && Config.GetInt(Config.Key.autoEncBackupMode) == 2)) // Mux if no backup, or if backup AND muxing is enabled for backups
await MuxOutputVideo(I.current.inPath, outPath, isBackup, !isBackup);
if(!isBackup)
await Loop(outPath, await GetLoopTimes());
}
public static async Task EncodeChunk(string outPath, I.OutMode mode, int firstFrameNum, int framesAmount)
{
string framesFileFull = Path.Combine(I.current.tempFolder, Paths.GetFrameOrderFilename(I.current.interpFactor));
string framesFileChunk = Path.Combine(I.current.tempFolder, Paths.GetFrameOrderFilenameChunk(firstFrameNum, firstFrameNum + framesAmount));
File.WriteAllLines(framesFileChunk, IoUtils.ReadLines(framesFileFull).Skip(firstFrameNum).Take(framesAmount));
if (Config.GetInt(Config.Key.sceneChangeFillMode) == 1)
await Blend.BlendSceneChanges(framesFileChunk, false);
string max = Config.Get(Config.Key.maxFps);
Fraction maxFps = max.Contains("/") ? new Fraction(max) : new Fraction(max.GetFloat());
bool fpsLimit = maxFps.GetFloat() != 0 && I.current.outFps.GetFloat() > maxFps.GetFloat();
VidExtraData extraData = await FfmpegCommands.GetVidExtraInfo(I.current.inPath);
bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt(Config.Key.maxFpsMode) == 0;
if (!dontEncodeFullFpsVid)
await FfmpegEncode.FramesToVideo(framesFileChunk, outPath, mode, I.current.outFps, new Fraction(), extraData, AvProcess.LogMode.Hidden, true); // Encode
if (fpsLimit)
{
string filename = Path.GetFileName(outPath);
string newParentDir = outPath.GetParentDir() + Paths.fpsLimitSuffix;
outPath = Path.Combine(newParentDir, filename);
await FfmpegEncode.FramesToVideo(framesFileChunk, outPath, mode, I.current.outFps, maxFps, extraData, AvProcess.LogMode.Hidden, true); // Encode with limited fps
}
}
static async Task Loop(string outPath, int looptimes)
{
if (looptimes < 1 || !Config.GetBool(Config.Key.enableLoop)) return;
Logger.Log($"Looping {looptimes} {(looptimes == 1 ? "time" : "times")} to reach target length of {Config.GetInt(Config.Key.minOutVidLength)}s...");
await FfmpegCommands.LoopVideo(outPath, looptimes, Config.GetInt(Config.Key.loopMode) == 0);
}
static async Task<int> GetLoopTimes()
{
int times = -1;
int minLength = Config.GetInt(Config.Key.minOutVidLength);
int minFrameCount = (minLength * I.current.outFps.GetFloat()).RoundToInt();
int outFrames = ((await I.GetCurrentInputFrameCount()) * I.current.interpFactor).RoundToInt();
if (outFrames / I.current.outFps.GetFloat() < minLength)
times = (int)Math.Ceiling((double)minFrameCount / (double)outFrames);
times--; // Not counting the 1st play (0 loops)
if (times <= 0) return -1; // Never try to loop 0 times, idk what would happen, probably nothing
return times;
}
public static async Task MuxOutputVideo(string inputPath, string outVideo, bool shortest = false, bool showLog = true)
{
if (!File.Exists(outVideo))
{
I.Cancel($"No video was encoded!\n\nFFmpeg Output:\n{AvProcess.lastOutputFfmpeg}");
return;
}
if (!Config.GetBool(Config.Key.keepAudio) && !Config.GetBool(Config.Key.keepAudio))
return;
if(showLog)
Program.mainForm.SetStatus("Muxing audio/subtitles into video...");
if (I.current.inputIsFrames)
{
Logger.Log("Skipping muxing from input step as there is no input video, only frames.", true);
return;
}
try
{
await FfmpegAudioAndMetadata.MergeStreamsFromInput(inputPath, outVideo, I.current.tempFolder, shortest);
}
catch (Exception e)
{
Logger.Log("Failed to merge audio/subtitles with output video!", !showLog);
Logger.Log("MergeAudio() Exception: " + e.Message, true);
}
}
}
}

185
Code/Main/FrameOrder.cs Normal file
View File

@@ -0,0 +1,185 @@
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.MiscUtils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Flowframes.Main
{
class FrameOrder
{
static Stopwatch benchmark = new Stopwatch();
static FileInfo[] frameFiles;
static FileInfo[] frameFilesWithoutLast;
static List<string> sceneFrames = new List<string>();
static Dictionary<int, string> frameFileContents = new Dictionary<int, string>();
static int lastOutFileCount;
public static async Task CreateFrameOrderFile(string framesPath, bool loopEnabled, float times)
{
Logger.Log("Generating frame order information...");
try
{
foreach (FileInfo file in IoUtils.GetFileInfosSorted(framesPath.GetParentDir(), false, $"{Paths.frameOrderPrefix}*.*"))
file.Delete();
benchmark.Restart();
await CreateEncFile(framesPath, loopEnabled, times);
Logger.Log($"Generating frame order information... Done.", false, true);
Logger.Log($"Generated frame order info file in {benchmark.ElapsedMilliseconds} ms", true);
}
catch (Exception e)
{
Logger.Log($"Error generating frame order information: {e.Message}\n{e.StackTrace}");
}
}
static Dictionary<string, int> dupesDict = new Dictionary<string, int>();
static void LoadDupesFile(string path)
{
dupesDict.Clear();
if (!File.Exists(path)) return;
string[] dupesFileLines = IoUtils.ReadLines(path);
foreach (string line in dupesFileLines)
{
string[] values = line.Split(':');
dupesDict.Add(values[0], values[1].GetInt());
}
}
public static async Task CreateEncFile(string framesPath, bool loopEnabled, float interpFactor)
{
if (Interpolate.canceled) return;
Logger.Log($"Generating frame order information for {interpFactor}x...", false, true);
bool loop = Config.GetBool(Config.Key.enableLoop);
bool sceneDetection = true;
string ext = Interpolate.current.interpExt;
frameFileContents.Clear();
lastOutFileCount = 0;
frameFiles = new DirectoryInfo(framesPath).GetFiles("*" + Interpolate.current.framesExt);
frameFilesWithoutLast = frameFiles;
Array.Resize(ref frameFilesWithoutLast, frameFilesWithoutLast.Length - 1);
string framesFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(interpFactor));
string fileContent = "";
string dupesFile = Path.Combine(framesPath.GetParentDir(), "dupes.ini");
LoadDupesFile(dupesFile);
string scnFramesPath = Path.Combine(framesPath.GetParentDir(), Paths.scenesDir);
sceneFrames.Clear();
if (Directory.Exists(scnFramesPath))
sceneFrames = Directory.GetFiles(scnFramesPath).Select(file => GetNameNoExt(file)).ToList();
bool debug = Config.GetBool("frameOrderDebug", false);
List<Task> tasks = new List<Task>();
int linesPerTask = 400 / (int)interpFactor;
int num = 0;
for (int i = 0; i < frameFilesWithoutLast.Length; i += linesPerTask)
{
tasks.Add(GenerateFrameLines(num, i, linesPerTask, (int)interpFactor, loopEnabled, sceneDetection, debug));
num++;
}
await Task.WhenAll(tasks);
for (int x = 0; x < frameFileContents.Count; x++)
fileContent += frameFileContents[x];
lastOutFileCount++;
int lastFrameTimes = Config.GetBool(Config.Key.fixOutputDuration) ? (int)interpFactor : 1;
for(int i = 0; i < lastFrameTimes; i++)
fileContent += $"{(i > 0 ? "\n" : "")}file '{Paths.interpDir}/{lastOutFileCount.ToString().PadLeft(Padding.interpFrames, '0')}{ext}'"; // Last frame (source)
if (loop)
fileContent = fileContent.Remove(fileContent.LastIndexOf("\n"));
File.WriteAllText(framesFile, fileContent);
}
static async Task GenerateFrameLines(int number, int startIndex, int count, int factor, bool loopEnabled, bool sceneDetection, bool debug)
{
int totalFileCount = (startIndex) * factor;
int interpFramesAmount = factor;
string ext = Interpolate.current.interpExt;
string fileContent = "";
for (int i = startIndex; i < (startIndex + count); i++)
{
if (Interpolate.canceled) return;
if (i >= frameFilesWithoutLast.Length) break;
string frameName = GetNameNoExt(frameFilesWithoutLast[i].Name);
string frameNameImport = GetNameNoExt(FrameRename.importFilenames[i]);
int dupesAmount = dupesDict.ContainsKey(frameNameImport) ? dupesDict[frameNameImport] : 0;
bool discardThisFrame = (sceneDetection && i < frameFilesWithoutLast.Length && sceneFrames.Contains(GetNameNoExt(FrameRename.importFilenames[i + 1]))); // i+2 is in scene detection folder, means i+1 is ugly interp frame
for (int frm = 0; frm < interpFramesAmount; frm++) // Generate frames file lines
{
if (discardThisFrame) // If frame is scene cut frame
{
string frameBeforeScn = i.ToString().PadLeft(Padding.inputFramesRenamed, '0') + Path.GetExtension(FrameRename.importFilenames[i]);
string frameAfterScn = (i + 1).ToString().PadLeft(Padding.inputFramesRenamed, '0') + Path.GetExtension(FrameRename.importFilenames[i + 1]);
string scnChangeNote = $"SCN:{frameBeforeScn}>{frameAfterScn}";
totalFileCount++;
fileContent = WriteFrameWithDupes(dupesAmount, fileContent, totalFileCount, ext, debug, $"[In: {frameName}] [{((frm == 0) ? " Source " : $"Interp {frm}")}]", scnChangeNote);
if (Config.GetInt(Config.Key.sceneChangeFillMode) == 0) // Duplicate last frame
{
int lastNum = totalFileCount;
for (int dupeCount = 1; dupeCount < interpFramesAmount; dupeCount++)
{
totalFileCount++;
fileContent = WriteFrameWithDupes(dupesAmount, fileContent, lastNum, ext, debug, $"[In: {frameName}] [DISCARDED]");
}
}
else
{
for (int dupeCount = 1; dupeCount < interpFramesAmount; dupeCount++)
{
totalFileCount++;
fileContent = WriteFrameWithDupes(dupesAmount, fileContent, totalFileCount, ext, debug, $"[In: {frameName}] [BLEND FRAME]");
}
}
frm = interpFramesAmount;
}
else
{
totalFileCount++;
fileContent = WriteFrameWithDupes(dupesAmount, fileContent, totalFileCount, ext, debug, $"[In: {frameName}] [{((frm == 0) ? " Source " : $"Interp {frm}")}]");
}
}
}
if (totalFileCount > lastOutFileCount)
lastOutFileCount = totalFileCount;
frameFileContents[number] = fileContent;
}
static string WriteFrameWithDupes(int dupesAmount, string fileContent, int frameNum, string ext, bool debug, string debugNote = "", string forcedNote = "")
{
for (int writtenDupes = -1; writtenDupes < dupesAmount; writtenDupes++) // Write duplicates
fileContent += $"file '{Paths.interpDir}/{frameNum.ToString().PadLeft(Padding.interpFrames, '0')}{ext}' # {(debug ? ($"Dupe {(writtenDupes + 1).ToString("000")} {debugNote}").Replace("Dupe 000", " ") : "")}{forcedNote}\n";
return fileContent;
}
static string GetNameNoExt (string path) { return Path.GetFileNameWithoutExtension(path); }
}
}

263
Code/Main/Interpolate.cs Normal file
View File

@@ -0,0 +1,263 @@
using Flowframes;
using Flowframes.Media;
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.Magick;
using Flowframes.Main;
using Flowframes.MiscUtils;
using Flowframes.Os;
using Flowframes.Ui;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using Padding = Flowframes.Data.Padding;
using Utils = Flowframes.Main.InterpolateUtils;
namespace Flowframes
{
public class Interpolate
{
public enum OutMode { VidMp4, VidMkv, VidWebm, VidProRes, VidAvi, VidGif, ImgPng }
public static int currentInputFrameCount;
public static bool currentlyUsingAutoEnc;
public static InterpSettings current;
public static bool canceled = false;
static Stopwatch sw = new Stopwatch();
public static async Task Start()
{
if (!BatchProcessing.busy && Program.busy) return;
canceled = false;
Program.initialRun = false;
Program.mainForm.SetWorking(true);
if (!Utils.InputIsValid(current.inPath, current.outPath, current.outFps, current.interpFactor, current.outMode)) return; // General input checks
if (!Utils.CheckAiAvailable(current.ai)) return; // Check if selected AI pkg is installed
if (!ResumeUtils.resumeNextRun && !Utils.CheckDeleteOldTempFolder()) return; // Try to delete temp folder if an old one exists
if (!Utils.CheckPathValid(current.inPath)) return; // Check if input path/file is valid
if (!(await Utils.CheckEncoderValid())) return; // Check NVENC compat
currentInputFrameCount = await GetFrameCountCached.GetFrameCountAsync(current.inPath);
current.stepByStep = false;
Program.mainForm.SetStatus("Starting...");
if (!ResumeUtils.resumeNextRun)
{
await GetFrames();
if (canceled) return;
sw.Restart();
await PostProcessFrames(false);
}
if (canceled) return;
await ResumeUtils.PrepareResumedRun();
//Task.Run(() => Utils.DeleteInterpolatedInputFrames());
await RunAi(current.interpFolder, current.ai);
if (canceled) return;
Program.mainForm.SetProgress(100);
if(!currentlyUsingAutoEnc)
await CreateVideo.Export(current.interpFolder, current.outPath, current.outMode, false);
if (Config.GetBool(Config.Key.keepTempFolder))
await Task.Run(async () => { await FrameRename.Unrename(); });
await Cleanup();
Program.mainForm.SetWorking(false);
Logger.Log("Total processing time: " + FormatUtils.Time(sw.Elapsed));
sw.Stop();
if(!BatchProcessing.busy)
OsUtils.ShowNotificationIfInBackground("Flowframes", $"Finished interpolation after {FormatUtils.Time(sw.Elapsed)}.");
Program.mainForm.InterpolationDone();
}
public static async Task<int> GetCurrentInputFrameCount ()
{
if (currentInputFrameCount < 2)
currentInputFrameCount = await GetFrameCountCached.GetFrameCountAsync(current.inPath);
return currentInputFrameCount;
}
public static async Task GetFrames ()
{
current.RefreshAlpha();
current.RefreshExtensions(InterpSettings.FrameType.Import);
if (Config.GetBool(Config.Key.scnDetect))
{
Program.mainForm.SetStatus("Extracting scenes from video...");
await FfmpegExtract.ExtractSceneChanges(current.inPath, Path.Combine(current.tempFolder, Paths.scenesDir), current.inFpsDetected, current.inputIsFrames, current.framesExt);
}
if (!current.inputIsFrames) // Extract if input is video, import if image sequence
await ExtractFrames(current.inPath, current.framesFolder, current.alpha);
else
await FfmpegExtract.ImportImagesCheckCompat(current.inPath, current.framesFolder, current.alpha, (await current.GetScaledRes()), true, current.framesExt);
}
public static async Task ExtractFrames(string inPath, string outPath, bool alpha)
{
if (canceled) return;
Program.mainForm.SetStatus("Extracting frames from video...");
current.RefreshExtensions(InterpSettings.FrameType.Import);
bool mpdecimate = Config.GetInt(Config.Key.dedupMode) == 2;
Size res = await Utils.GetOutputResolution(inPath, true, true);
await FfmpegExtract.VideoToFrames(inPath, outPath, alpha, current.inFpsDetected, mpdecimate, false, res, current.framesExt);
if (mpdecimate)
{
int framesLeft = IoUtils.GetAmountOfFiles(outPath, false, "*" + current.framesExt);
int framesDeleted = currentInputFrameCount - framesLeft;
float percentDeleted = ((float)framesDeleted / currentInputFrameCount) * 100f;
string keptPercent = $"{(100f - percentDeleted).ToString("0.0")}%";
if(QuickSettingsTab.trimEnabled)
Logger.Log($"Deduplication: Kept {framesLeft} frames.");
else
Logger.Log($"Deduplication: Kept {framesLeft} ({keptPercent}) frames, deleted {framesDeleted} frames.");
}
if(!Config.GetBool("allowConsecutiveSceneChanges", true))
Utils.FixConsecutiveSceneFrames(Path.Combine(current.tempFolder, Paths.scenesDir), current.framesFolder);
}
public static async Task PostProcessFrames (bool stepByStep)
{
if (canceled) return;
Program.mainForm.SetStatus("Processing frames...");
int extractedFrames = IoUtils.GetAmountOfFiles(current.framesFolder, false, "*" + current.framesExt);
if (!Directory.Exists(current.framesFolder) || currentInputFrameCount <= 0 || extractedFrames < 2)
{
if(extractedFrames == 1)
Cancel("Only a single frame was extracted from your input file!\n\nPossibly your input is an image, not a video?");
else
Cancel("Frame extraction failed!\n\nYour input file might be incompatible.");
}
if (Config.GetInt(Config.Key.dedupMode) == 1)
await Dedupe.Run(current.framesFolder);
else
Dedupe.ClearCache();
if (!Config.GetBool(Config.Key.enableLoop))
{
await Utils.CopyLastFrame(currentInputFrameCount);
}
else
{
FileInfo[] frameFiles = IoUtils.GetFileInfosSorted(current.framesFolder);
string ext = frameFiles.First().Extension;
int lastNum = frameFiles.Last().Name.GetInt() + 1;
string loopFrameTargetPath = Path.Combine(current.framesFolder, lastNum.ToString().PadLeft(Padding.inputFrames, '0') + ext);
File.Copy(frameFiles.First().FullName, loopFrameTargetPath, true);
Logger.Log($"Copied loop frame to {loopFrameTargetPath}.", true);
}
}
public static async Task RunAi(string outpath, AI ai, bool stepByStep = false)
{
if (canceled) return;
await Task.Run(async () => { await Dedupe.CreateDupesFile(current.framesFolder, currentInputFrameCount, current.framesExt); });
await Task.Run(async () => { await FrameRename.Rename(); });
await Task.Run(async () => { await FrameOrder.CreateFrameOrderFile(current.framesFolder, Config.GetBool(Config.Key.enableLoop), current.interpFactor); });
Program.mainForm.SetStatus("Downloading models...");
await ModelDownloader.DownloadModelFiles(ai, current.model.dir);
if (canceled) return;
currentlyUsingAutoEnc = Utils.CanUseAutoEnc(stepByStep, current);
IoUtils.CreateDir(outpath);
List<Task> tasks = new List<Task>();
if (ai.aiName == Implementations.rifeCuda.aiName)
tasks.Add(AiProcess.RunRifeCuda(current.framesFolder, current.interpFactor, current.model.dir));
if (ai.aiName == Implementations.rifeNcnn.aiName)
tasks.Add(AiProcess.RunRifeNcnn(current.framesFolder, outpath, (int)current.interpFactor, current.model.dir));
if (ai.aiName == Implementations.flavrCuda.aiName)
tasks.Add(AiProcess.RunFlavrCuda(current.framesFolder, current.interpFactor, current.model.dir));
if (ai.aiName == Implementations.dainNcnn.aiName)
tasks.Add(AiProcess.RunDainNcnn(current.framesFolder, outpath, current.interpFactor, current.model.dir, Config.GetInt(Config.Key.dainNcnnTilesize, 512)));
if (ai.aiName == Implementations.xvfiCuda.aiName)
tasks.Add(AiProcess.RunXvfiCuda(current.framesFolder, current.interpFactor, current.model.dir));
if (currentlyUsingAutoEnc)
{
Logger.Log($"{Logger.GetLastLine()} (Using Auto-Encode)", true);
tasks.Add(AutoEncode.MainLoop(outpath));
}
Program.mainForm.SetStatus("Running AI...");
await Task.WhenAll(tasks);
}
public static void Cancel(string reason = "", bool noMsgBox = false)
{
if (current == null)
return;
canceled = true;
Program.mainForm.SetStatus("Canceled.");
Program.mainForm.SetProgress(0);
AiProcess.Kill();
AvProcess.Kill();
if (!current.stepByStep && !Config.GetBool(Config.Key.keepTempFolder))
{
if(false /* IOUtils.GetAmountOfFiles(Path.Combine(current.tempFolder, Paths.resumeDir), true) > 0 */) // TODO: Uncomment for 1.23
{
DialogResult dialogResult = MessageBox.Show($"Delete the temp folder (Yes) or keep it for resuming later (No)?", "Delete temporary files?", MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.Yes)
Task.Run(async () => { await IoUtils.TryDeleteIfExistsAsync(current.tempFolder); });
}
else
{
Task.Run(async () => { await IoUtils.TryDeleteIfExistsAsync(current.tempFolder); });
}
}
AutoEncode.busy = false;
Program.mainForm.SetWorking(false);
Program.mainForm.SetTab("interpolation");
Logger.LogIfLastLineDoesNotContainMsg("Canceled interpolation.");
if (!string.IsNullOrWhiteSpace(reason) && !noMsgBox)
Utils.ShowMessage($"Canceled:\n\n{reason}");
}
public static async Task Cleanup(bool ignoreKeepSetting = false, int retriesLeft = 3, bool isRetry = false)
{
if ((!ignoreKeepSetting && Config.GetBool(Config.Key.keepTempFolder)) || !Program.busy) return;
if (!isRetry)
Logger.Log("Deleting temporary files...");
try
{
await Task.Run(async () => { Directory.Delete(current.tempFolder, true); });
}
catch (Exception e)
{
Logger.Log("Cleanup Error: " + e.Message, true);
if(retriesLeft > 0)
{
await Task.Delay(1000);
await Cleanup(ignoreKeepSetting, retriesLeft - 1, true);
}
}
}
}
}

View File

@@ -0,0 +1,147 @@
using Flowframes.Media;
using Flowframes.IO;
using System;
using System.IO;
using System.Threading.Tasks;
using Flowframes.MiscUtils;
using System.Windows.Forms;
namespace Flowframes.Main
{
using static Interpolate;
class InterpolateSteps
{
public static async Task Run(string step)
{
Logger.Log($"[SBS] Running step '{step}'", true);
canceled = false;
Program.mainForm.SetWorking(true);
if(current == null)
{
Logger.Log($"[SBS] Getting new current settings", true);
current = Program.mainForm.GetCurrentSettings();
}
else
{
Logger.Log($"[SBS] Updating current settings", true);
current = Program.mainForm.UpdateCurrentSettings(current);
}
current.RefreshAlpha();
current.stepByStep = true;
if (!InterpolateUtils.InputIsValid(current.inPath, current.outPath, current.outFps, current.interpFactor, current.outMode)) return; // General input checks
if (!InterpolateUtils.CheckPathValid(current.inPath)) return; // Check if input path/file is valid
if (step.Contains("Extract Frames"))
await ExtractFramesStep();
if (step.Contains("Run Interpolation"))
await InterpolateStep();
if (step.Contains("Export"))
await CreateOutputVid();
if (step.Contains("Cleanup"))
await Reset();
Program.mainForm.SetWorking(false);
Program.mainForm.SetStatus("Done running step.");
Logger.Log("Done running this step.");
}
public static async Task ExtractFramesStep()
{
if (!(await IoUtils.TryDeleteIfExistsAsync(current.framesFolder)))
{
InterpolateUtils.ShowMessage("Failed to delete existing frames folder - Make sure no file is opened in another program!", "Error");
return;
}
currentInputFrameCount = await GetFrameCountCached.GetFrameCountAsync(current.inPath);
await GetFrames();
await PostProcessFrames(true);
}
public static async Task InterpolateStep()
{
if (!InterpolateUtils.CheckAiAvailable(current.ai)) return;
current.framesFolder = Path.Combine(current.tempFolder, Paths.framesDir);
if (IoUtils.GetAmountOfFiles(current.framesFolder, false, "*") < 2)
{
if (Config.GetBool(Config.Key.sbsRunPreviousStepIfNeeded))
{
Logger.Log($"There are no extracted frames to interpolate - Running extract step first...");
await ExtractFramesStep();
}
if (IoUtils.GetAmountOfFiles(current.framesFolder, false, "*") < 2)
{
InterpolateUtils.ShowMessage("There are no extracted frames that can be interpolated!\nDid you run the extraction step?", "Error");
return;
}
}
if (!(await IoUtils.TryDeleteIfExistsAsync(current.interpFolder)))
{
InterpolateUtils.ShowMessage("Failed to delete existing frames folder - Make sure no file is opened in another program!", "Error");
return;
}
currentInputFrameCount = await GetFrameCountCached.GetFrameCountAsync(current.inPath);
if (Config.GetBool(Config.Key.sbsAllowAutoEnc) && !(await InterpolateUtils.CheckEncoderValid())) return;
if (canceled) return;
Program.mainForm.SetStatus("Running AI...");
await RunAi(current.interpFolder, current.ai, true);
await Task.Run(async () => { await FrameRename.Unrename(); }); // Get timestamps back
Program.mainForm.SetProgress(0);
}
public static async Task CreateOutputVid()
{
if (IoUtils.GetAmountOfFiles(current.interpFolder, false) < 2)
{
if (Config.GetBool(Config.Key.sbsRunPreviousStepIfNeeded))
{
Logger.Log($"There are no interpolated frames to export - Running interpolation step first...");
await InterpolateStep();
}
if (IoUtils.GetAmountOfFiles(current.interpFolder, false) < 2)
{
Cancel($"There are no interpolated frames to encode!\n\nDid you delete the folder?");
return;
}
}
if (!(await InterpolateUtils.CheckEncoderValid())) return;
string[] outFrames = IoUtils.GetFilesSorted(current.interpFolder, current.interpExt);
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 " +
"this step as the video was already created in the \"Interpolate\" step.", "Error");
return;
}
await CreateVideo.Export(current.interpFolder, current.outPath, current.outMode, true);
}
public static async Task Reset()
{
DialogResult dialog = MessageBox.Show($"Are you sure you want to remove all temporary files?", "Are you sure?", MessageBoxButtons.YesNo);
if (dialog == DialogResult.Yes)
await Cleanup(true);
}
}
}

View File

@@ -0,0 +1,328 @@
using Flowframes.Media;
using Flowframes.Data;
using Flowframes.Forms;
using Flowframes.IO;
using Flowframes.MiscUtils;
using Flowframes.Os;
using Flowframes.Ui;
using System;
using System.Collections.Generic;
using System.Diagnostics;
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;
using Padding = Flowframes.Data.Padding;
namespace Flowframes.Main
{
class InterpolateUtils
{
public static async Task CopyLastFrame(int lastFrameNum)
{
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') + I.current.framesExt);
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 int GetProgressWaitTime(int numFrames)
{
float hddMultiplier = !Program.lastInputPathIsSsd ? 2f : 1f;
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)
{
string basePath = inPath.GetParentDir();
if (Config.GetInt(Config.Key.tempFolderLoc) == 1)
basePath = outPath.GetParentDir();
if (Config.GetInt(Config.Key.tempFolderLoc) == 2)
basePath = outPath;
if (Config.GetInt(Config.Key.tempFolderLoc) == 3)
basePath = Paths.GetExeDir();
if (Config.GetInt(Config.Key.tempFolderLoc) == 4)
{
string custPath = Config.Get(Config.Key.tempDirCustom);
if (IoUtils.IsDirValid(custPath))
basePath = custPath;
}
return Path.Combine(basePath, Path.GetFileNameWithoutExtension(inPath).StripBadChars().Remove(" ").Trunc(30, false) + "-temp");
}
public static bool InputIsValid(string inDir, string outDir, Fraction fpsOut, float factor, I.OutMode outMode)
{
try
{
bool passes = true;
bool isFile = !IoUtils.IsPathDirectory(inDir);
if ((passes && isFile && !IoUtils.IsFileValid(inDir)) || (!isFile && !IoUtils.IsDirValid(inDir)))
{
ShowMessage("Input path is not valid!");
passes = false;
}
if (passes && !IoUtils.IsDirValid(outDir))
{
ShowMessage("Output path is not valid!");
passes = false;
}
if (passes && fpsOut.GetFloat() < 1f || fpsOut.GetFloat() > 1000f)
{
ShowMessage($"Invalid output frame rate ({fpsOut.GetFloat()}).\nMust be 1-1000.");
passes = false;
}
string fpsLimitValue = Config.Get(Config.Key.maxFps);
float fpsLimit = (fpsLimitValue.Contains("/") ? new Fraction(Config.Get(Config.Key.maxFps)).GetFloat() : fpsLimitValue.GetFloat());
if (outMode == I.OutMode.VidGif && fpsOut.GetFloat() > 50 && !(fpsLimit > 0 && fpsLimit <= 50))
Logger.Log($"Warning: GIF will be encoded at 50 FPS instead of {fpsOut.GetFloat()} as the format doesn't support frame rates that high.");
if (!passes)
I.Cancel("Invalid settings detected.", true);
return passes;
}
catch(Exception e)
{
Logger.Log($"Failed to run InputIsValid: {e.Message}\n{e.StackTrace}", true);
return false;
}
}
public static bool CheckAiAvailable(AI ai)
{
if (IoUtils.GetAmountOfFiles(Path.Combine(Paths.GetPkgPath(), ai.pkgDir), true) < 1)
{
ShowMessage("The selected AI is not installed!", "Error");
I.Cancel("Selected AI not available.", true);
return false;
}
if (I.current.ai.aiName.ToUpper().Contains("CUDA") && NvApi.gpuList.Count < 1)
{
ShowMessage("Warning: No Nvidia GPU was detected. CUDA might fall back to CPU!\n\nTry an NCNN implementation instead if you don't have an Nvidia GPU.", "Error");
if (!Config.GetBool("allowCudaWithoutDetectedGpu", true))
{
I.Cancel("No CUDA-capable graphics card available.", true);
return false;
}
}
return true;
}
public static bool CheckDeleteOldTempFolder()
{
if (!IoUtils.TryDeleteIfExists(I.current.tempFolder))
{
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();
return false;
}
return true;
}
public static bool CheckPathValid(string path)
{
if (IoUtils.IsPathDirectory(path))
{
if (!IoUtils.IsDirValid(path))
{
ShowMessage("Input directory is not valid.");
I.Cancel();
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;
}
public static bool IsVideoValid(string videoPath)
{
if (videoPath == null || !IoUtils.IsFileValid(videoPath))
return false;
return true;
}
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 GetMediaResolutionCached.GetSizeAsync(inputPath);
return GetOutputResolution(resolution, print, returnZeroIfUnchanged);
}
public static Size GetOutputResolution(Size inputRes, bool print = false, bool returnZeroIfUnchanged = false)
{
int maxHeightValue = Config.GetInt(Config.Key.maxVidHeight);
int maxHeight = RoundDivisibleBy(maxHeightValue, FfmpegCommands.GetPadding());
if (inputRes.Height > maxHeight)
{
float factor = (float)maxHeight / inputRes.Height;
Logger.Log($"Un-rounded downscaled size: {(inputRes.Width * factor).ToString("0.00")}x{maxHeightValue}", true);
int width = RoundDivisibleBy((inputRes.Width * factor).RoundToInt(), FfmpegCommands.GetPadding());
if (print)
Logger.Log($"Video is bigger than the maximum - Downscaling to {width}x{maxHeight}.");
return new Size(width, maxHeight);
}
else
{
if (returnZeroIfUnchanged)
return new Size();
else
return inputRes;
}
}
public static int RoundDivisibleBy(int number, int divisibleBy) // Round to a number that's divisible by 2 (for h264 etc)
{
int a = (number / divisibleBy) * divisibleBy; // Smaller multiple
int b = a + divisibleBy; // Larger multiple
return (number - a > b - number) ? b : a; // Return of closest of two
}
public static bool CanUseAutoEnc(bool stepByStep, InterpSettings current)
{
AutoEncode.UpdateChunkAndBufferSizes();
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(Config.Key.sbsAllowAutoEnc))
{
Logger.Log($"Not Using AutoEnc: Using step-by-step mode, but 'sbsAllowAutoEnc' is false.", true);
return false;
}
if (!stepByStep && Config.GetInt(Config.Key.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(Config.Key.uhdThresh);
}
public static void FixConsecutiveSceneFrames(string sceneFramesPath, string sourceFramesPath)
{
if (!Directory.Exists(sceneFramesPath) || IoUtils.GetAmountOfFiles(sceneFramesPath, false) < 1)
return;
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)
{
if (sceneFramesToDelete.Contains(scnFrame))
continue;
int sourceIndexForScnFrame = sourceFrames.IndexOf(scnFrame); // Get source index of scene frame
if ((sourceIndexForScnFrame + 1) == sourceFrames.Count)
continue;
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 + I.current.framesExt));
}
}
}

121
Code/Main/ResumeUtils.cs Normal file
View File

@@ -0,0 +1,121 @@
using Flowframes.Data;
using Flowframes.IO;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Flowframes.MiscUtils;
using Flowframes.Ui;
using I = Flowframes.Interpolate;
namespace Flowframes.Main
{
class ResumeUtils
{
public static bool resumeNextRun;
public static float timeBetweenSaves = 10;
public static int minFrames = 100;
public static int safetyDelayFrames = 50;
public static string resumeFilename = "resume.ini";
public static string interpSettingsFilename = "interpSettings.ini";
public static string filenameMapFilename = "frameFilenames.ini";
public static Stopwatch timeSinceLastSave = new Stopwatch();
public static void Save ()
{
if (timeSinceLastSave.IsRunning && timeSinceLastSave.ElapsedMilliseconds < (timeBetweenSaves * 1000f).RoundToInt()) return;
int frames = (int)Math.Round((float)InterpolationProgress.interpolatedInputFramesCount / I.current.interpFactor) - safetyDelayFrames;
if (frames < 1) return;
timeSinceLastSave.Restart();
Directory.CreateDirectory(Path.Combine(I.current.tempFolder, Paths.resumeDir));
SaveState(frames);
SaveInterpSettings();
SaveFilenameMap();
}
static void SaveState (int frames)
{
ResumeState state = new ResumeState(I.currentlyUsingAutoEnc, frames);
string filePath = Path.Combine(I.current.tempFolder, Paths.resumeDir, resumeFilename);
File.WriteAllText(filePath, state.ToString());
}
static async Task SaveFilenameMap ()
{
string filePath = Path.Combine(I.current.tempFolder, Paths.resumeDir, filenameMapFilename);
if (File.Exists(filePath) && IoUtils.GetFilesize(filePath) > 0)
return;
string fileContent = "";
int counter = 0;
foreach (string file in FrameRename.importFilenames)
{
if (counter % 1000 == 0) await Task.Delay(1);
fileContent += $"{file}\n";
counter++;
}
File.WriteAllText(filePath, fileContent);
}
static void SaveInterpSettings ()
{
string filepath = Path.Combine(I.current.tempFolder, Paths.resumeDir, interpSettingsFilename);
File.WriteAllText(filepath, I.current.Serialize());
}
public static void LoadTempFolder (string tempFolderPath)
{
string resumeFolderPath = Path.Combine(tempFolderPath, Paths.resumeDir);
string interpSettingsPath = Path.Combine(resumeFolderPath, interpSettingsFilename);
InterpSettings interpSettings = new InterpSettings(File.ReadAllText(interpSettingsPath));
Program.mainForm.LoadBatchEntry(interpSettings);
}
public static async Task PrepareResumedRun ()
{
if (!resumeNextRun) return;
string stateFilepath = Path.Combine(I.current.tempFolder, Paths.resumeDir, resumeFilename);
ResumeState state = new ResumeState(File.ReadAllText(stateFilepath));
string fileMapFilepath = Path.Combine(I.current.tempFolder, Paths.resumeDir, filenameMapFilename);
List<string> inputFrameLines = File.ReadAllLines(fileMapFilepath).Where(l => l.Trim().Length > 3).ToList();
List<string> inputFrames = inputFrameLines.Select(l => Path.Combine(I.current.framesFolder, l.Split('|')[1])).ToList();
for (int i = 0; i < state.interpolatedInputFrames; i++)
{
IoUtils.TryDeleteIfExists(inputFrames[i]);
if (i % 1000 == 0) await Task.Delay(1);
}
Directory.Move(I.current.interpFolder, I.current.interpFolder + Paths.prevSuffix); // Move existing interp frames
Directory.CreateDirectory(I.current.interpFolder); // Re-create empty interp folder
LoadFilenameMap();
}
static void LoadFilenameMap()
{
List<string> files = new List<string>();
string filePath = Path.Combine(I.current.tempFolder, Paths.resumeDir, filenameMapFilename);
string[] fileLines = File.ReadAllLines(filePath);
foreach (string line in fileLines)
{
if (line.Trim().Length < 3) continue;
files.Add(line.Trim());
}
FrameRename.importFilenames = files.ToArray();
}
}
}

211
Code/Media/AvProcess.cs Normal file
View File

@@ -0,0 +1,211 @@
using Flowframes.IO;
using Flowframes.Os;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Flowframes.MiscUtils;
using Microsoft.VisualBasic;
namespace Flowframes
{
class AvProcess
{
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;
public static string lastOutputFfmpeg;
public enum LogMode { Visible, OnlyLastLine, Hidden }
static LogMode currentLogMode;
static bool showProgressBar;
static readonly 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);
}
public static async Task RunFfmpeg(string args, LogMode logMode, string loglevel, TaskType taskType = TaskType.Other, bool progressBar = false)
{
await RunFfmpeg(args, "", logMode, loglevel, taskType, progressBar);
}
public static async Task RunFfmpeg(string args, string workingDir, LogMode logMode, TaskType taskType = TaskType.Other, bool progressBar = false)
{
await RunFfmpeg(args, workingDir, logMode, defLogLevel, taskType, progressBar);
}
public static async Task RunFfmpeg(string args, string workingDir, LogMode logMode, string loglevel, TaskType taskType = TaskType.Other, bool progressBar = false)
{
lastOutputFfmpeg = "";
currentLogMode = logMode;
showProgressBar = progressBar;
Process ffmpeg = OsUtils.NewProcess(true);
timeSinceLastOutput.Restart();
lastAvProcess = ffmpeg;
lastTask = taskType;
if (string.IsNullOrWhiteSpace(loglevel))
loglevel = defLogLevel;
string beforeArgs = $"-hide_banner -stats -loglevel {loglevel} -y";
if(!string.IsNullOrWhiteSpace(workingDir))
ffmpeg.StartInfo.Arguments = $"{GetCmdArg()} cd /D {workingDir.Wrap()} & {Path.Combine(GetAvDir(), "ffmpeg.exe").Wrap()} {beforeArgs} {args}";
else
ffmpeg.StartInfo.Arguments = $"{GetCmdArg()} cd /D {GetAvDir().Wrap()} & ffmpeg.exe {beforeArgs} {args}";
if (logMode != LogMode.Hidden) Logger.Log("Running FFmpeg...", false);
Logger.Log($"ffmpeg {beforeArgs} {args}", true, false, "ffmpeg");
ffmpeg.OutputDataReceived += FfmpegOutputHandler;
ffmpeg.ErrorDataReceived += FfmpegOutputHandler;
ffmpeg.Start();
ffmpeg.BeginOutputReadLine();
ffmpeg.BeginErrorReadLine();
while (!ffmpeg.HasExited)
await Task.Delay(1);
if(progressBar)
Program.mainForm.SetProgress(0);
}
static void FfmpegOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
timeSinceLastOutput.Restart();
if (Interpolate.canceled || outLine == null || outLine.Data == null)
return;
string line = outLine.Data;
lastOutputFfmpeg = lastOutputFfmpeg + "\n" + line;
bool hidden = currentLogMode == LogMode.Hidden;
if (HideMessage(line)) // Don't print certain warnings
hidden = true;
bool replaceLastLine = currentLogMode == LogMode.OnlyLastLine;
if (line.StartsWith("frame="))
line = FormatUtils.BeautifyFfmpegStats(line);
Logger.Log(line, hidden, replaceLastLine, "ffmpeg");
if (line.Contains(".srt: Invalid data found"))
Logger.Log($"Warning: Failed to encode subtitle track {line.Split(':')[2]}. This track will be missing in the output file.");
if (line.Contains("Could not open file"))
Interpolate.Cancel($"FFmpeg Error: {line}");
if (line.Contains("No NVENC capable devices found") || line.MatchesWildcard("*nvcuda.dll*"))
Interpolate.Cancel($"FFmpeg Error: {line}\nMake sure you have an NVENC-capable Nvidia GPU.");
if (!hidden && showProgressBar && line.Contains("Time:"))
{
Regex timeRegex = new Regex("(?<=Time:).*(?= )");
UpdateFfmpegProgress(timeRegex.Match(line).Value);
}
}
static bool HideMessage (string msg)
{
string[] hiddenMsgs = new string[] { "can produce invalid output", "pixel format", "provided invalid" };
foreach (string str in hiddenMsgs)
if (msg.MatchesWildcard($"*{str}*"))
return true;
return false;
}
public static async Task<string> GetFfmpegOutputAsync(string args, bool setBusy = false, bool progressBar = false)
{
timeSinceLastOutput.Restart();
if (Program.busy) setBusy = false;
lastOutputFfmpeg = "";
showProgressBar = progressBar;
Process ffmpeg = OsUtils.NewProcess(true);
lastAvProcess = ffmpeg;
ffmpeg.StartInfo.Arguments = $"{GetCmdArg()} cd /D {GetAvDir().Wrap()} & ffmpeg.exe -hide_banner -y -stats {args}";
Logger.Log($"ffmpeg {args}", true, false, "ffmpeg");
if (setBusy) Program.mainForm.SetWorking(true);
lastOutputFfmpeg = await OsUtils.GetOutputAsync(ffmpeg);
while (!ffmpeg.HasExited) await Task.Delay(50);
while(timeSinceLastOutput.ElapsedMilliseconds < 200) await Task.Delay(50);
if (setBusy) Program.mainForm.SetWorking(false);
return lastOutputFfmpeg;
}
public static string GetFfprobeOutput (string args)
{
Process ffprobe = OsUtils.NewProcess(true);
ffprobe.StartInfo.Arguments = $"{GetCmdArg()} cd /D {GetAvDir().Wrap()} & ffprobe.exe {args}";
Logger.Log($"ffprobe {args}", true, false, "ffmpeg");
ffprobe.Start();
ffprobe.WaitForExit();
string output = ffprobe.StandardOutput.ReadToEnd();
string err = ffprobe.StandardError.ReadToEnd();
if (!string.IsNullOrWhiteSpace(err)) output += "\n" + err;
return output;
}
public static void UpdateFfmpegProgress(string ffmpegTime)
{
Form1 form = Program.mainForm;
long currInDuration = (form.currInDurationCut < form.currInDuration) ? form.currInDurationCut : form.currInDuration;
if (currInDuration < 1)
{
Program.mainForm.SetProgress(0);
return;
}
long total = currInDuration / 100;
long current = FormatUtils.TimestampToMs(ffmpegTime);
int progress = Convert.ToInt32(current / total);
Program.mainForm.SetProgress(progress);
}
static string GetAvDir ()
{
return Path.Combine(Paths.GetPkgPath(), Paths.audioVideoDir);
}
static string GetCmdArg ()
{
return "/C";
}
public static async Task SetBusyWhileRunning ()
{
if (Program.busy) return;
await Task.Delay(100);
while(!lastAvProcess.HasExited)
await Task.Delay(10);
}
}
}

52
Code/Media/FfmpegAlpha.cs Normal file
View File

@@ -0,0 +1,52 @@
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.Main;
using Flowframes.MiscUtils;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using static Flowframes.AvProcess;
namespace Flowframes.Media
{
partial class FfmpegAlpha : FfmpegCommands
{
public static async Task ExtractAlphaDir(string rgbDir, string alphaDir)
{
Directory.CreateDirectory(alphaDir);
foreach (FileInfo file in IoUtils.GetFileInfosSorted(rgbDir))
{
string args = $"-i {file.FullName.Wrap()} -vf \"format=yuva444p16le,alphaextract,format=yuv420p,{GetPadFilter()}\" {Path.Combine(alphaDir, file.Name).Wrap()}";
await RunFfmpeg(args, LogMode.Hidden);
}
}
public static async Task RemoveAlpha(string inputDir, string outputDir, string fillColor = "black")
{
Directory.CreateDirectory(outputDir);
foreach (FileInfo file in IoUtils.GetFileInfosSorted(inputDir))
{
string outPath = Path.Combine(outputDir, "_" + file.Name);
Size s = IoUtils.GetImage(file.FullName).Size;
string args = $" -f lavfi -i color={fillColor}:s={s.Width}x{s.Height} -i {file.FullName.Wrap()} -filter_complex overlay=0:0:shortest=1 {outPath.Wrap()}";
await RunFfmpeg(args, LogMode.Hidden);
file.Delete();
File.Move(outPath, file.FullName);
}
}
public static async Task MergeAlphaIntoRgb(string rgbDir, int rgbPad, string alphaDir, int aPad, bool deleteAlphaDir)
{
string filter = "-filter_complex [0:v:0][1:v:0]alphamerge[out] -map [out]";
string args = $"-i \"{rgbDir}/%{rgbPad}d.png\" -i \"{alphaDir}/%{aPad}d.png\" {filter} \"{rgbDir}/%{rgbPad}d.png\"";
await RunFfmpeg(args, LogMode.Hidden);
if (deleteAlphaDir)
await IoUtils.TryDeleteIfExistsAsync(alphaDir);
}
}
}

View File

@@ -0,0 +1,86 @@
using Flowframes.IO;
using Flowframes.Ui;
using System.IO;
using System.Threading.Tasks;
using static Flowframes.AvProcess;
using Utils = Flowframes.Media.FfmpegUtils;
using I = Flowframes.Interpolate;
namespace Flowframes.Media
{
partial class FfmpegAudioAndMetadata : FfmpegCommands
{
#region Mux From Input
public static async Task MergeStreamsFromInput (string inputVideo, string interpVideo, string tempFolder, bool shortest)
{
if (!File.Exists(inputVideo) && !I.current.inputIsFrames)
{
Logger.Log("Warning: Input video file not found, can't copy audio/subtitle streams to output video!");
return;
}
string containerExt = Path.GetExtension(interpVideo);
string tempPath = Path.Combine(tempFolder, $"vid{containerExt}");
string outPath = Path.Combine(tempFolder, $"muxed{containerExt}");
IoUtils.TryDeleteIfExists(tempPath);
File.Move(interpVideo, tempPath);
string inName = Path.GetFileName(tempPath);
string outName = Path.GetFileName(outPath);
string subArgs = "-c:s " + Utils.GetSubCodecForContainer(containerExt);
bool audioCompat = Utils.ContainerSupportsAllAudioFormats(I.current.outMode, GetAudioCodecs(inputVideo));
string audioArgs = audioCompat ? "" : Utils.GetAudioFallbackArgs(I.current.outMode);
if (!audioCompat)
Logger.Log("Warning: Input audio format(s) not fully supported in output container - Will re-encode.", true, false, "ffmpeg");
bool audio = Config.GetBool(Config.Key.keepAudio);
bool subs = Config.GetBool(Config.Key.keepSubs);
bool meta = Config.GetBool(Config.Key.keepMeta);
if (!audio)
audioArgs = "-an";
if (!subs || (subs && !Utils.ContainerSupportsSubs(containerExt)))
subArgs = "-sn";
bool isMkv = I.current.outMode == I.OutMode.VidMkv;
string mkvFix = isMkv ? "-max_interleave_delta 0" : ""; // https://reddit.com/r/ffmpeg/comments/efddfs/starting_new_cluster_due_to_timestamp/
string metaArg = (isMkv && meta) ? "-map 1:t?" : ""; // https://reddit.com/r/ffmpeg/comments/fw4jnh/how_to_make_ffmpeg_keep_attached_images_in_mkv_as/
string shortestArg = shortest ? "-shortest" : "";
if (QuickSettingsTab.trimEnabled)
{
string otherStreamsName = $"otherStreams{containerExt}";
string[] trim = FfmpegExtract.GetTrimArgs();
string args1 = $"{trim[0]} -i {inputVideo.Wrap()} {trim[1]} -map 0 -map -0:v -map -0:d -c copy {audioArgs} {subArgs} {otherStreamsName}"; // Extract trimmed
await RunFfmpeg(args1, tempFolder, LogMode.Hidden);
string args2 = $"-i {inName} -i {otherStreamsName} -map 0:v:0 -map 1:a:? -map 1:s:? {metaArg} -c copy {audioArgs} {subArgs} {mkvFix} {shortestArg} {outName}"; // Merge interp + trimmed original
await RunFfmpeg(args2, tempFolder, LogMode.Hidden);
IoUtils.TryDeleteIfExists(Path.Combine(tempFolder, otherStreamsName));
}
else // If trimming is disabled we can pull the streams directly from the input file
{
string args = $"-i {inName} -i {inputVideo.Wrap()} -map 0:v:0 -map 1:a:? -map 1:s:? {metaArg} -c copy {audioArgs} {subArgs} {mkvFix} {shortestArg} {outName}";
await RunFfmpeg(args, tempFolder, LogMode.Hidden);
}
if (File.Exists(outPath) && IoUtils.GetFilesize(outPath) > 512)
{
File.Delete(tempPath);
File.Move(outPath, interpVideo);
}
else
{
File.Move(tempPath, interpVideo); // Muxing failed, move unmuxed video file back
}
}
#endregion
}
}

View File

@@ -0,0 +1,282 @@
using Flowframes.Media;
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.Main;
using Flowframes.MiscUtils;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using static Flowframes.AvProcess;
using Utils = Flowframes.Media.FfmpegUtils;
namespace Flowframes
{
class FfmpegCommands
{
//public static string padFilter = "pad=width=ceil(iw/2)*2:height=ceil(ih/2)*2:color=black@0";
public static string hdrFilter = @"-vf zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p";
public static string pngCompr = "-compression_level 3";
public static string mpDecDef = "\"mpdecimate\"";
public static string mpDecAggr = "\"mpdecimate=hi=64*32:lo=64*32:frac=0.1\"";
public static int GetPadding ()
{
return (Interpolate.current.ai.aiName == Implementations.flavrCuda.aiName) ? 8 : 2; // FLAVR input needs to be divisible by 8
}
public static string GetPadFilter ()
{
int padPixels = GetPadding();
return $"pad=width=ceil(iw/{padPixels})*{padPixels}:height=ceil(ih/{padPixels})*{padPixels}:color=black@0";
}
public static async Task ConcatVideos(string concatFile, string outPath, int looptimes = -1, bool showLog = true)
{
Logger.Log($"ConcatVideos('{Path.GetFileName(concatFile)}', '{outPath}', {looptimes})", true, false, "ffmpeg");
if(showLog)
Logger.Log($"Merging videos...", false, Logger.GetLastLine().Contains("frame"));
IoUtils.RenameExistingFile(outPath);
string loopStr = (looptimes > 0) ? $"-stream_loop {looptimes}" : "";
string vfrFilename = Path.GetFileName(concatFile);
string args = $" {loopStr} -vsync 1 -f concat -i {vfrFilename} -c copy -movflags +faststart -fflags +genpts {outPath.Wrap()}";
await RunFfmpeg(args, concatFile.GetParentDir(), LogMode.Hidden, TaskType.Merge);
}
public static async Task LoopVideo(string inputFile, int times, bool delSrc = false)
{
string pathNoExt = Path.ChangeExtension(inputFile, null);
string ext = Path.GetExtension(inputFile);
string loopSuffix = Config.Get(Config.Key.exportNamePatternLoop).Replace("[LOOPS]", $"{times}").Replace("[PLAYS]", $"{times + 1}");
string outpath = $"{pathNoExt}{loopSuffix}{ext}";
IoUtils.RenameExistingFile(outpath);
string args = $" -stream_loop {times} -i {inputFile.Wrap()} -c copy {outpath.Wrap()}";
await RunFfmpeg(args, LogMode.Hidden);
if (delSrc)
DeleteSource(inputFile);
}
public static async Task ChangeSpeed(string inputFile, float newSpeedPercent, bool delSrc = false)
{
string pathNoExt = Path.ChangeExtension(inputFile, null);
string ext = Path.GetExtension(inputFile);
float val = newSpeedPercent / 100f;
string speedVal = (1f / val).ToString("0.0000").Replace(",", ".");
string args = " -itsscale " + speedVal + " -i \"" + inputFile + "\" -c copy \"" + pathNoExt + "-" + newSpeedPercent + "pcSpeed" + ext + "\"";
await RunFfmpeg(args, LogMode.OnlyLastLine);
if (delSrc)
DeleteSource(inputFile);
}
public static long GetDuration(string inputFile)
{
Logger.Log($"GetDuration({inputFile}) - Reading Duration using ffprobe.", true, false, "ffmpeg");
string args = $" -v panic -select_streams v:0 -show_entries format=duration -of csv=s=x:p=0 -sexagesimal {inputFile.Wrap()}";
string output = GetFfprobeOutput(args);
return FormatUtils.TimestampToMs(output);
}
public static async Task<Fraction> GetFramerate(string inputFile, bool preferFfmpeg = false)
{
Logger.Log($"GetFramerate(inputFile = '{inputFile}', preferFfmpeg = {preferFfmpeg})", true, false, "ffmpeg");
Fraction ffprobeFps = new Fraction(0, 1);
Fraction ffmpegFps = new Fraction(0, 1);
try
{
string ffprobeOutput = await GetVideoInfoCached.GetFfprobeInfoAsync(inputFile, "r_frame_rate");
string fpsStr = ffprobeOutput.SplitIntoLines().First();
string[] numbers = fpsStr.Split('=')[1].Split('/');
Logger.Log($"Fractional FPS from ffprobe: {numbers[0]}/{numbers[1]} = {((float)numbers[0].GetInt() / numbers[1].GetInt())}", true, false, "ffmpeg");
ffprobeFps = new Fraction(numbers[0].GetInt(), numbers[1].GetInt());
}
catch (Exception ffprobeEx)
{
Logger.Log("GetFramerate ffprobe Error: " + ffprobeEx.Message, true, false);
}
try
{
string ffmpegOutput = await GetVideoInfoCached.GetFfmpegInfoAsync(inputFile);
string[] entries = ffmpegOutput.Split(',');
foreach (string entry in entries)
{
if (entry.Contains(" fps") && !entry.Contains("Input ")) // Avoid reading FPS from the filename, in case filename contains "fps"
{
string num = entry.Replace(" fps", "").Trim().Replace(",", ".");
Logger.Log($"Float FPS from ffmpeg: {num.GetFloat()}", true, false, "ffmpeg");
ffmpegFps = new Fraction(num.GetFloat());
}
}
}
catch(Exception ffmpegEx)
{
Logger.Log("GetFramerate ffmpeg Error: " + ffmpegEx.Message, true, false);
}
if (preferFfmpeg)
{
if (ffmpegFps.GetFloat() > 0)
return ffmpegFps;
else
return ffprobeFps;
}
else
{
if (ffprobeFps.GetFloat() > 0)
return ffprobeFps;
else
return ffmpegFps;
}
}
public static Size GetSize(string inputFile)
{
Logger.Log($"GetSize('{inputFile}')", true, false, "ffmpeg");
string args = $" -v panic -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 {inputFile.Wrap()}";
string[] outputLines = GetFfprobeOutput(args).SplitIntoLines();
foreach(string line in outputLines)
{
if (!line.Contains("x") || line.Trim().Length < 3)
continue;
string[] numbers = line.Split('x');
return new Size(numbers[0].GetInt(), numbers[1].GetInt());
}
return new Size(0, 0);
}
public static async Task<int> GetFrameCountAsync(string inputFile)
{
Logger.Log($"GetFrameCountAsync('{inputFile}') - Trying ffprobe first.", true, false, "ffmpeg");
int frames = await ReadFrameCountFfprobeAsync(inputFile, Config.GetBool(Config.Key.ffprobeFrameCount)); // Try reading frame count with ffprobe
if (frames > 0) return frames;
Logger.Log($"Failed to get frame count using ffprobe (frames = {frames}). Trying to read with ffmpeg.", true, false, "ffmpeg");
frames = await ReadFrameCountFfmpegAsync(inputFile); // Try reading frame count with ffmpeg
if (frames > 0) return frames;
Logger.Log("Failed to get total frame count of video.");
return 0;
}
static int ReadFrameCountFromDuration (string inputFile, long durationMs, float fps)
{
float durationSeconds = durationMs / 1000f;
float frameCount = durationSeconds * fps;
int frameCountRounded = frameCount.RoundToInt();
Logger.Log($"ReadFrameCountFromDuration: Got frame count of {frameCount}, rounded to {frameCountRounded}");
return frameCountRounded;
}
static async Task<int> ReadFrameCountFfprobeAsync(string inputFile, bool readFramesSlow)
{
string args = $" -v panic -threads 0 -select_streams v:0 -show_entries stream=nb_frames -of default=noprint_wrappers=1 {inputFile.Wrap()}";
if (readFramesSlow)
{
Logger.Log("Counting total frames using FFprobe. This can take a moment...");
await Task.Delay(10);
args = $" -v panic -threads 0 -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 {inputFile.Wrap()}";
}
string info = GetFfprobeOutput(args);
string[] entries = info.SplitIntoLines();
try
{
if (readFramesSlow)
return info.GetInt();
foreach (string entry in entries)
{
if (entry.Contains("nb_frames="))
return entry.GetInt();
}
}
catch { }
return -1;
}
static async Task<int> ReadFrameCountFfmpegAsync (string inputFile)
{
string args = $" -loglevel panic -stats -i {inputFile.Wrap()} -map 0:v:0 -c copy -f null - ";
string info = await GetFfmpegOutputAsync(args, true, true);
try
{
string[] lines = info.SplitIntoLines();
string lastLine = lines.Last();
return lastLine.Substring(0, lastLine.IndexOf("fps")).GetInt();
}
catch
{
return -1;
}
}
public static async Task<VidExtraData> GetVidExtraInfo(string inputFile)
{
string ffprobeOutput = await GetVideoInfoCached.GetFfprobeInfoAsync(inputFile);
VidExtraData data = new VidExtraData(ffprobeOutput);
return data;
}
public static async Task<bool> IsEncoderCompatible(string enc)
{
Logger.Log($"IsEncoderCompatible('{enc}')", true, false, "ffmpeg");
string args = $"-loglevel error -f lavfi -i color=black:s=540x540 -vframes 1 -an -c:v {enc} -f null -";
string output = await GetFfmpegOutputAsync(args);
return !output.ToLower().Contains("error");
}
public static string GetAudioCodec(string path, int streamIndex = -1)
{
Logger.Log($"GetAudioCodec('{Path.GetFileName(path)}', {streamIndex})", true, false, "ffmpeg");
string stream = (streamIndex < 0) ? "a" : $"{streamIndex}";
string args = $"-v panic -show_streams -select_streams {stream} -show_entries stream=codec_name {path.Wrap()}";
string info = GetFfprobeOutput(args);
string[] entries = info.SplitIntoLines();
foreach (string entry in entries)
{
if (entry.Contains("codec_name="))
return entry.Split('=')[1];
}
return "";
}
public static List<string> GetAudioCodecs(string path, int streamIndex = -1)
{
Logger.Log($"GetAudioCodecs('{Path.GetFileName(path)}', {streamIndex})", true, false, "ffmpeg");
List<string> codecNames = new List<string>();
string args = $"-loglevel panic -select_streams a -show_entries stream=codec_name {path.Wrap()}";
string info = GetFfprobeOutput(args);
string[] entries = info.SplitIntoLines();
foreach (string entry in entries)
{
if (entry.Contains("codec_name="))
codecNames.Add(entry.Remove("codec_name=").Trim());
}
return codecNames;
}
public static void DeleteSource(string path)
{
Logger.Log("[FFCmds] Deleting input file/dir: " + path, true);
if (IoUtils.IsPathDirectory(path) && Directory.Exists(path))
Directory.Delete(path, true);
if (!IoUtils.IsPathDirectory(path) && File.Exists(path))
File.Delete(path);
}
}
}

106
Code/Media/FfmpegEncode.cs Normal file
View File

@@ -0,0 +1,106 @@
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.MiscUtils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using static Flowframes.AvProcess;
using Utils = Flowframes.Media.FfmpegUtils;
namespace Flowframes.Media
{
partial class FfmpegEncode : FfmpegCommands
{
public static async Task FramesToVideo(string framesFile, string outPath, Interpolate.OutMode outMode, Fraction fps, Fraction resampleFps, VidExtraData extraData, LogMode logMode = LogMode.OnlyLastLine, bool isChunk = false)
{
if (logMode != LogMode.Hidden)
Logger.Log((resampleFps.GetFloat() <= 0) ? "Encoding video..." : $"Encoding video resampled to {resampleFps.GetString()} FPS...");
IoUtils.RenameExistingFile(outPath);
Directory.CreateDirectory(outPath.GetParentDir());
string encArgs = Utils.GetEncArgs(Utils.GetCodec(outMode));
if (!isChunk && outMode == Interpolate.OutMode.VidMp4) encArgs += $" -movflags +faststart";
string inArg = $"-f concat -i {Path.GetFileName(framesFile)}";
string linksDir = Path.Combine(framesFile + Paths.symlinksSuffix);
if (Config.GetBool(Config.Key.allowSymlinkEncoding, true) && Symlinks.SymlinksAllowed())
{
if (await Symlinks.MakeSymlinksForEncode(framesFile, linksDir, Padding.interpFrames))
inArg = $"-i \"{linksDir}/%{Padding.interpFrames}d{GetConcatFileExt(framesFile)}\"";
}
string extraArgs = Config.Get(Config.Key.ffEncArgs);
string rate = fps.ToString().Replace(",", ".");
List<string> filters = new List<string>();
if (resampleFps.GetFloat() >= 0.1f)
filters.Add($"fps=fps={resampleFps}");
if (Config.GetBool(Config.Key.keepColorSpace) && extraData.HasAllValues())
{
Logger.Log($"Applying color transfer ({extraData.colorSpace}).", true, false, "ffmpeg");
filters.Add($"scale=out_color_matrix={extraData.colorSpace}");
extraArgs += $" -colorspace {extraData.colorSpace} -color_primaries {extraData.colorPrimaries} -color_trc {extraData.colorTransfer} -color_range:v \"{extraData.colorRange}\"";
}
string vf = filters.Count > 0 ? $"-vf {string.Join(",", filters)}" : "";
string args = $"-vsync 0 -r {rate} {inArg} {encArgs} {vf} {GetAspectArg(extraData)} {extraArgs} -threads {Config.GetInt(Config.Key.ffEncThreads)} {outPath.Wrap()}";
await RunFfmpeg(args, framesFile.GetParentDir(), logMode, "error", TaskType.Encode, !isChunk);
IoUtils.TryDeleteIfExists(linksDir);
}
public static string GetConcatFileExt (string concatFilePath)
{
return Path.GetExtension(File.ReadAllLines(concatFilePath).FirstOrDefault().Split('\'')[1]);
}
static string GetAspectArg (VidExtraData extraData)
{
if (!string.IsNullOrWhiteSpace(extraData.displayRatio) && !extraData.displayRatio.MatchesWildcard("*N/A*"))
return $"-aspect {extraData.displayRatio}";
else
return "";
}
public static async Task FramesToFrames(string framesFile, string outDir, Fraction fps, Fraction resampleFps, string format = "png", LogMode logMode = LogMode.OnlyLastLine)
{
Directory.CreateDirectory(outDir);
string inArg = $"-f concat -i {Path.GetFileName(framesFile)}";
string linksDir = Path.Combine(framesFile + Paths.symlinksSuffix);
if (Config.GetBool(Config.Key.allowSymlinkEncoding, true) && Symlinks.SymlinksAllowed())
{
if (await Symlinks.MakeSymlinksForEncode(framesFile, linksDir, Padding.interpFrames))
inArg = $"-i {Path.GetFileName(framesFile) + Paths.symlinksSuffix}/%{Padding.interpFrames}d{GetConcatFileExt(framesFile)}";
}
string rate = fps.ToString().Replace(",", ".");
string vf = (resampleFps.GetFloat() < 0.1f) ? "" : $"-vf fps=fps={resampleFps}";
string compression = format == "png" ? pngCompr : "-q:v 1";
string args = $"-vsync 0 -r {rate} {inArg} {compression} {vf} \"{outDir}/%{Padding.interpFrames}d.{format}\"";
await RunFfmpeg(args, framesFile.GetParentDir(), logMode, "error", TaskType.Encode, true);
IoUtils.TryDeleteIfExists(linksDir);
}
public static async Task FramesToGifConcat(string framesFile, string outPath, Fraction rate, bool palette, int colors, Fraction resampleFps, LogMode logMode = LogMode.OnlyLastLine)
{
if (rate.GetFloat() > 50f && (resampleFps.GetFloat() > 50f || resampleFps.GetFloat() < 1))
resampleFps = new Fraction(50, 1); // Force limit framerate as encoding above 50 will cause problems
if (logMode != LogMode.Hidden)
Logger.Log((resampleFps.GetFloat() <= 0) ? $"Encoding GIF..." : $"Encoding GIF resampled to {resampleFps.GetFloat().ToString().Replace(",", ".")} FPS...");
string framesFilename = Path.GetFileName(framesFile);
string dither = Config.Get(Config.Key.gifDitherType).Split(' ').First();
string paletteFilter = palette ? $"-vf \"split[s0][s1];[s0]palettegen={colors}[p];[s1][p]paletteuse=dither={dither}\"" : "";
string fpsFilter = (resampleFps.GetFloat() <= 0) ? "" : $"fps=fps={resampleFps}";
string vf = FormatUtils.ConcatStrings(new string[] { paletteFilter, fpsFilter });
string extraArgs = Config.Get(Config.Key.ffEncArgs);
string args = $"-f concat -r {rate} -i {framesFilename.Wrap()} -gifflags -offsetting {vf} {extraArgs} {outPath.Wrap()}";
await RunFfmpeg(args, framesFile.GetParentDir(), LogMode.OnlyLastLine, "error", TaskType.Encode);
}
}
}

312
Code/Media/FfmpegExtract.cs Normal file
View File

@@ -0,0 +1,312 @@
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.Main;
using Flowframes.MiscUtils;
using Flowframes.Ui;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using static Flowframes.AvProcess;
namespace Flowframes.Media
{
partial class FfmpegExtract : FfmpegCommands
{
public static async Task ExtractSceneChanges(string inPath, string outDir, Fraction rate, bool inputIsFrames, string format)
{
Logger.Log("Extracting scene changes...");
Directory.CreateDirectory(outDir);
string inArg = $"-i {inPath.Wrap()}";
if (inputIsFrames)
{
string concatFile = Path.Combine(Paths.GetDataPath(), "png-scndetect-concat-temp.ini");
FfmpegUtils.CreateConcatFile(inPath, concatFile, Filetypes.imagesInterpCompat);
inArg = $"-f concat -safe 0 -i {concatFile.Wrap()}";
}
string scnDetect = $"-vf \"select='gt(scene,{Config.GetFloatString(Config.Key.scnDetectValue)})'\"";
string rateArg = (rate.GetFloat() > 0) ? $"-r {rate}" : "";
string args = $"-vsync 0 {GetTrimArg(true)} {inArg} {GetImgArgs(format)} {rateArg} {scnDetect} -frame_pts 1 -s 256x144 {GetTrimArg(false)} \"{outDir}/%{Padding.inputFrames}d{format}\"";
LogMode logMode = await Interpolate.GetCurrentInputFrameCount() > 50 ? LogMode.OnlyLastLine : LogMode.Hidden;
await RunFfmpeg(args, logMode, inputIsFrames ? "panic" : "warning", TaskType.ExtractFrames, true);
bool hiddenLog = await Interpolate.GetCurrentInputFrameCount() <= 50;
int amount = IoUtils.GetAmountOfFiles(outDir, false);
Logger.Log($"Detected {amount} scene {(amount == 1 ? "change" : "changes")}.".Replace(" 0 ", " no "), false, !hiddenLog);
}
static string GetImgArgs(string extension, bool includePixFmt = true, bool alpha = false)
{
extension = extension.ToLower().Remove(".").Replace("jpeg", "jpg");
string pixFmt = "-pix_fmt rgb24";
string args = "";
if (extension.Contains("png"))
{
pixFmt = alpha ? "rgba" : "rgb24";
args = $"{pngCompr}";
}
if (extension.Contains("jpg"))
{
pixFmt = "yuv420p";
args = $"-q:v 1";
}
if (extension.Contains("webp"))
{
pixFmt = "yuv420p";
args = $"-q:v 100";
}
if (includePixFmt)
args += $" -pix_fmt {pixFmt}";
return args;
}
public static async Task VideoToFrames(string inputFile, string framesDir, bool alpha, Fraction rate, bool deDupe, bool delSrc, Size size, string format)
{
Logger.Log("Extracting video frames from input video...");
Logger.Log($"VideoToFrames() - Alpha: {alpha} - Rate: {rate} - Size: {size} - Format: {format}", true, false, "ffmpeg");
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
IoUtils.CreateDir(framesDir);
string mpStr = deDupe ? ((Config.GetInt(Config.Key.mpdecimateMode) == 0) ? mpDecDef : mpDecAggr) : "";
string filters = FormatUtils.ConcatStrings(new[] { GetPadFilter(), mpStr });
string vf = filters.Length > 2 ? $"-vf {filters}" : "";
string rateArg = (rate.GetFloat() > 0) ? $" -r {rate}" : "";
string args = $"{GetTrimArg(true)} -i {inputFile.Wrap()} {GetImgArgs(format, true, alpha)} -vsync 0 {rateArg} -frame_pts 1 {vf} {sizeStr} {GetTrimArg(false)} \"{framesDir}/%{Padding.inputFrames}d{format}\"";
LogMode logMode = await Interpolate.GetCurrentInputFrameCount() > 50 ? LogMode.OnlyLastLine : LogMode.Hidden;
await RunFfmpeg(args, logMode, TaskType.ExtractFrames, true);
int amount = IoUtils.GetAmountOfFiles(framesDir, false, "*" + format);
Logger.Log($"Extracted {amount} {(amount == 1 ? "frame" : "frames")} from input.", false, true);
await Task.Delay(1);
if (delSrc)
DeleteSource(inputFile);
}
public static async Task ImportImagesCheckCompat(string inPath, string outPath, bool alpha, Size size, bool showLog, string format)
{
bool compatible = await Task.Run(async () => { return AreImagesCompatible(inPath, Config.GetInt(Config.Key.maxVidHeight)); });
if (!alpha && compatible)
{
await CopyImages(inPath, outPath, showLog);
}
else
{
await ImportImages(inPath, outPath, alpha, size, showLog, format);
}
}
public static async Task CopyImages (string inpath, string outpath, bool showLog)
{
if (showLog) Logger.Log($"Copying images from {new DirectoryInfo(inpath).Name}...");
Directory.CreateDirectory(outpath);
Dictionary<string, string> moveFromTo = new Dictionary<string, string>();
int counter = 0;
foreach (FileInfo file in IoUtils.GetFileInfosSorted(inpath))
{
string newFilename = counter.ToString().PadLeft(Padding.inputFrames, '0') + file.Extension;
moveFromTo.Add(file.FullName, Path.Combine(outpath, newFilename));
counter++;
}
if (Config.GetBool(Config.Key.allowSymlinkEncoding) && Config.GetBool(Config.Key.allowSymlinkImport, true))
{
Dictionary<string, string> moveFromToSwapped = moveFromTo.ToDictionary(x => x.Value, x => x.Key); // From/To => To/From (Link/Target)
await Symlinks.CreateSymlinksParallel(moveFromToSwapped);
}
else
{
await Task.Run(async () => {
foreach (KeyValuePair<string, string> moveFromToPair in moveFromTo)
File.Copy(moveFromToPair.Key, moveFromToPair.Value);
});
}
}
static bool AreImagesCompatible (string inpath, int maxHeight)
{
NmkdStopwatch sw = new NmkdStopwatch();
string[] validExtensions = Filetypes.imagesInterpCompat; // = new string[] { ".jpg", ".jpeg", ".png" };
FileInfo[] files = IoUtils.GetFileInfosSorted(inpath);
if (files.Length < 1)
{
Logger.Log("[AreImagesCompatible] Sequence not compatible: No files found.", true);
return false;
}
bool allSameExtension = files.All(x => x.Extension == files.First().Extension);
if (!allSameExtension)
{
Logger.Log($"Sequence not compatible: Not all files have the same extension.", true);
return false;
}
bool allValidExtension = files.All(x => validExtensions.Contains(x.Extension));
if (!allValidExtension)
{
Logger.Log($"Sequence not compatible: Not all files have a valid extension ({string.Join(", ", validExtensions)}).", true);
return false;
}
Image[] randomSamples = files.OrderBy(arg => Guid.NewGuid()).Take(10).Select(x => IoUtils.GetImage(x.FullName)).ToArray();
bool allSameSize = randomSamples.All(i => i.Size == randomSamples.First().Size);
if (!allSameSize)
{
Logger.Log($"Sequence not compatible: Not all images have the same dimensions [{sw.GetElapsedStr()}].", true);
return false;
}
int div = GetPadding();
bool allDivBy2 = randomSamples.All(i => (i.Width % div == 0) && (i.Height % div == 0));
if (!allDivBy2)
{
Logger.Log($"Sequence not compatible: Not all image dimensions are divisible by {div} [{sw.GetElapsedStr()}].", true);
return false;
}
bool allSmallEnough = randomSamples.All(i => (i.Height <= maxHeight));
if (!allSmallEnough)
{
Logger.Log($"Sequence not compatible: Image dimensions above max size [{sw.GetElapsedStr()}].", true);
return false;
}
bool all24Bit = randomSamples.All(i => (i.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb));
if (!all24Bit)
{
Logger.Log($"Sequence not compatible: Some images are not 24-bit (8bpp) [{sw.GetElapsedStr()}].", true);
return false;
}
Interpolate.current.framesExt = files.First().Extension;
Logger.Log($"Sequence compatible! [{sw.GetElapsedStr()}]", true);
return true;
}
public static async Task ImportImages(string inPath, string outPath, bool alpha, Size size, bool showLog, string format)
{
if (showLog) Logger.Log($"Importing images from {new DirectoryInfo(inPath).Name}...");
Logger.Log($"ImportImages() - Alpha: {alpha} - Size: {size} - Format: {format}", true, false, "ffmpeg");
IoUtils.CreateDir(outPath);
string concatFile = Path.Combine(Paths.GetDataPath(), "import-concat-temp.ini");
FfmpegUtils.CreateConcatFile(inPath, concatFile, Filetypes.imagesInterpCompat);
string inArg = $"-f concat -safe 0 -i {concatFile.Wrap()}";
string linksDir = Path.Combine(concatFile + Paths.symlinksSuffix);
if (Config.GetBool(Config.Key.allowSymlinkEncoding, true) && Symlinks.SymlinksAllowed())
{
if (await Symlinks.MakeSymlinksForEncode(concatFile, linksDir, Padding.interpFrames))
inArg = $"-i \"{linksDir}/%{Padding.interpFrames}d{FfmpegEncode.GetConcatFileExt(concatFile)}\"";
}
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
string vf = $"-vf {GetPadFilter()}";
string args = $"-r 25 {inArg} {GetImgArgs(format, true, alpha)} {sizeStr} -vsync 0 -start_number 0 {vf} \"{outPath}/%{Padding.inputFrames}d{format}\"";
LogMode logMode = IoUtils.GetAmountOfFiles(inPath, false) > 50 ? LogMode.OnlyLastLine : LogMode.Hidden;
await RunFfmpeg(args, logMode, "panic", TaskType.ExtractFrames);
}
public static string[] GetTrimArgs()
{
return new string[] { GetTrimArg(true), GetTrimArg(false) };
}
public static string GetTrimArg(bool input)
{
if (!QuickSettingsTab.trimEnabled)
return "";
int fastSeekThresh = 180;
bool fastSeek = QuickSettingsTab.trimStartSecs > fastSeekThresh;
string arg = "";
if (input)
{
if (fastSeek)
arg += $"-ss {QuickSettingsTab.trimStartSecs - fastSeekThresh}";
else
return arg;
}
else
{
if (fastSeek)
{
arg += $"-ss {fastSeekThresh}";
long trimTimeSecs = QuickSettingsTab.trimEndSecs - QuickSettingsTab.trimStartSecs;
if (QuickSettingsTab.doTrimEnd)
arg += $" -to {fastSeekThresh + trimTimeSecs}";
}
else
{
arg += $"-ss {QuickSettingsTab.trimStart}";
if (QuickSettingsTab.doTrimEnd)
arg += $" -to {QuickSettingsTab.trimEnd}";
}
}
return arg;
}
public static async Task ImportSingleImage(string inputFile, string outPath, Size size)
{
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
bool isPng = (Path.GetExtension(outPath).ToLower() == ".png");
string comprArg = isPng ? pngCompr : "";
string pixFmt = "-pix_fmt " + (isPng ? $"rgb24 {comprArg}" : "yuvj420p");
string args = $"-i {inputFile.Wrap()} {comprArg} {sizeStr} {pixFmt} -vf {GetPadFilter()} {outPath.Wrap()}";
await RunFfmpeg(args, LogMode.Hidden, TaskType.ExtractFrames);
}
public static async Task ExtractSingleFrame(string inputFile, string outputPath, int frameNum)
{
bool isPng = (Path.GetExtension(outputPath).ToLower() == ".png");
string comprArg = isPng ? pngCompr : "";
string pixFmt = "-pix_fmt " + (isPng ? $"rgb24 {comprArg}" : "yuvj420p");
string args = $"-i {inputFile.Wrap()} -vf \"select=eq(n\\,{frameNum})\" -vframes 1 {pixFmt} {outputPath.Wrap()}";
await RunFfmpeg(args, LogMode.Hidden, TaskType.ExtractFrames);
}
public static async Task ExtractLastFrame(string inputFile, string outputPath, Size size)
{
if (QuickSettingsTab.trimEnabled)
return;
if (IoUtils.IsPathDirectory(outputPath))
outputPath = Path.Combine(outputPath, "last.png");
bool isPng = (Path.GetExtension(outputPath).ToLower() == ".png");
string comprArg = isPng ? pngCompr : "";
string pixFmt = "-pix_fmt " + (isPng ? $"rgb24 {comprArg}" : "yuvj420p");
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
string trim = QuickSettingsTab.trimEnabled ? $"-ss {QuickSettingsTab.GetTrimEndMinusOne()} -to {QuickSettingsTab.trimEnd}" : "";
string sseof = string.IsNullOrWhiteSpace(trim) ? "-sseof -1" : "";
string args = $"{sseof} -i {inputFile.Wrap()} -update 1 {pixFmt} {sizeStr} {trim} {outputPath.Wrap()}";
await RunFfmpeg(args, LogMode.Hidden, TaskType.ExtractFrames);
}
}
}

301
Code/Media/FfmpegUtils.cs Normal file
View File

@@ -0,0 +1,301 @@
using Flowframes.IO;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Flowframes.Media
{
class FfmpegUtils
{
public enum Codec { H264, H265, H264Nvenc, H265Nvenc, Av1, Vp9, ProRes, AviRaw }
public static Codec GetCodec(Interpolate.OutMode mode)
{
if (mode == Interpolate.OutMode.VidMp4 || mode == Interpolate.OutMode.VidMkv)
{
int mp4MkvEnc = Config.GetInt(Config.Key.mp4Enc);
if (mp4MkvEnc == 0) return Codec.H264;
if (mp4MkvEnc == 1) return Codec.H265;
if (mp4MkvEnc == 2) return Codec.H264Nvenc;
if (mp4MkvEnc == 3) return Codec.H265Nvenc;
if (mp4MkvEnc == 4) return Codec.Av1;
}
if (mode == Interpolate.OutMode.VidWebm)
return Codec.Vp9;
if (mode == Interpolate.OutMode.VidProRes)
return Codec.ProRes;
if (mode == Interpolate.OutMode.VidAvi)
return Codec.AviRaw;
return Codec.H264;
}
public static string GetEnc(Codec codec)
{
switch (codec)
{
case Codec.H264: return "libx264";
case Codec.H265: return "libx265";
case Codec.H264Nvenc: return "h264_nvenc";
case Codec.H265Nvenc: return "hevc_nvenc";
case Codec.Av1: return "libsvtav1";
case Codec.Vp9: return "libvpx-vp9";
case Codec.ProRes: return "prores_ks";
case Codec.AviRaw: return Config.Get(Config.Key.aviCodec);
}
return "libx264";
}
public static string GetEncArgs (Codec codec)
{
string args = $"-c:v {GetEnc(codec)} ";
if(codec == Codec.H264)
{
string preset = Config.Get(Config.Key.ffEncPreset).ToLower().Remove(" ");
args += $"-crf {Config.GetInt(Config.Key.h264Crf)} -preset {preset} -pix_fmt {GetPixFmt()}";
}
if (codec == Codec.H265)
{
string preset = Config.Get(Config.Key.ffEncPreset).ToLower().Remove(" ");
int crf = Config.GetInt(Config.Key.h265Crf);
args += $"{(crf > 0 ? $"-crf {crf}" : "-x265-params lossless=1")} -preset {preset} -pix_fmt {GetPixFmt()}";
}
if (codec == Codec.H264Nvenc)
{
int cq = (Config.GetInt(Config.Key.h264Crf) * 1.1f).RoundToInt();
args += $"-b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")} -pix_fmt {GetPixFmt()}";
}
if (codec == Codec.H265Nvenc)
{
int cq = (Config.GetInt(Config.Key.h265Crf) * 1.1f).RoundToInt();
args += $"-b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")} -pix_fmt {GetPixFmt()}";
}
if (codec == Codec.Av1)
{
int cq = (Config.GetInt(Config.Key.av1Crf) * 1.0f).RoundToInt();
args += $"-b:v 0 -qp {cq} -g 240 {GetSvtAv1Speed()} -tile_rows 2 -tile_columns 2 -pix_fmt {GetPixFmt()}";
}
if (codec == Codec.Vp9)
{
int crf = Config.GetInt(Config.Key.vp9Crf);
string qualityStr = (crf > 0) ? $"-b:v 0 -crf {crf}" : "-lossless 1";
args += $"{qualityStr} {GetVp9Speed()} -tile-columns 2 -tile-rows 2 -row-mt 1 -pix_fmt {GetPixFmt()}";
}
if(codec == Codec.ProRes)
{
args += $"-profile:v {Config.GetInt(Config.Key.proResProfile)} -pix_fmt {GetPixFmt()}";
}
if (codec == Codec.AviRaw)
{
args += $"-pix_fmt {Config.Get(Config.Key.aviColors)}";
}
return args;
}
static string GetVp9Speed ()
{
string preset = Config.Get(Config.Key.ffEncPreset).ToLower().Remove(" ");
string arg = "";
if (preset == "veryslow") arg = "0";
if (preset == "slower") arg = "1";
if (preset == "slow") arg = "2";
if (preset == "medium") arg = "3";
if (preset == "fast") arg = "4";
if (preset == "faster") arg = "5";
if (preset == "veryfast") arg = "4 -deadline realtime";
return $"-cpu-used {arg}";
}
static string GetSvtAv1Speed()
{
string preset = Config.Get(Config.Key.ffEncPreset).ToLower().Remove(" ");
string arg = "8";
if (preset == "veryslow") arg = "2";
if (preset == "slower") arg = "3";
if (preset == "slow") arg = "4";
if (preset == "medium") arg = "5";
if (preset == "fast") arg = "6";
if (preset == "faster") arg = "7";
if (preset == "veryfast") arg = "8";
return $"-preset {arg}";
}
static string GetPixFmt ()
{
switch (Config.GetInt(Config.Key.pixFmt))
{
case 0: return "yuv420p";
case 1: return "yuv444p";
case 2: return "yuv420p10le";
case 3: return "yuv444p10le";
}
return "yuv420p";
}
public static bool ContainerSupportsAllAudioFormats (Interpolate.OutMode outMode, List<string> codecs)
{
if(codecs.Count < 1)
Logger.Log($"Warning: ContainerSupportsAllAudioFormats() was called, but codec list has {codecs.Count} entries.", true, false, "ffmpeg");
foreach (string format in codecs)
{
if (!ContainerSupportsAudioFormat(outMode, format))
return false;
}
return true;
}
public static bool ContainerSupportsAudioFormat (Interpolate.OutMode outMode, string format)
{
bool supported = false;
string alias = GetAudioExt(format);
string[] formatsMp4 = new string[] { "m4a", "ac3", "dts" };
string[] formatsMkv = new string[] { "m4a", "ac3", "dts", "ogg", "mp2", "mp3", "wav", "wma" };
string[] formatsWebm = new string[] { "ogg" };
string[] formatsProres = new string[] { "m4a", "ac3", "dts", "wav" };
string[] formatsAvi = new string[] { "m4a", "ac3", "dts" };
switch (outMode)
{
case Interpolate.OutMode.VidMp4: supported = formatsMp4.Contains(alias); break;
case Interpolate.OutMode.VidMkv: supported = formatsMkv.Contains(alias); break;
case Interpolate.OutMode.VidWebm: supported = formatsWebm.Contains(alias); break;
case Interpolate.OutMode.VidProRes: supported = formatsProres.Contains(alias); break;
case Interpolate.OutMode.VidAvi: supported = formatsAvi.Contains(alias); break;
}
Logger.Log($"Checking if {outMode} supports audio format '{format}' ({alias}): {supported}", true, false, "ffmpeg");
return supported;
}
public static string GetExt(Interpolate.OutMode outMode, bool dot = true)
{
string ext = dot ? "." : "";
switch (outMode)
{
case Interpolate.OutMode.VidMp4: ext += "mp4"; break;
case Interpolate.OutMode.VidMkv: ext += "mkv"; break;
case Interpolate.OutMode.VidWebm: ext += "webm"; break;
case Interpolate.OutMode.VidProRes: ext += "mov"; break;
case Interpolate.OutMode.VidAvi: ext += "avi"; break;
case Interpolate.OutMode.VidGif: ext += "gif"; break;
}
return ext;
}
public static string GetAudioExt(string videoFile, int streamIndex = -1)
{
return GetAudioExt(FfmpegCommands.GetAudioCodec(videoFile, streamIndex));
}
public static string GetAudioExt (string codec)
{
if (codec.StartsWith("pcm_"))
return "wav";
switch (codec)
{
case "vorbis": return "ogg";
case "opus": return "ogg";
case "mp2": return "mp2";
case "mp3": return "mp3";
case "aac": return "m4a";
case "ac3": return "ac3";
case "eac3": return "ac3";
case "dts": return "dts";
case "alac": return "wav";
case "flac": return "wav";
case "wmav1": return "wma";
case "wmav2": return "wma";
}
return "unsupported";
}
public static string GetAudioFallbackArgs (Interpolate.OutMode outMode)
{
string codec = "aac";
string bitrate = $"{Config.GetInt(Config.Key.aacBitrate, 160)}";
if(outMode == Interpolate.OutMode.VidMkv || outMode == Interpolate.OutMode.VidWebm)
{
codec = "libopus";
bitrate = $"{Config.GetInt(Config.Key.opusBitrate, 128)}";
}
return $"-c:a {codec} -b:a {bitrate}k -ac 2";
}
public static string GetAudioExtForContainer(string containerExt)
{
containerExt = containerExt.Remove(".");
string ext = "m4a";
if (containerExt == "webm" || containerExt == "mkv")
ext = "ogg";
return ext;
}
public static string GetSubCodecForContainer(string containerExt)
{
containerExt = containerExt.Remove(".");
if (containerExt == "mp4") return "mov_text";
if (containerExt == "webm") return "webvtt";
return "copy"; // Default: Copy SRT subs
}
public static bool ContainerSupportsSubs(string containerExt, bool showWarningIfNotSupported = true)
{
containerExt = containerExt.Remove(".");
bool supported = (containerExt == "mp4" || containerExt == "mkv" || containerExt == "webm" || containerExt == "mov");
Logger.Log($"Subtitles {(supported ? "are supported" : "not supported")} by {containerExt.ToUpper()}", true);
if (showWarningIfNotSupported && Config.GetBool(Config.Key.keepSubs) && !supported)
Logger.Log($"Warning: Subtitle transfer is enabled, but {containerExt.ToUpper()} does not support subtitles properly. MKV is recommended instead.");
return supported;
}
public static void CreateConcatFile (string inputFilesDir, string outputPath, string[] validExtensions = null)
{
string concatFileContent = "";
string[] files = IoUtils.GetFilesSorted(inputFilesDir);
foreach (string file in files)
{
if (validExtensions != null && !validExtensions.Contains(Path.GetExtension(file).ToLower()))
continue;
concatFileContent += $"file '{file.Replace(@"\", "/")}'\n";
}
File.WriteAllText(outputPath, concatFileContent);
}
}
}

View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading.Tasks;
using Flowframes.Data;
using Flowframes.IO;
namespace Flowframes.Media
{
class GetFrameCountCached
{
public static Dictionary<PseudoUniqueFile, int> cache = new Dictionary<PseudoUniqueFile, int>();
public static async Task<int> GetFrameCountAsync(string path)
{
Logger.Log($"Getting frame count ({path})", true);
long filesize = IoUtils.GetFilesize(path);
PseudoUniqueFile hash = new PseudoUniqueFile(path, filesize);
if (filesize > 0 && CacheContains(hash))
{
Logger.Log($"Cache contains this hash, using cached value.", true);
return GetFromCache(hash);
}
else
{
Logger.Log($"Hash not cached, reading frame count.", true);
}
int frameCount;
if (IoUtils.IsPathDirectory(path))
frameCount = IoUtils.GetAmountOfFiles(path, false);
else
frameCount = await FfmpegCommands.GetFrameCountAsync(path);
Logger.Log($"Adding hash with value {frameCount} to cache.", true);
cache.Add(hash, frameCount);
return frameCount;
}
private static bool CacheContains (PseudoUniqueFile hash)
{
foreach(KeyValuePair<PseudoUniqueFile, int> entry in cache)
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
return true;
return false;
}
private static int GetFromCache(PseudoUniqueFile hash)
{
foreach (KeyValuePair<PseudoUniqueFile, int> entry in cache)
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
return entry.Value;
return 0;
}
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading.Tasks;
using Flowframes.Data;
using Flowframes.IO;
namespace Flowframes.Media
{
class GetMediaResolutionCached
{
public static Dictionary<PseudoUniqueFile, Size> cache = new Dictionary<PseudoUniqueFile, Size>();
public static async Task<Size> GetSizeAsync(string path)
{
Logger.Log($"Getting media resolution ({path})", true);
long filesize = IoUtils.GetFilesize(path);
PseudoUniqueFile hash = new PseudoUniqueFile(path, filesize);
if (filesize > 0 && CacheContains(hash))
{
Logger.Log($"Cache contains this hash, using cached value.", true);
return GetFromCache(hash);
}
else
{
Logger.Log($"Hash not cached, reading resolution.", true);
}
Size size;
size = await IoUtils.GetVideoOrFramesRes(path);
Logger.Log($"Adding hash with value {size} to cache.", true);
cache.Add(hash, size);
return size;
}
private static bool CacheContains(PseudoUniqueFile hash)
{
foreach (KeyValuePair<PseudoUniqueFile, Size> entry in cache)
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
return true;
return false;
}
private static Size GetFromCache(PseudoUniqueFile hash)
{
foreach (KeyValuePair<PseudoUniqueFile, Size> entry in cache)
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
return entry.Value;
return new Size();
}
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.Os;
namespace Flowframes.Media
{
class GetVideoInfoCached
{
enum InfoType { Ffmpeg, Ffprobe };
static Dictionary<PseudoUniqueFile, string> ffmpegCache = new Dictionary<PseudoUniqueFile, string>();
static Dictionary<PseudoUniqueFile, string> ffprobeCache = new Dictionary<PseudoUniqueFile, string>();
public static async Task<string> GetFfmpegInfoAsync(string path, string lineFilter = "")
{
return await GetInfoAsync(path, InfoType.Ffmpeg, lineFilter);
}
public static async Task<string> GetFfprobeInfoAsync(string path, string lineFilter = "")
{
return await GetInfoAsync(path, InfoType.Ffprobe, lineFilter);
}
static async Task<string> GetInfoAsync(string path, InfoType type, string lineFilter)
{
Logger.Log($"Get{type}InfoAsync({path})", true);
Dictionary<PseudoUniqueFile, string> cacheDict = new Dictionary<PseudoUniqueFile, string>(type == InfoType.Ffmpeg ? ffmpegCache : ffprobeCache);
long filesize = IoUtils.GetFilesize(path);
PseudoUniqueFile hash = new PseudoUniqueFile(path, filesize);
if (filesize > 0 && CacheContains(hash, ref cacheDict))
{
Logger.Log($"Returning cached {type} info.", true);
return GetFromCache(hash, ref cacheDict);
}
Process process = OsUtils.NewProcess(true);
string avPath = Path.Combine(Paths.GetPkgPath(), Paths.audioVideoDir);
if(type == InfoType.Ffmpeg)
process.StartInfo.Arguments = $"/C cd /D {avPath.Wrap()} & ffmpeg.exe -hide_banner -y -stats -i {path.Wrap()}";
if (type == InfoType.Ffprobe)
process.StartInfo.Arguments = $"/C cd /D {avPath.Wrap()} & ffprobe -v quiet -show_format -show_streams {path.Wrap()}";
string output = await OsUtils.GetOutputAsync(process);
if (type == InfoType.Ffmpeg)
ffmpegCache.Add(hash, output);
if (type == InfoType.Ffprobe)
ffprobeCache.Add(hash, output);
if (!string.IsNullOrWhiteSpace(lineFilter.Trim()))
output = string.Join("\n", output.SplitIntoLines().Where(x => x.Contains(lineFilter)).ToArray());
return output;
}
private static bool CacheContains(PseudoUniqueFile hash, ref Dictionary<PseudoUniqueFile, string> cacheDict)
{
foreach (KeyValuePair<PseudoUniqueFile, string> entry in cacheDict)
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
return true;
return false;
}
private static string GetFromCache(PseudoUniqueFile hash, ref Dictionary<PseudoUniqueFile, string> cacheDict)
{
foreach (KeyValuePair<PseudoUniqueFile, string> entry in cacheDict)
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
return entry.Value;
return "";
}
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.MiscUtils
{
class BackgroundTaskManager
{
public static ulong currentId = 0;
public static List<RunningTask> runningTasks = new List<RunningTask>();
public class RunningTask
{
public NmkdStopwatch timer;
public string name;
public ulong id;
public int timeoutSeconds;
public RunningTask (string name, ulong id, int timeoutSeconds)
{
this.name = name;
this.id = id;
this.timeoutSeconds = timeoutSeconds;
timer = new NmkdStopwatch();
}
}
public static bool IsBusy ()
{
Logger.Log($"[BgTaskMgr] BackgroundTaskManager is busy - {runningTasks.Count} tasks running.", true);
return runningTasks.Count > 0;
}
public static void ClearExpired ()
{
foreach(RunningTask task in runningTasks)
{
if(task.timer.sw.ElapsedMilliseconds > task.timeoutSeconds * 1000)
{
Logger.Log($"[BgTaskMgr] Task with ID {task.id} timed out, has been running for {task.timer.GetElapsedStr()}!", true);
runningTasks.Remove(task);
}
}
}
public static ulong Add(string name = "Unnamed Task", int timeoutSeconds = 120)
{
ulong id = currentId;
runningTasks.Add(new RunningTask(name, currentId, timeoutSeconds));
currentId++;
return id;
}
public static void Remove(ulong id)
{
foreach(RunningTask task in new List<RunningTask>(runningTasks))
{
if(task.id == id)
{
Logger.Log($"[BgTaskMgr] Task '{task.name}' has finished after {task.timer.GetElapsedStr()} (Timeout {task.timeoutSeconds}s)", true);
runningTasks.Remove(task);
}
}
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
namespace Flowframes.MiscUtils
{
class Benchmarker
{
// Benchmark a method with return type (via Delegate/Func)
public static object BenchmarkMethod(string methodName, Delegate method, params object[] args)
{
NmkdStopwatch sw = new NmkdStopwatch();
var returnVal = method.DynamicInvoke(args);
Logger.Log($"Ran {methodName} in {sw.GetElapsedStr()}", true);
return returnVal;
}
// Benchmark a void method (via Action)
public static void BenchmarkMethod(string methodName, Action method, params object[] args)
{
NmkdStopwatch sw = new NmkdStopwatch();
method.DynamicInvoke(args);
Logger.Log($"Ran {methodName} in {sw.GetElapsedStr()}", true);
}
}
}

View File

@@ -0,0 +1,182 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.MiscUtils
{
class FormatUtils
{
public static string Bytes(long sizeBytes)
{
try
{
string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" };
if (sizeBytes == 0)
return "0" + suf[0];
long bytes = Math.Abs(sizeBytes);
int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
double num = Math.Round(bytes / Math.Pow(1024, place), 1);
return ($"{Math.Sign(sizeBytes) * num} {suf[place]}");
}
catch
{
return "N/A B";
}
}
public static string Time(long milliseconds)
{
double secs = (milliseconds / 1000f);
if (milliseconds <= 1000)
{
return milliseconds + "ms";
}
return secs.ToString("0.00") + "s";
}
public static string Time (TimeSpan span, bool allowMs = true)
{
if(span.TotalHours >= 1f)
return span.ToString(@"hh\:mm\:ss");
if (span.TotalMinutes >= 1f)
return span.ToString(@"mm\:ss");
if (span.TotalSeconds >= 1f || !allowMs)
return span.ToString(@"ss".TrimStart('0').PadLeft(1, '0')) + "s";
return span.ToString(@"fff").TrimStart('0').PadLeft(1, '0') + "ms";
}
public static string TimeSw(Stopwatch sw)
{
long elapsedMs = sw.ElapsedMilliseconds;
return Time(elapsedMs);
}
public static long TimestampToSecs(string timestamp, bool hasMilliseconds = true)
{
try
{
string[] values = timestamp.Split(':');
int hours = int.Parse(values[0]);
int minutes = int.Parse(values[1]);
int seconds = int.Parse(values[2].Split('.')[0]);
long secs = hours * 3600 + minutes * 60 + seconds;
if (hasMilliseconds)
{
int milliseconds = int.Parse(values[2].Split('.')[1].Substring(0, 2)) * 10;
if (milliseconds >= 500)
secs++;
}
return secs;
}
catch (Exception e)
{
Logger.Log($"TimestampToSecs({timestamp}) Exception: {e.Message}", true);
return 0;
}
}
public static long TimestampToMs(string timestamp, bool hasMilliseconds = true)
{
try
{
string[] values = timestamp.Split(':');
int hours = int.Parse(values[0]);
int minutes = int.Parse(values[1]);
int seconds = int.Parse(values[2].Split('.')[0]);
long ms = 0;
if (hasMilliseconds)
{
int milliseconds = int.Parse(values[2].Split('.')[1].Substring(0, 2)) * 10;
ms = hours * 3600000 + minutes * 60000 + seconds * 1000 + milliseconds;
}
else
{
ms = hours * 3600000 + minutes * 60000 + seconds * 1000;
}
return ms;
}
catch (Exception e)
{
Logger.Log($"MsFromTimeStamp({timestamp}) Exception: {e.Message}", true);
return 0;
}
}
public static string SecsToTimestamp(long seconds)
{
return (new DateTime(1970, 1, 1)).AddSeconds(seconds).ToString("HH:mm:ss");
}
public static string MsToTimestamp(long milliseconds)
{
return (new DateTime(1970, 1, 1)).AddMilliseconds(milliseconds).ToString("HH:mm:ss");
}
public static string Ratio(long numFrom, long numTo)
{
float ratio = ((float)numFrom / (float)numTo) * 100f;
return ratio.ToString("0.00") + "%";
}
public static int RatioInt(long numFrom, long numTo)
{
double ratio = Math.Round(((float)numFrom / (float)numTo) * 100f);
return (int)ratio;
}
public static string RatioIntStr(long numFrom, long numTo)
{
double ratio = Math.Round(((float)numFrom / (float)numTo) * 100f);
return ratio + "%";
}
public static string ConcatStrings(string[] strings, char delimiter = ',', bool distinct = false)
{
string outStr = "";
strings = strings.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();
if(distinct)
strings = strings.Distinct().ToArray();
for (int i = 0; i < strings.Length; i++)
{
outStr += strings[i];
if (i + 1 != strings.Length)
outStr += delimiter;
}
return outStr;
}
public static System.Drawing.Size ParseSize (string str)
{
try
{
string[] values = str.Split('x');
return new System.Drawing.Size(values[0].GetInt(), values[1].GetInt());
}
catch
{
return new System.Drawing.Size();
}
}
public static string BeautifyFfmpegStats (string line)
{
return line.Remove("q=-0.0").Remove("q=-1.0").Remove("size=N/A").Remove("bitrate=N/A").Replace("frame=", "Frame: ")
.Replace("fps=", "FPS: ").Replace("q=", "QP: ").Replace("time=", "Time: ").Replace("speed=", "Relative Speed: ")
.Replace("bitrate=", "Bitrate: ").Replace("Lsize=", "Size: ").Replace("size=", "Size: ").TrimWhitespaces();
}
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Flowframes.Data;
using Flowframes.IO;
namespace Flowframes.MiscUtils
{
class FrameRename
{
public static bool framesAreRenamed;
public static string[] importFilenames; // index=renamed, value=original TODO: Store on disk instead for crashes?
public static async Task Rename()
{
importFilenames = IoUtils.GetFilesSorted(Interpolate.current.framesFolder).Select(x => Path.GetFileName(x)).ToArray();
await IoUtils.RenameCounterDir(Interpolate.current.framesFolder, 0, Padding.inputFramesRenamed);
framesAreRenamed = true;
}
public static async Task Unrename()
{
Stopwatch sw = new Stopwatch();
sw.Restart();
string[] files = IoUtils.GetFilesSorted(Interpolate.current.framesFolder);
for (int i = 0; i < files.Length; i++)
{
string movePath = Path.Combine(Interpolate.current.framesFolder, importFilenames[i]);
File.Move(files[i], movePath);
if (sw.ElapsedMilliseconds > 100)
{
await Task.Delay(1);
sw.Restart();
}
}
framesAreRenamed = false;
}
}
}

View File

@@ -0,0 +1,87 @@
using Flowframes.Data;
using Flowframes.Forms;
using Flowframes.IO;
using Flowframes.Main;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Flowframes.MiscUtils
{
class ModelDownloadFormUtils
{
public static ModelDownloadForm form;
static int taskCounter = 0;
static int tasksToDo = 0;
static bool canceled = false;
public static async Task DownloadModels (bool rifeC, bool rifeN, bool dainN, bool flavrC)
{
form.SetDownloadBtnEnabled(true);
canceled = false;
List<AI> ais = new List<AI>();
if (rifeC) ais.Add(Implementations.rifeCuda);
if (rifeN) ais.Add(Implementations.rifeNcnn);
if (dainN) ais.Add(Implementations.dainNcnn);
if (flavrC) ais.Add(Implementations.flavrCuda);
if (ais.Count < 1)
return;
taskCounter = 1;
tasksToDo = GetTaskCount(ais) + 1;
form.SetWorking(true);
await Task.Delay(10);
UpdateProgressBar();
foreach (AI ai in ais)
await DownloadForAi(ai);
form.SetWorking(false);
form.SetStatus($"Done.");
form.SetDownloadBtnEnabled(false);
}
public static async Task DownloadForAi(AI ai)
{
ModelCollection modelCollection = AiModels.GetModels(ai);
for (int i = 0; i < modelCollection.models.Count; i++)
{
if (canceled)
return;
ModelCollection.ModelInfo modelInfo = modelCollection.models[i];
form.SetStatus($"Downloading files for {modelInfo.ai.aiName.Replace("_", "-")}...");
await ModelDownloader.DownloadModelFiles(ai, modelInfo.dir, false);
taskCounter++;
UpdateProgressBar();
}
}
static void UpdateProgressBar ()
{
form.SetProgress((((float)taskCounter / tasksToDo) * 100f).RoundToInt());
}
public static void Cancel ()
{
canceled = true;
ModelDownloader.canceled = true;
}
public static int GetTaskCount (List<AI> ais)
{
int count = 0;
foreach(AI ai in ais)
{
ModelCollection modelCollection = AiModels.GetModels(ai);
count += modelCollection.models.Count;
}
return count;
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Diagnostics;
using System.Linq;
namespace Flowframes.MiscUtils
{
class NmkdStopwatch
{
public Stopwatch sw = new Stopwatch();
public NmkdStopwatch (bool startOnCreation = true)
{
if (startOnCreation)
sw.Restart();
}
public string GetElapsedStr ()
{
return FormatUtils.TimeSw(sw);
}
}
}

564
Code/Os/AiProcess.cs Normal file
View File

@@ -0,0 +1,564 @@
using Flowframes.IO;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Flowframes.Ui;
using Flowframes.Main;
using Flowframes.Data;
using Flowframes.MiscUtils;
namespace Flowframes.Os
{
class AiProcess
{
public static bool hasShownError;
public static string lastLogName;
public static Process lastAiProcess;
public static Stopwatch processTime = new Stopwatch();
public static Stopwatch processTimeMulti = new Stopwatch();
public static int lastStartupTimeMs = 1000;
static string lastInPath;
public static void Kill ()
{
if (lastAiProcess == null) return;
try
{
AiProcessSuspend.SetRunning(false);
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();
lastAiProcess = proc;
AiProcessSuspend.SetRunning(true);
lastInPath = string.IsNullOrWhiteSpace(inPath) ? Interpolate.current.framesFolder : inPath;
hasShownError = false;
}
static void SetProgressCheck(string interpPath, float factor)
{
int frames = IoUtils.GetAmountOfFiles(lastInPath, false);
int target = ((frames * factor) - (factor - 1)).RoundToInt();
InterpolationProgress.progressPaused = false;
InterpolationProgress.currentFactor = factor;
if (InterpolationProgress.progCheckRunning)
InterpolationProgress.targetFrames = target;
else
InterpolationProgress.GetProgressByFrameAmount(interpPath, target);
}
static async Task AiFinished(string aiName)
{
if (Interpolate.canceled) return;
Program.mainForm.SetProgress(100);
AiProcessSuspend.SetRunning(false);
int interpFramesFiles = IoUtils.GetAmountOfFiles(Interpolate.current.interpFolder, false, "*" + Interpolate.current.interpExt);
int interpFramesCount = interpFramesFiles + InterpolationProgress.deletedFramesCount;
InterpolationProgress.UpdateInterpProgress(interpFramesCount, InterpolationProgress.targetFrames);
string logStr = $"Done running {aiName} - Interpolation took {FormatUtils.Time(processTime.Elapsed)}. Peak Output FPS: {InterpolationProgress.peakFpsOut.ToString("0.00")}";
if (Interpolate.currentlyUsingAutoEnc && AutoEncode.HasWorkToDo())
{
logStr += " - Waiting for encoding to finish...";
Program.mainForm.SetStatus("Creating output video from frames...");
}
Logger.Log(logStr);
processTime.Stop();
if(interpFramesCount < 3)
{
string[] logLines = File.ReadAllLines(Path.Combine(Paths.GetLogPath(), lastLogName + ".txt"));
string log = string.Join("\n", logLines.Reverse().Take(10).Reverse().Select(x => x.Split("]: ").Last()).ToList());
string amount = interpFramesCount > 0 ? $"Only {interpFramesCount}" : "No";
Interpolate.Cancel($"Interpolation failed - {amount} interpolated frames were created.\n\n\nLast 10 log lines:\n{log}\n\nCheck the log '{lastLogName}' for more details.");
return;
}
while (Interpolate.currentlyUsingAutoEnc && Program.busy)
{
if (AvProcess.lastAvProcess != null && !AvProcess.lastAvProcess.HasExited && AvProcess.lastTask == AvProcess.TaskType.Encode)
{
string lastLine = AvProcess.lastOutputFfmpeg.SplitIntoLines().Last();
Logger.Log(FormatUtils.BeautifyFfmpegStats(lastLine), false, Logger.GetLastLine().ToLower().Contains("frame"));
}
if (AvProcess.lastAvProcess.HasExited && !AutoEncode.HasWorkToDo()) // Stop logging if ffmpeg is not running & AE is done
break;
await Task.Delay(500);
}
}
public static async Task RunRifeCuda(string framesPath, float interpFactor, string mdl)
{
if(Interpolate.currentlyUsingAutoEnc) // Ensure AutoEnc is not paused
AutoEncode.paused = false;
try
{
string rifeDir = Path.Combine(Paths.GetPkgPath(), Implementations.rifeCuda.pkgDir);
string script = "rife.py";
if (!File.Exists(Path.Combine(rifeDir, script)))
{
Interpolate.Cancel("RIFE script not found! Make sure you didn't modify any files.");
return;
}
string archFilesDir = Path.Combine(rifeDir, "arch");
string archFilesDirModel = Path.Combine(rifeDir, mdl, "arch");
if (Directory.Exists(archFilesDirModel))
{
Logger.Log($"Model {mdl} has architecture python files - copying to arch.", true);
IoUtils.DeleteContentsOfDir(archFilesDir);
IoUtils.CopyDir(archFilesDirModel, archFilesDir);
}
await RunRifeCudaProcess(framesPath, Paths.interpDir, script, interpFactor, mdl);
}
catch (Exception e)
{
Logger.Log("Error running RIFE-CUDA: " + e.Message);
}
await AiFinished("RIFE");
}
public static async Task RunRifeCudaProcess (string inPath, string outDir, string script, float interpFactor, string mdl)
{
string outPath = Path.Combine(inPath.GetParentDir(), outDir);
Directory.CreateDirectory(outPath);
string uhdStr = await InterpolateUtils.UseUhd() ? "--UHD" : "";
string wthreads = $"--wthreads {2 * (int)interpFactor}";
string rbuffer = $"--rbuffer {Config.GetInt(Config.Key.rifeCudaBufferSize, 200)}";
//string scale = $"--scale {Config.GetFloat("rifeCudaScale", 1.0f).ToStringDot()}";
string prec = Config.GetBool(Config.Key.rifeCudaFp16) ? "--fp16" : "";
string args = $" --input {inPath.Wrap()} --output {outDir} --model {mdl} --exp {(int)Math.Log(interpFactor, 2)} {uhdStr} {wthreads} {rbuffer} {prec}";
Process rifePy = OsUtils.NewProcess(!OsUtils.ShowHiddenCmd());
AiStarted(rifePy, 3500);
SetProgressCheck(Path.Combine(Interpolate.current.tempFolder, outDir), interpFactor);
rifePy.StartInfo.Arguments = $"{OsUtils.GetCmdArg()} cd /D {Path.Combine(Paths.GetPkgPath(), Implementations.rifeCuda.pkgDir).Wrap()} & " +
$"set CUDA_VISIBLE_DEVICES={Config.Get(Config.Key.torchGpus)} & {Python.GetPyCmd()} {script} {args}";
Logger.Log($"Running RIFE (CUDA){(await InterpolateUtils.UseUhd() ? " (UHD Mode)" : "")}...", false);
Logger.Log("cmd.exe " + rifePy.StartInfo.Arguments, true);
if (!OsUtils.ShowHiddenCmd())
{
rifePy.OutputDataReceived += (sender, outLine) => { LogOutput(outLine.Data, "rife-cuda-log"); };
rifePy.ErrorDataReceived += (sender, outLine) => { LogOutput("[E] " + outLine.Data, "rife-cuda-log", true); };
}
rifePy.Start();
if (!OsUtils.ShowHiddenCmd())
{
rifePy.BeginOutputReadLine();
rifePy.BeginErrorReadLine();
}
while (!rifePy.HasExited) await Task.Delay(1);
}
public static async Task RunFlavrCuda(string framesPath, float interpFactor, string mdl)
{
if (Interpolate.currentlyUsingAutoEnc) // Ensure AutoEnc is not paused
AutoEncode.paused = false;
try
{
string flavDir = Path.Combine(Paths.GetPkgPath(), Implementations.flavrCuda.pkgDir);
string script = "flavr.py";
if (!File.Exists(Path.Combine(flavDir, script)))
{
Interpolate.Cancel("FLAVR script not found! Make sure you didn't modify any files.");
return;
}
await RunFlavrCudaProcess(framesPath, Paths.interpDir, script, interpFactor, mdl);
}
catch (Exception e)
{
Logger.Log("Error running FLAVR-CUDA: " + e.Message);
}
await AiFinished("FLAVR");
}
public static async Task RunFlavrCudaProcess(string inPath, string outDir, string script, float interpFactor, string mdl)
{
string outPath = Path.Combine(inPath.GetParentDir(), outDir);
Directory.CreateDirectory(outPath);
string args = $" --input {inPath.Wrap()} --output {outPath.Wrap()} --model {mdl}/{mdl}.pth --factor {interpFactor}";
Process flavrPy = OsUtils.NewProcess(!OsUtils.ShowHiddenCmd());
AiStarted(flavrPy, 4000);
SetProgressCheck(Path.Combine(Interpolate.current.tempFolder, outDir), interpFactor);
flavrPy.StartInfo.Arguments = $"{OsUtils.GetCmdArg()} cd /D {Path.Combine(Paths.GetPkgPath(), Implementations.flavrCuda.pkgDir).Wrap()} & " +
$"set CUDA_VISIBLE_DEVICES={Config.Get(Config.Key.torchGpus)} & {Python.GetPyCmd()} {script} {args}";
Logger.Log($"Running FLAVR (CUDA)...", false);
Logger.Log("cmd.exe " + flavrPy.StartInfo.Arguments, true);
if (!OsUtils.ShowHiddenCmd())
{
flavrPy.OutputDataReceived += (sender, outLine) => { LogOutput(outLine.Data, "flavr-cuda-log"); };
flavrPy.ErrorDataReceived += (sender, outLine) => { LogOutput("[E] " + outLine.Data, "flavr-cuda-log", true); };
}
flavrPy.Start();
if (!OsUtils.ShowHiddenCmd())
{
flavrPy.BeginOutputReadLine();
flavrPy.BeginErrorReadLine();
}
while (!flavrPy.HasExited) await Task.Delay(1);
}
public static async Task RunRifeNcnn (string framesPath, string outPath, int factor, string mdl)
{
processTimeMulti.Restart();
try
{
Logger.Log($"Running RIFE (NCNN){(await InterpolateUtils.UseUhd() ? " (UHD Mode)" : "")}...", false);
await RunRifeNcnnMulti(framesPath, outPath, factor, mdl);
}
catch (Exception e)
{
Logger.Log("Error running RIFE-NCNN: " + e.Message);
}
await AiFinished("RIFE");
}
static async Task RunRifeNcnnMulti(string framesPath, string outPath, int factor, string mdl)
{
int times = (int)Math.Log(factor, 2);
if (times > 1)
AutoEncode.paused = true; // Disable autoenc until the last iteration
else
AutoEncode.paused = false;
for (int iteration = 1; iteration <= times; iteration++)
{
if (Interpolate.canceled) return;
if (Interpolate.currentlyUsingAutoEnc && iteration == times) // Enable autoenc if this is the last iteration
AutoEncode.paused = false;
if (iteration > 1)
{
Logger.Log($"Re-Running RIFE for {Math.Pow(2, iteration)}x interpolation...", false);
string lastInterpPath = outPath + $"-run{iteration - 1}";
Directory.Move(outPath, lastInterpPath); // Rename last interp folder
await RunRifeNcnnProcess(lastInterpPath, outPath, mdl);
await IoUtils.TryDeleteIfExistsAsync(lastInterpPath);
}
else
{
await RunRifeNcnnProcess(framesPath, outPath, mdl);
}
}
}
static async Task RunRifeNcnnProcess(string inPath, string outPath, string mdl)
{
Directory.CreateDirectory(outPath);
Process rifeNcnn = OsUtils.NewProcess(!OsUtils.ShowHiddenCmd());
AiStarted(rifeNcnn, 1500, inPath);
SetProgressCheck(outPath, 2);
string uhdStr = await InterpolateUtils.UseUhd() ? "-u" : "";
string ttaStr = Config.GetBool(Config.Key.rifeNcnnUseTta, false) ? "-x" : "";
rifeNcnn.StartInfo.Arguments = $"{OsUtils.GetCmdArg()} cd /D {Path.Combine(Paths.GetPkgPath(), Implementations.rifeNcnn.pkgDir).Wrap()} & rife-ncnn-vulkan.exe " +
$" -v -i {inPath.Wrap()} -o {outPath.Wrap()} -m {mdl.ToLower()} {ttaStr} {uhdStr} -g {Config.Get(Config.Key.ncnnGpus)} -f {GetNcnnPattern()} -j {GetNcnnThreads()}";
Logger.Log("cmd.exe " + rifeNcnn.StartInfo.Arguments, true);
if (!OsUtils.ShowHiddenCmd())
{
rifeNcnn.OutputDataReceived += (sender, outLine) => { LogOutput("[O] " + outLine.Data, "rife-ncnn-log"); };
rifeNcnn.ErrorDataReceived += (sender, outLine) => { LogOutput("[E] " + outLine.Data, "rife-ncnn-log", true); };
}
rifeNcnn.Start();
if (!OsUtils.ShowHiddenCmd())
{
rifeNcnn.BeginOutputReadLine();
rifeNcnn.BeginErrorReadLine();
}
while (!rifeNcnn.HasExited) await Task.Delay(1);
}
public static async Task RunDainNcnn(string framesPath, string outPath, float factor, string mdl, int tilesize)
{
if (Interpolate.currentlyUsingAutoEnc) // Ensure AutoEnc is not paused
AutoEncode.paused = false;
try
{
await RunDainNcnnProcess(framesPath, outPath, factor, mdl, tilesize);
}
catch (Exception e)
{
Logger.Log("Error running DAIN-NCNN: " + e.Message);
}
await AiFinished("DAIN");
}
public static async Task RunDainNcnnProcess (string framesPath, string outPath, float factor, string mdl, int tilesize)
{
string dainDir = Path.Combine(Paths.GetPkgPath(), Implementations.dainNcnn.pkgDir);
Directory.CreateDirectory(outPath);
Process dain = OsUtils.NewProcess(!OsUtils.ShowHiddenCmd());
AiStarted(dain, 1500);
SetProgressCheck(outPath, factor);
int targetFrames = ((IoUtils.GetAmountOfFiles(lastInPath, false, "*.*") * factor).RoundToInt()) - (factor.RoundToInt() - 1); // TODO: Won't work with fractional factors
string args = $" -v -i {framesPath.Wrap()} -o {outPath.Wrap()} -n {targetFrames} -m {mdl.ToLower()}" +
$" -t {GetNcnnTilesize(tilesize)} -g {Config.Get(Config.Key.ncnnGpus)} -f {GetNcnnPattern()} -j 2:1:2";
dain.StartInfo.Arguments = $"{OsUtils.GetCmdArg()} cd /D {dainDir.Wrap()} & dain-ncnn-vulkan.exe {args}";
Logger.Log("Running DAIN...", false);
Logger.Log("cmd.exe " + dain.StartInfo.Arguments, true);
if (!OsUtils.ShowHiddenCmd())
{
dain.OutputDataReceived += (sender, outLine) => { LogOutput("[O] " + outLine.Data, "dain-ncnn-log"); };
dain.ErrorDataReceived += (sender, outLine) => { LogOutput("[E] " + outLine.Data, "dain-ncnn-log", true); };
}
dain.Start();
if (!OsUtils.ShowHiddenCmd())
{
dain.BeginOutputReadLine();
dain.BeginErrorReadLine();
}
while (!dain.HasExited)
await Task.Delay(100);
}
public static async Task RunXvfiCuda(string framesPath, float interpFactor, string mdl)
{
if (Interpolate.currentlyUsingAutoEnc) // Ensure AutoEnc is not paused
AutoEncode.paused = false;
try
{
string xvfiDir = Path.Combine(Paths.GetPkgPath(), Implementations.xvfiCuda.pkgDir);
string script = "main.py";
if (!File.Exists(Path.Combine(xvfiDir, script)))
{
Interpolate.Cancel("XVFI script not found! Make sure you didn't modify any files.");
return;
}
await RunXvfiCudaProcess(framesPath, Paths.interpDir, script, interpFactor, mdl);
}
catch (Exception e)
{
Logger.Log("Error running XVFI-CUDA: " + e.Message);
}
await AiFinished("XVFI");
}
public static async Task RunXvfiCudaProcess(string inPath, string outDir, string script, float interpFactor, string mdlDir)
{
string pkgPath = Path.Combine(Paths.GetPkgPath(), Implementations.xvfiCuda.pkgDir);
string basePath = inPath.GetParentDir();
string outPath = Path.Combine(basePath, outDir);
Directory.CreateDirectory(outPath);
string mdlArgs = File.ReadAllText(Path.Combine(pkgPath, mdlDir, "args.ini"));
string args = $" --custom_path {basePath.Wrap()} --input {inPath.Wrap()} --output {outPath.Wrap()} --mdl_dir {mdlDir}" +
$" --multiple {interpFactor} --gpu 0 {mdlArgs}";
Process xvfiPy = OsUtils.NewProcess(!OsUtils.ShowHiddenCmd());
AiStarted(xvfiPy, 3500);
SetProgressCheck(Path.Combine(Interpolate.current.tempFolder, outDir), interpFactor);
xvfiPy.StartInfo.Arguments = $"{OsUtils.GetCmdArg()} cd /D {pkgPath.Wrap()} & " +
$"set CUDA_VISIBLE_DEVICES={Config.Get(Config.Key.torchGpus)} & {Python.GetPyCmd()} {script} {args}";
Logger.Log($"Running XVFI (CUDA)...", false);
Logger.Log("cmd.exe " + xvfiPy.StartInfo.Arguments, true);
if (!OsUtils.ShowHiddenCmd())
{
xvfiPy.OutputDataReceived += (sender, outLine) => { LogOutput(outLine.Data, "xvfi-cuda-log"); };
xvfiPy.ErrorDataReceived += (sender, outLine) => { LogOutput("[E] " + outLine.Data, "xvfi-cuda-log", true); };
}
xvfiPy.Start();
if (!OsUtils.ShowHiddenCmd())
{
xvfiPy.BeginOutputReadLine();
xvfiPy.BeginErrorReadLine();
}
while (!xvfiPy.HasExited) await Task.Delay(1);
}
static void LogOutput (string line, string logFilename, bool err = false)
{
if (string.IsNullOrWhiteSpace(line) || line.Length < 6)
return;
Stopwatch sw = new Stopwatch();
sw.Restart();
//if (line.Contains("iVBOR"))
//{
// try
// {
// string[] split = line.Split(':');
// //MemoryStream stream = new MemoryStream(Convert.FromBase64String(split[1]));
// //Image img = Image.FromStream(stream);
// Logger.Log($"Received image {split[0]} in {sw.ElapsedMilliseconds} ms", true);
// }
// catch (Exception e)
// {
// Logger.Log($"Failed to decode b64 string - {e}:");
// Logger.Log(line);
// }
// return;
//}
lastLogName = logFilename;
Logger.Log(line, true, false, logFilename);
if (line.Contains("ff:nocuda-cpu"))
Logger.Log("WARNING: CUDA-capable GPU device is not available, running on CPU instead!");
if (!hasShownError && err && line.ToLower().Contains("out of memory"))
{
hasShownError = true;
InterpolateUtils.ShowMessage($"Your GPU ran out of VRAM! Please try a video with a lower resolution or use the Max Video Size option in the settings.\n\n{line}", "Error");
}
if (!hasShownError && err && line.ToLower().Contains("modulenotfounderror"))
{
hasShownError = true;
InterpolateUtils.ShowMessage($"A python module is missing.\nCheck {logFilename} for details.\n\n{line}", "Error");
if(!Python.HasEmbeddedPyFolder())
Process.Start("https://github.com/n00mkrad/flowframes/blob/main/PythonDependencies.md");
}
if (!hasShownError && line.ToLower().Contains("no longer supports this gpu"))
{
hasShownError = true;
InterpolateUtils.ShowMessage($"Your GPU seems to be outdated and is not supported!\n\n{line}", "Error");
}
if (!hasShownError && line.ToLower().Contains("illegal memory access"))
{
hasShownError = true;
InterpolateUtils.ShowMessage($"Your GPU appears to be unstable! If you have an overclock enabled, please disable it!\n\n{line}", "Error");
}
if (!hasShownError && line.ToLower().Contains("error(s) in loading state_dict"))
{
hasShownError = true;
string msg = (Interpolate.current.ai.aiName == Implementations.flavrCuda.aiName) ? "\n\nFor FLAVR, you need to select the correct model for each scale!" : "";
InterpolateUtils.ShowMessage($"Error loading the AI model!\n\n{line}{msg}", "Error");
}
if (!hasShownError && line.ToLower().Contains("unicodeencodeerror"))
{
hasShownError = true;
InterpolateUtils.ShowMessage($"It looks like your path contains invalid characters - remove them and try again!\n\n{line}", "Error");
}
if (!hasShownError && err && (line.Contains("RuntimeError") || line.Contains("ImportError") || line.Contains("OSError")))
{
hasShownError = true;
InterpolateUtils.ShowMessage($"A python error occured during interpolation!\nCheck {logFilename} for details.\n\n{line}", "Error");
}
if (!hasShownError && err && line.MatchesWildcard("vk*Instance* failed"))
{
hasShownError = true;
InterpolateUtils.ShowMessage($"Vulkan failed to start up!\n\n{line}\n\nThis most likely means your GPU is not compatible.", "Error");
}
if (!hasShownError && err && line.Contains("vkAllocateMemory failed"))
{
hasShownError = true;
bool usingDain = (Interpolate.current.ai.aiName == Implementations.dainNcnn.aiName);
string msg = usingDain ? "\n\nTry reducing the tile size in the AI settings." : "\n\nTry a lower resolution (Settings -> Max Video Size).";
InterpolateUtils.ShowMessage($"Vulkan ran out of memory!\n\n{line}{msg}", "Error");
}
if (!hasShownError && err && line.Contains("invalid gpu device"))
{
hasShownError = true;
InterpolateUtils.ShowMessage($"A Vulkan error occured during interpolation!\n\n{line}\n\nAre your GPU IDs set correctly?", "Error");
}
if (!hasShownError && err && line.MatchesWildcard("vk* failed"))
{
hasShownError = true;
InterpolateUtils.ShowMessage($"A Vulkan error occured during interpolation!\n\n{line}", "Error");
}
if (hasShownError)
Interpolate.Cancel();
InterpolationProgress.UpdateLastFrameFromInterpOutput(line);
}
static string GetNcnnPattern ()
{
return $"%0{Padding.interpFrames}d{Interpolate.current.interpExt}";
}
static string GetNcnnTilesize(int tilesize)
{
int gpusAmount = Config.Get(Config.Key.ncnnGpus).Split(',').Length;
string tilesizeStr = $"{tilesize}";
for (int i = 1; i < gpusAmount; i++)
tilesizeStr += $",{tilesize}";
return tilesizeStr;
}
static string GetNcnnThreads ()
{
int gpusAmount = Config.Get(Config.Key.ncnnGpus).Split(',').Length;
int procThreads = Config.GetInt(Config.Key.ncnnThreads);
string progThreadsStr = $"{procThreads}";
for (int i = 1; i < gpusAmount; i++)
progThreadsStr += $",{procThreads}";
return $"4:{progThreadsStr}:4"; ;
}
}
}

114
Code/Os/AiProcessSuspend.cs Normal file
View File

@@ -0,0 +1,114 @@
using Flowframes.Extensions;
using Flowframes.Properties;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace Flowframes.Os
{
class AiProcessSuspend
{
public static bool aiProcFrozen;
static List<Process> suspendedProcesses = new List<Process>();
public static bool isRunning;
public static void Reset()
{
SetRunning(false);
SetPauseButtonStyle(false);
}
public static void SetRunning (bool running)
{
isRunning = running;
Program.mainForm.GetPauseBtn().Visible = running;
}
public static void SuspendIfRunning ()
{
if(!aiProcFrozen)
SuspendResumeAi(true);
}
public static void ResumeIfPaused()
{
if (aiProcFrozen)
SuspendResumeAi(false);
}
public static void SuspendResumeAi(bool freeze, bool excludeCmd = true)
{
if (AiProcess.lastAiProcess == null || AiProcess.lastAiProcess.HasExited)
return;
Process currProcess = AiProcess.lastAiProcess;
Logger.Log($"{(freeze ? "Suspending" : "Resuming")} main process ({currProcess.StartInfo.FileName} {currProcess.StartInfo.Arguments})", true);
if (freeze)
{
List<Process> procs = new List<Process>();
procs.Add(currProcess);
foreach (var subProc in OsUtils.GetChildProcesses(currProcess))
procs.Add(subProc);
aiProcFrozen = true;
SetPauseButtonStyle(true);
AiProcess.processTime.Stop();
foreach (Process process in procs)
{
if (process == null || process.HasExited)
continue;
if (excludeCmd && (process.ProcessName == "conhost" || process.ProcessName == "cmd"))
continue;
Logger.Log($"Suspending {process.ProcessName}", true);
process.Suspend();
suspendedProcesses.Add(process);
}
}
else
{
aiProcFrozen = false;
SetPauseButtonStyle(false);
AiProcess.processTime.Start();
foreach (Process process in new List<Process>(suspendedProcesses)) // We MUST clone the list here since we modify it in the loop!
{
if (process == null || process.HasExited)
continue;
Logger.Log($"Resuming {process.ProcessName}", true);
process.Resume();
suspendedProcesses.Remove(process);
}
}
}
public static void SetPauseButtonStyle (bool paused)
{
System.Windows.Forms.Button btn = Program.mainForm.GetPauseBtn();
if (paused)
{
btn.BackgroundImage = Resources.baseline_play_arrow_white_48dp;
btn.FlatAppearance.BorderColor = System.Drawing.Color.MediumSeaGreen;
btn.FlatAppearance.MouseOverBackColor = System.Drawing.Color.MediumSeaGreen;
}
else
{
btn.BackgroundImage = Resources.baseline_pause_white_48dp;
btn.FlatAppearance.BorderColor= System.Drawing.Color.DarkOrange;
btn.FlatAppearance.MouseOverBackColor = System.Drawing.Color.DarkOrange;
}
}
}
}

131
Code/Os/NvApi.cs Normal file
View File

@@ -0,0 +1,131 @@
using NvAPIWrapper;
using NvAPIWrapper.GPU;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace Flowframes.Os
{
class NvApi
{
public enum Architecture { Undetected, Fermi, Kepler, Maxwell, Pascal, Turing, Ampere };
public static List<PhysicalGPU> gpuList = new List<PhysicalGPU>();
public static void Init()
{
try
{
NVIDIA.Initialize();
PhysicalGPU[] gpus = PhysicalGPU.GetPhysicalGPUs();
if (gpus.Length == 0)
return;
gpuList = gpus.ToList();
List<string> gpuNames = new List<string>();
foreach (PhysicalGPU gpu in gpus)
gpuNames.Add(gpu.FullName);
string gpuNamesList = string.Join(", ", gpuNames);
Logger.Log($"Initialized Nvidia API. GPU{(gpus.Length > 1 ? "s" : "")}: {gpuNamesList}");
}
catch (Exception e)
{
Logger.Log("No Nvidia GPU(s) detected. You will not be able to use CUDA implementations on GPU.");
Logger.Log($"Failed to initialize NvApi: {e.Message}\nIgnore this if you don't have an Nvidia GPU.", true);
}
}
public static float GetVramGb (int gpu = 0)
{
try
{
return (gpuList[gpu].MemoryInformation.AvailableDedicatedVideoMemoryInkB / 1000f / 1024f);
}
catch
{
return 0f;
}
}
public static float GetFreeVramGb(int gpu = 0)
{
try
{
return (gpuList[gpu].MemoryInformation.CurrentAvailableDedicatedVideoMemoryInkB / 1000f / 1024f);
}
catch
{
return 0f;
}
}
public static string GetGpuName()
{
try
{
NVIDIA.Initialize();
PhysicalGPU[] gpus = PhysicalGPU.GetPhysicalGPUs();
if (gpus.Length == 0)
return "";
return gpus[0].FullName;
}
catch
{
return "";
}
}
public static bool HasAmpereOrNewer()
{
foreach (PhysicalGPU gpu in gpuList)
{
Architecture arch = GetArch(gpu);
if (arch == Architecture.Ampere || arch == Architecture.Undetected)
return true;
}
return false;
}
public static Architecture GetArch(PhysicalGPU gpu)
{
string gpuCode = gpu.ArchitectInformation.ShortName;
if (gpuCode.Trim().StartsWith("GF")) return Architecture.Fermi;
if (gpuCode.Trim().StartsWith("GK")) return Architecture.Kepler;
if (gpuCode.Trim().StartsWith("GM")) return Architecture.Maxwell;
if (gpuCode.Trim().StartsWith("GP")) return Architecture.Pascal;
if (gpuCode.Trim().StartsWith("TU")) return Architecture.Turing;
if (gpuCode.Trim().StartsWith("GA")) return Architecture.Ampere;
return Architecture.Undetected;
}
public static bool HasTensorCores (int gpu = 0)
{
try
{
if (gpuList == null)
Init();
if (gpuList == null)
return false;
Architecture arch = GetArch(gpuList[gpu]);
return arch == Architecture.Turing || arch == Architecture.Ampere;
}
catch (Exception e)
{
Logger.Log($"HasTensorCores({gpu}) Error: {e.Message}", true);
return false;
}
}
}
}

264
Code/Os/OsUtils.cs Normal file
View File

@@ -0,0 +1,264 @@
using System.Collections.Generic;
using System.Text;
using System.Security.Principal;
using System;
using System.Diagnostics;
using System.Management;
using System.Threading.Tasks;
using System.Windows.Forms;
using Flowframes.IO;
using DiskDetector;
using DiskDetector.Models;
using Microsoft.VisualBasic.Devices;
using Flowframes.MiscUtils;
using System.Linq;
using Tulpep.NotificationWindow;
namespace Flowframes.Os
{
class OsUtils
{
public static bool IsUserAdministrator()
{
//bool value to hold our return value
bool isAdmin;
WindowsIdentity user = null;
try
{
//get the currently logged in user
user = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(user);
isAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator);
}
catch (Exception e)
{
Logger.Log("IsUserAdministrator() Error: " + e.Message);
isAdmin = false;
}
finally
{
if (user != null)
user.Dispose();
}
return isAdmin;
}
public static Process SetStartInfo(Process proc, bool hidden, string filename = "cmd.exe")
{
proc.StartInfo.UseShellExecute = !hidden;
proc.StartInfo.RedirectStandardOutput = hidden;
proc.StartInfo.RedirectStandardError = hidden;
proc.StartInfo.CreateNoWindow = hidden;
proc.StartInfo.FileName = filename;
return proc;
}
public static Process NewProcess(bool hidden, string filename = "cmd.exe")
{
Process proc = new Process();
return SetStartInfo(proc, hidden, filename);
}
public static void KillProcessTree(int pid)
{
ManagementObjectSearcher processSearcher = new ManagementObjectSearcher
("Select * From Win32_Process Where ParentProcessID=" + pid);
ManagementObjectCollection processCollection = processSearcher.Get();
try
{
Process proc = Process.GetProcessById(pid);
if (!proc.HasExited) proc.Kill();
}
catch (ArgumentException)
{
// Process already exited.
}
if (processCollection != null)
{
foreach (ManagementObject mo in processCollection)
{
KillProcessTree(Convert.ToInt32(mo["ProcessID"])); //kill child processes(also kills childrens of childrens etc.)
}
}
}
public static string GetCmdArg()
{
bool stayOpen = Config.GetInt(Config.Key.cmdDebugMode) == 2;
if (stayOpen)
return "/K";
else
return "/C";
}
public static bool ShowHiddenCmd()
{
return Config.GetInt(Config.Key.cmdDebugMode) > 0;
}
public static bool DriveIsSSD(string path)
{
try
{
var detectedDrives = Detector.DetectFixedDrives(QueryType.SeekPenalty);
if (detectedDrives.Count != 0)
{
char pathDriveLetter = (path[0].ToString().ToUpper())[0];
foreach (var detectedDrive in detectedDrives)
{
if (detectedDrive.DriveLetter == pathDriveLetter && detectedDrive.HardwareType.ToString().ToLower().Trim() == "ssd")
return true;
}
}
}
catch (Exception e)
{
Logger.Log("Failed to detect drive type: " + e.Message, true);
return true; // Default to SSD on fail
}
return false;
}
public static bool HasNonAsciiChars(string str)
{
return (Encoding.UTF8.GetByteCount(str) != str.Length);
}
public static int GetFreeRamMb()
{
try
{
return (int)(new ComputerInfo().AvailablePhysicalMemory / 1048576);
}
catch
{
return 1000;
}
}
public static string TryGetOs()
{
string info = "";
try
{
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem"))
{
ManagementObjectCollection information = searcher.Get();
if (information != null)
{
foreach (ManagementObject obj in information)
info = $"{obj["Caption"]} | {obj["OSArchitecture"]}";
}
info = info.Replace("NT 5.1.2600", "XP").Replace("NT 5.2.3790", "Server 2003");
}
}
catch (Exception e)
{
Logger.Log("TryGetOs Error: " + e.Message, true);
}
return info;
}
public static IEnumerable<Process> GetChildProcesses(Process process)
{
List<Process> children = new List<Process>();
ManagementObjectSearcher mos = new ManagementObjectSearcher(String.Format("Select * From Win32_Process Where ParentProcessID={0}", process.Id));
foreach (ManagementObject mo in mos.Get())
{
children.Add(Process.GetProcessById(Convert.ToInt32(mo["ProcessID"])));
}
return children;
}
public static async Task<string> GetOutputAsync(Process process, bool onlyLastLine = false)
{
Logger.Log($"Getting output for {process.StartInfo.FileName} {process.StartInfo.Arguments}", true);
NmkdStopwatch sw = new NmkdStopwatch();
Stopwatch timeSinceLastOutput = new Stopwatch();
timeSinceLastOutput.Restart();
string output = "";
process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => output += $"{e.Data}\n";
process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => output += $"{e.Data}\n";
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
while (!process.HasExited) await Task.Delay(50);
while (timeSinceLastOutput.ElapsedMilliseconds < 100) await Task.Delay(50);
output = output.Trim('\r', '\n');
Logger.Log($"Output (after {sw.GetElapsedStr()}): {output.Replace("\r", " / ").Replace("\n", " / ").Trunc(250)}", true);
if (onlyLastLine)
output = output.SplitIntoLines().LastOrDefault();
return output;
}
public static void Shutdown ()
{
Process proc = NewProcess(true);
proc.StartInfo.Arguments = "/C shutdown -s -t 0";
proc.Start();
}
public static void Hibernate()
{
Application.SetSuspendState(PowerState.Hibernate, true, true);
}
public static void Sleep()
{
Application.SetSuspendState(PowerState.Suspend, true, true);
}
public static void ShowNotification(string title, string text)
{
var popupNotifier = new PopupNotifier { TitleText = title, ContentText = text, IsRightToLeft = false };
popupNotifier.BodyColor = System.Drawing.ColorTranslator.FromHtml("#323232");
popupNotifier.ContentColor = System.Drawing.Color.White;
popupNotifier.TitleColor = System.Drawing.Color.LightGray;
popupNotifier.GradientPower = 0;
popupNotifier.Popup();
}
public static void ShowNotificationIfInBackground (string title, string text)
{
if (Program.mainForm.IsInFocus())
return;
ShowNotification(title, text);
}
public static string GetGpus ()
{
ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_DisplayConfiguration");
List<string> gpus = new List<string>();
foreach (ManagementObject mo in searcher.Get())
{
foreach (PropertyData property in mo.Properties)
{
if (property.Name == "Description")
{
gpus.Add(property.Value.ToString());
Logger.Log($"[GetGpus] Found GPU: {property.Value}", true);
}
}
}
return string.Join(", ", gpus);
}
}
}

183
Code/Os/Python.cs Normal file
View File

@@ -0,0 +1,183 @@
using Flowframes.IO;
using Flowframes.MiscUtils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Flowframes.Os
{
class Python
{
static bool hasCheckedSysPy = false;
static bool sysPyInstalled = false;
public static string compactOutput;
public static async Task CheckCompression ()
{
if(HasEmbeddedPyFolder() && (Config.Get(Config.Key.compressedPyVersion) != Updater.GetInstalledVer().ToString()))
{
Program.mainForm.SetWorking(true, false);
Stopwatch sw = new Stopwatch();
sw.Restart();
try
{
bool shownPatienceMsg = false;
Logger.Log("Compressing python runtime. This only needs to be done once.");
compactOutput = "";
Process compact = OsUtils.NewProcess(true);
compact.StartInfo.Arguments = $"/C compact /C /S:{GetPyFolder().Wrap()} /EXE:LZX";
compact.OutputDataReceived += new DataReceivedEventHandler(CompactOutputHandler);
compact.ErrorDataReceived += new DataReceivedEventHandler(CompactOutputHandler);
compact.Start();
compact.BeginOutputReadLine();
compact.BeginErrorReadLine();
while (!compact.HasExited)
{
await Task.Delay(500);
if(sw.ElapsedMilliseconds > 10000)
{
Logger.Log($"This can take up to a few minutes, but only needs to be done once. (Elapsed: {FormatUtils.Time(sw.Elapsed)})", false, shownPatienceMsg);
shownPatienceMsg = true;
await Task.Delay(500);
}
}
Config.Set("compressedPyVersion", Updater.GetInstalledVer().ToString());
Logger.Log("Done compressing python runtime.");
Logger.WriteToFile(compactOutput, true, "compact");
}
catch { }
Program.mainForm.SetWorking(false);
}
}
static void CompactOutputHandler (object sendingProcess, DataReceivedEventArgs outLine)
{
if (outLine == null || outLine.Data == null)
return;
string line = outLine.Data;
compactOutput = compactOutput + line + "\n";
}
public static string GetPyCmd (bool unbufferedStdOut = true)
{
if (HasEmbeddedPyFolder())
{
Logger.Log("Using embedded Python runtime.");
return Path.Combine(GetPyFolder(), "python.exe").Wrap() + (unbufferedStdOut ? " -u " : "");
}
else
{
if (IsSysPyInstalled())
{
return "python" + (unbufferedStdOut ? " -u " : "");
}
else
{
MessageBox.Show("Neither the Flowframes Python Runtime nor System Python installation could be found!\nEither redownload Flowframes with the embedded Python runtime enabled or install Python/Pytorch yourself.");
Interpolate.Cancel("Neither the Flowframes Python Runtime nor System Python installation could be found!");
}
}
return "";
}
public static bool HasEmbeddedPyFolder ()
{
return (Directory.Exists(GetPyFolder()) && IoUtils.GetDirSize(GetPyFolder(), false) > 1024 * 1024 * 5);
}
public static string GetPyFolder ()
{
if (Directory.Exists(Path.Combine(Paths.GetPkgPath(), "py-amp")))
return Path.Combine(Paths.GetPkgPath(), "py-amp");
if (Directory.Exists(Path.Combine(Paths.GetPkgPath(), "py-tu")))
return Path.Combine(Paths.GetPkgPath(), "py-tu");
return "";
}
public static bool IsPytorchReady ()
{
string torchVer = GetPytorchVer();
if (!string.IsNullOrWhiteSpace(torchVer) && torchVer.Length <= 35)
return true;
else
return false;
}
static string GetPytorchVer()
{
try
{
Process py = OsUtils.NewProcess(true);
py.StartInfo.Arguments = "\"/C\" " + GetPyCmd() + " -c \"import torch; print(torch.__version__)\"";
Logger.Log("[DepCheck] CMD: " + py.StartInfo.Arguments);
py.Start();
py.WaitForExit();
string output = py.StandardOutput.ReadToEnd();
string err = py.StandardError.ReadToEnd();
if (!string.IsNullOrWhiteSpace(err)) output += "\n" + err;
Logger.Log("[DepCheck] Pytorch Check Output: " + output.Trim());
return output;
}
catch
{
return "";
}
}
public static bool IsSysPyInstalled ()
{
if (hasCheckedSysPy)
return sysPyInstalled;
bool isInstalled = false;
Logger.Log("Checking if system Python is available...", true);
string sysPyVer = GetSysPyVersion();
if (!string.IsNullOrWhiteSpace(sysPyVer) && !sysPyVer.ToLower().Contains("not found") && sysPyVer.Length <= 35)
{
isInstalled = true;
Logger.Log("Using Python installation: " + sysPyVer, true);
}
hasCheckedSysPy = true;
sysPyInstalled = isInstalled;
return sysPyInstalled;
}
static string GetSysPyVersion()
{
string pythonOut = GetSysPythonOutput();
Logger.Log("[DepCheck] System Python Check Output: " + pythonOut.Trim(), true);
try
{
string ver = pythonOut.Split('(')[0].Trim();
Logger.Log("[DepCheck] Sys Python Ver: " + ver, true);
return ver;
}
catch
{
return "";
}
}
static string GetSysPythonOutput()
{
Process py = OsUtils.NewProcess(true);
py.StartInfo.Arguments = "/C python -V";
Logger.Log("[DepCheck] CMD: " + py.StartInfo.Arguments, true);
py.Start();
py.WaitForExit();
string output = py.StandardOutput.ReadToEnd();
string err = py.StandardError.ReadToEnd();
return output + "\n" + err;
}
}
}

127
Code/Os/StartupChecks.cs Normal file
View File

@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;
using Flowframes.IO;
namespace Flowframes.Os
{
class StartupChecks
{
static bool IsWin10Or11()
{
string osInfoStr = OsUtils.TryGetOs();
if (string.IsNullOrWhiteSpace(osInfoStr))
return true; // If it fails, assume we are on Win10
string[] osInfo = osInfoStr.Split(" | ");
string version = osInfo[0].Remove("Microsoft").Trim();
return (version.ToLower().Contains("windows 10") || version.ToLower().Contains("windows 11"));
}
static bool Is32Bit()
{
string osInfoStr = OsUtils.TryGetOs();
if (string.IsNullOrWhiteSpace(osInfoStr))
return false; // If it fails, assume we are on 64bit
string[] osInfo = osInfoStr.Split(" | ");
string arch = osInfo[1].Trim();
return arch.Contains("32");
}
public static void CheckOs()
{
if (!File.Exists(Paths.GetVerPath()) && Paths.GetExeDir().ToLower().Contains("temp"))
{
MessageBox.Show("You seem to be running Flowframes out of an archive.\nPlease extract the whole archive first!", "Error");
IoUtils.TryDeleteIfExists(Paths.GetDataPath());
Application.Exit();
}
string[] osInfo = OsUtils.TryGetOs().Split(" | ");
string version = osInfo[0].Remove("Microsoft").Trim();
if (Is32Bit() && !Config.GetBool("allow32Bit", false))
{
MessageBox.Show("This application is not compatible with 32 bit operating systems!", "Error");
Application.Exit();
}
if (string.IsNullOrWhiteSpace(version))
return;
if (!version.ToLower().Contains("windows 10") && !version.ToLower().Contains("windows 11") && !Config.GetBool("ignoreIncompatibleOs", false))
{
MessageBox.Show($"This application was made for Windows 10/11 and is not officially compatible with {version}.\n\n" +
$"Use it at your own risk and do NOT ask for support as long as your are on {version}.", "Warning");
}
}
public static async Task SymlinksCheck()
{
if (!IsWin10Or11())
return;
bool silent = Config.GetBool("silentDevmodeCheck", true);
string ver = Updater.GetInstalledVer().ToString();
bool symlinksAllowed = Symlinks.SymlinksAllowed();
Logger.Log($"SymlinksAllowed: {symlinksAllowed}", true);
if (!symlinksAllowed && Config.Get(Config.Key.askedForDevModeVersion) != ver)
{
if (!silent)
{
MessageBox.Show("Flowframes will now enable Windows' Developer Mode which is required for video encoding improvements.\n\n" +
"This requires administrator privileges once.", "Message");
}
Logger.Log($"Trying to enable dev mode.", true);
string devmodeBatchPath = Path.Combine(Paths.GetDataPath(), "devmode.bat");
File.WriteAllText(devmodeBatchPath, Properties.Resources.devmode);
Process devmodeProc = OsUtils.NewProcess(true);
devmodeProc.StartInfo.Arguments = $"/C {devmodeBatchPath.Wrap()}";
devmodeProc.Start();
while (!devmodeProc.HasExited) await Task.Delay(100);
bool symlinksWorksNow = false;
for (int retries = 8; retries > 0; retries--)
{
symlinksWorksNow = Symlinks.SymlinksAllowed();
if (symlinksWorksNow)
break;
await Task.Delay(500);
}
if (!symlinksWorksNow)
{
if (!silent)
{
MessageBox.Show("Failed to enable developer mode - Perhaps you do not have sufficient privileges.\n\n" +
"Without Developer Mode, video encoding will be noticably slower.\n\nYou can still try enabling " +
"it manually in the Windows 10 Settings:\nSettings -> Update & security -> For developers -> Developer mode.", "Message");
}
Logger.Log("Failed to enable dev mode.", true);
Config.Set("askedForDevModeVersion", ver);
}
else
{
Logger.Log("Enabled Windows Developer Mode.", silent);
}
IoUtils.TryDeleteIfExists(devmodeBatchPath);
}
}
}
}

187
Code/Os/Updater.cs Normal file
View File

@@ -0,0 +1,187 @@
using Flowframes.Data;
using Flowframes.Forms;
using Flowframes.IO;
using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Flowframes.Os
{
class Updater
{
public enum VersionCompareResult { Older, Newer, Equal };
public static string latestVerUrl = "https://dl.nmkd.de/flowframes/exe/ver.ini";
public static string GetInstalledVerStr()
{
Version ver = GetInstalledVer();
if (ver.Major == 0 && ver.Minor == 0 && ver.Minor == 0) return "";
return ver.ToString();
}
public static Version GetInstalledVer()
{
try
{
string verStr = IoUtils.ReadLines(Paths.GetVerPath())[0];
return new Version(verStr);
}
catch (Exception e)
{
Logger.Log("Error getting installed version!");
Logger.Log(e.Message, true);
return new Version(0, 0, 0);
}
}
public static VersionCompareResult CompareVersions(Version currentVersion, Version newVersion)
{
Logger.Log($"Checking if {newVersion} > {currentVersion}", true);
int result = newVersion.CompareTo(currentVersion);
if (result > 0)
{
Logger.Log($"{newVersion} is newer than {currentVersion}.", true);
return VersionCompareResult.Newer;
}
if (result < 0)
{
Logger.Log($"{newVersion} is older than {currentVersion}.", true);
return VersionCompareResult.Older;
}
Logger.Log($"{newVersion} is equal to {currentVersion}.", true);
return VersionCompareResult.Equal;
}
public static Version GetLatestVer(bool patreon)
{
var client = new WebClient();
int line = patreon ? 0 : 2;
return new Version(client.DownloadString(latestVerUrl).SplitIntoLines()[line]);
}
public static string GetLatestVerLink(bool patreon)
{
int line = patreon ? 1 : 3;
var client = new WebClient();
try
{
return client.DownloadString(latestVerUrl).SplitIntoLines()[line].Trim();
}
catch
{
Logger.Log("Failed to get latest version link from ver.ini!", true);
return "";
}
}
public static async Task UpdateTo(int version, UpdaterForm form = null)
{
Logger.Log("Updating to " + version, true);
string savePath = Path.Combine(Paths.GetExeDir(), $"FlowframesV{version}");
try
{
var client = new WebClient();
client.DownloadProgressChanged += async (sender, args) =>
{
if (form != null && (args.ProgressPercentage % 5 == 0))
{
Logger.Log("Downloading update... " + args.ProgressPercentage, true);
form.SetProgLabel(args.ProgressPercentage, $"Downloading latest version... {args.ProgressPercentage}%");
await Task.Delay(20);
}
};
client.DownloadFileCompleted += (sender, args) =>
{
form.SetProgLabel(100f, $"Downloading latest version... 100%");
};
await client.DownloadFileTaskAsync(new Uri($"https://dl.nmkd.de/flowframes/exe/{version}/Flowframes.exe"), savePath);
}
catch (Exception e)
{
MessageBox.Show("Error: Failed to download update.\n\n" + e.Message, "Error");
Logger.Log("Updater Error during download: " + e.Message, true);
return;
}
try
{
Logger.Log("Installing v" + version, true);
string runningExePath = Paths.GetExe();
string oldExePath = runningExePath + ".old";
IoUtils.TryDeleteIfExists(oldExePath);
File.Move(runningExePath, oldExePath);
File.Move(savePath, runningExePath);
}
catch (Exception e)
{
MessageBox.Show("Error: Failed to install update.\n\n" + e.Message, "Error");
Logger.Log("Updater Error during install: " + e.Message, true);
return;
}
form.SetProgLabel(101f, $"Update downloaded.");
await Task.Delay(20);
MessageBox.Show("Update was installed!\nFlowframes will now close. Restart it to use the new version.", "Message");
Application.Exit();
}
public static async Task AsyncUpdateCheck()
{
Version installed = GetInstalledVer();
Version latestPat = GetLatestVer(true);
Version latestFree = GetLatestVer(false);
Logger.Log($"You are running Flowframes {installed}. The latest Patreon version is {latestPat}, the latest free version is {latestFree}.");
if (installed.ToString() != "0.0.0")
Program.mainForm.Text = $"Flowframes {installed} [GPU: {OsUtils.GetGpus().Remove("NVIDIA ").Remove("AMD ").Remove("Intel ")}]";
}
public static async Task UpdateModelList()
{
if (!Config.GetBool("fetchModelsFromRepo", false))
return;
foreach (AI ai in Implementations.networks)
{
try
{
var client = new WebClient();
string aiName = ai.pkgDir;
string url = $"https://raw.githubusercontent.com/n00mkrad/flowframes/main/Pkgs/{aiName}/models.txt";
string movePath = Path.Combine(Paths.GetPkgPath(), aiName, "models.txt");
string savePath = movePath + ".tmp";
if (!Directory.Exists(savePath.GetParentDir()))
{
Logger.Log($"Skipping {ai.pkgDir} models file download as '{savePath.GetParentDir()}' does not exist!", true);
continue;
}
Logger.Log($"Saving models file from '{url}' to '{savePath}'", true);
client.DownloadFile(url, savePath);
if (IoUtils.GetFilesize(savePath) > 8)
{
File.Delete(movePath);
File.Move(savePath, movePath);
}
else
{
File.Delete(savePath);
}
Program.mainForm.UpdateAiModelCombox();
}
catch (Exception e)
{
Logger.Log($"Failed to fetch models file for {ai.friendlyName}. Ignore this if you are not connected to the internet.");
Logger.Log($"{e.Message}\n{e.StackTrace}", true);
}
}
}
}
}

137
Code/Program.cs Normal file
View File

@@ -0,0 +1,137 @@
using Flowframes.Data;
using Flowframes.Forms;
using Flowframes.IO;
using Flowframes.MiscUtils;
using Flowframes.Os;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
[assembly: System.Windows.Media.DisableDpiAwareness] // Disable Dpi awareness in the application assembly.
namespace Flowframes
{
static class Program
{
public static string[] fileArgs = new string[0];
public static string[] args = new string[0];
public static bool initialRun = true;
public static Form1 mainForm;
public static bool busy = false;
public static string lastInputPath;
public static bool lastInputPathIsSsd;
public static Queue<InterpSettings> batchQueue = new Queue<InterpSettings>();
[STAThread]
static void Main()
{
Config.Init();
if (Config.GetBool(Config.Key.delLogsOnStartup))
IoUtils.DeleteContentsOfDir(Paths.GetLogPath()); // Clear out older logs from previous session
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
Task.Run(() => DiskSpaceCheckLoop());
fileArgs = Environment.GetCommandLineArgs().Where(a => a[0] != '-' && File.Exists(a)).ToList().Skip(1).ToArray();
args = Environment.GetCommandLineArgs().Where(a => a[0] == '-').Select(x => x.Trim().Substring(1).ToLowerInvariant()).ToArray();
Logger.Log($"Command Line: {Environment.CommandLine}", true);
Logger.Log($"Files: {(fileArgs.Length > 0 ? string.Join(", ", fileArgs) : "None")}", true);
Logger.Log($"Args: {(args.Length > 0 ? string.Join(", ", args) : "None")}", true);
LaunchMainForm();
}
static void LaunchMainForm()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
mainForm = new Form1();
Application.Run(mainForm);
}
static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
string text = $"Unhandled Thread Exception!\n\n{e.Exception.Message}\n\nStack Trace:\n{e.Exception.StackTrace}\n\n" +
$"The error has been copied to the clipboard. Please inform the developer about this.";
ShowUnhandledError(text);
}
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Exception ex = e.ExceptionObject as Exception;
string text = $"Unhandled UI Exception!\n\n{ex.Message}\n\nStack Trace:\n{ex.StackTrace}\n\n" +
$"The error has been copied to the clipboard. Please inform the developer about this.";
ShowUnhandledError(text);
}
static void ShowUnhandledError(string text)
{
MessageBox.Show(text, "Unhandled Error");
Clipboard.SetText(text);
}
static async Task DiskSpaceCheckLoop()
{
while (true)
{
if (busy)
{
try
{
if (Interpolate.current == null || Interpolate.current.tempFolder.Length < 3)
return;
string drivePath = Interpolate.current.tempFolder.Substring(0, 2);
long mb = IoUtils.GetDiskSpace(Interpolate.current.tempFolder);
Logger.Log($"Disk space check for '{drivePath}/': {(mb / 1024f).ToString("0.0")} GB free.", true);
bool lowDiskSpace = mb < (Config.GetInt(Config.Key.lowDiskSpacePauseGb, 5) * 1024);
bool tooLowDiskSpace = mb < (Config.GetInt(Config.Key.lowDiskSpaceCancelGb, 2) * 1024);
string spaceGb = (mb / 1024f).ToString("0.0");
if (!Interpolate.canceled && (AiProcess.lastAiProcess != null && !AiProcess.lastAiProcess.HasExited) && lowDiskSpace)
{
if (tooLowDiskSpace)
{
Interpolate.Cancel($"Not enough disk space on '{drivePath}/' ({spaceGb} GB)!");
}
else
{
bool showMsg = !AiProcessSuspend.aiProcFrozen;
AiProcessSuspend.SuspendIfRunning();
if (showMsg)
{
MessageBox.Show($"Interpolation has been paused because you are running out of disk space on '{drivePath}/' ({spaceGb} GB)!\n\n" +
$"Please either clear up some disk space or cancel the interpolation.", "Warning");
}
}
}
}
catch (Exception e)
{
Logger.Log($"Disk space check failed: {e.Message}", true);
}
}
await Task.Delay(15000);
}
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Flowframes.Ui
{
public static class ControlExtensions
{
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool LockWindowUpdate(IntPtr hWndLock);
public static void Suspend(this Control control)
{
LockWindowUpdate(control.Handle);
}
public static void Resume(this Control control)
{
LockWindowUpdate(IntPtr.Zero);
}
}
}

120
Code/Ui/DebugFormHelper.cs Normal file
View File

@@ -0,0 +1,120 @@
using Flowframes.IO;
using Flowframes.MiscUtils;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Flowframes.Ui
{
class DebugFormHelper
{
#region Log Viewer
public static void FillLogDropdown(ComboBox dd)
{
int oldIndex = dd.SelectedIndex;
dd.Items.Clear();
FileInfo[] logFiles = IoUtils.GetFileInfosSorted(Paths.GetLogPath(), false, "*.txt");
foreach (FileInfo file in logFiles)
dd.Items.Add(file.Name);
if (oldIndex < 0)
{
if (dd.Items.Count > 0)
dd.SelectedIndex = 0;
for (int i = 0; i < dd.Items.Count; i++)
{
if (((string)dd.Items[i]).Split('.').FirstOrDefault() == Logger.defaultLogName)
dd.SelectedIndex = i;
}
}
else
{
dd.SelectedIndex = oldIndex;
}
}
public static void RefreshLogBox(TextBox logBox, string logFilename)
{
//bool wrap = logBox.WordWrap;
//logBox.WordWrap = true;
logBox.Text = File.ReadAllText(Path.Combine(Paths.GetLogPath(), logFilename)).Trim('\r', '\n');
logBox.SelectionStart = logBox.Text.Length;
logBox.ScrollToCaret();
//logBox.WordWrap = wrap;
}
public static void ToggleMonospace(TextBox logBox)
{
bool isMonospace = logBox.Font.Name.ToLower().Contains("consolas");
if (isMonospace)
logBox.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.5f);
else
logBox.Font = new System.Drawing.Font("Consolas", 8.0f);
}
public static void CopyLogToClipboard(string logFilename)
{
StringCollection paths = new StringCollection();
string path = Path.Combine(Paths.GetLogPath(), logFilename);
paths.Add(path);
Clipboard.SetFileDropList(paths);
}
#endregion
#region Config Editor Grid
public static void LoadGrid(DataGridView grid)
{
if (grid.Columns.Count < 2)
{
grid.Columns.Add("keys", "Key Name");
grid.Columns.Add("vals", "Saved Value");
}
grid.Rows.Clear();
foreach (KeyValuePair<string, string> keyValuePair in Config.cachedValues)
{
grid.Rows.Add(keyValuePair.Key, keyValuePair.Value);
}
grid.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
grid.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
grid.Columns[0].FillWeight = 50;
grid.Columns[1].FillWeight = 50;
}
public static void SaveGrid(DataGridView grid)
{
NmkdStopwatch sw = new NmkdStopwatch();
Dictionary<string, string> dict = new Dictionary<string, string>();
foreach (DataGridViewRow row in grid.Rows)
{
string key = row.Cells[0].Value?.ToString();
string val = row.Cells[1].Value?.ToString();
if (key == null || val == null || string.IsNullOrWhiteSpace(key.Trim()) || string.IsNullOrWhiteSpace(val.Trim()))
continue;
dict.Add(key, val);
}
Config.Set(dict);
Logger.Log($"Config Editor: Saved {grid.Rows.Count} config keys in {sw.GetElapsedStr()}", true);
}
#endregion
}
}

93
Code/Ui/GetWebInfo.cs Normal file
View File

@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.VisualBasic.Logging;
namespace Flowframes.Ui
{
class GetWebInfo
{
public static async Task LoadNews (Label newsLabel)
{
try
{
string url = $"https://raw.githubusercontent.com/n00mkrad/flowframes/main/changelog.txt";
var client = new WebClient();
var str = await client.DownloadStringTaskAsync(new Uri(url));
newsLabel.Text = str;
}
catch(Exception e)
{
Logger.Log($"Failed to load news: {e.Message}", true);
}
}
public static async Task LoadPatronListCsv(Label patronsLabel)
{
try
{
string url = $"https://raw.githubusercontent.com/n00mkrad/flowframes/main/patrons.csv";
var client = new WebClient();
var csvData = await client.DownloadStringTaskAsync(new Uri(url));
patronsLabel.Text = ParsePatreonCsv(csvData);
}
catch (Exception e)
{
Logger.Log($"Failed to load patreon CSV: {e.Message}", true);
}
}
public static string ParsePatreonCsv(string csvData)
{
try
{
Logger.Log("Parsing Patrons from CSV...", true);
List<string> goldPatrons = new List<string>();
List<string> silverPatrons = new List<string>();
string str = "Gold:\n";
string[] lines = csvData.SplitIntoLines().Select(x => x.Replace(";", ",")).ToArray();
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i];
string[] values = line.Split(',');
if (i == 0 || line.Length < 10 || values.Length < 5) continue;
string name = values[0].Trim();
string status = values[4].Trim();
// float amount = float.Parse(values[7], System.Globalization.CultureInfo.InvariantCulture);
string tier = values[9].Trim();
if (status.Contains("Active"))
{
if (tier.Contains("Gold"))
goldPatrons.Add(name.Trunc(30));
if (tier.Contains("Silver"))
silverPatrons.Add(name.Trunc(30));
}
}
Logger.Log($"Found {goldPatrons.Count} Gold Patrons, {silverPatrons.Count} Silver Patrons", true);
foreach (string pat in goldPatrons)
str += pat + "\n";
str += "\nSilver:\n";
foreach (string pat in silverPatrons)
str += pat + "\n";
return str;
}
catch (Exception e)
{
Logger.Log($"Failed to parse Patreon CSV: {e.Message}\n{e.StackTrace}", true);
return "Failed to load patron list.";
}
}
}
}

View File

@@ -0,0 +1,169 @@
using Flowframes.IO;
using Flowframes.MiscUtils;
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using Flowframes.Forms;
using Flowframes.Main;
using I = Flowframes.Interpolate;
namespace Flowframes.Ui
{
class InterpolationProgress
{
public static int deletedFramesCount;
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 PictureBox preview;
public static BigPreviewForm bigPreviewForm;
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);
deletedFramesCount = 0;
lastFrame = 0;
peakFpsOut = 0f;
while (Program.busy)
{
if (!progressPaused && AiProcess.processTime.IsRunning && Directory.Exists(currentOutdir))
{
if (firstProgUpd && Program.mainForm.IsInFocus())
Program.mainForm.SetTab("preview");
firstProgUpd = false;
string lastFramePath = currentOutdir + "\\" + lastFrame.ToString("00000000") + I.current.interpExt;
if (lastFrame > 1)
UpdateInterpProgress(lastFrame, targetFrames, lastFramePath);
await Task.Delay((target < 1000) ? 100 : 200); // Update 10x/sec if interpolating <1k frames, otherwise 5x/sec
if (lastFrame >= targetFrames)
break;
}
else
{
await Task.Delay(100);
}
}
progCheckRunning = false;
if (I.canceled)
Program.mainForm.SetProgress(0);
}
public static void UpdateLastFrameFromInterpOutput(string output)
{
try
{
string ncnnStr = I.current.ai.aiName.Contains("NCNN") ? " done" : "";
Regex frameRegex = new Regex($@"(?<=.)\d*(?={I.current.interpExt}{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!", true);
}
}
public static int interpolatedInputFramesCount;
public static float peakFpsOut;
public static int previewUpdateRateMs = 200;
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);
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 / currentFactor).ToString("0.00");
string fpsOut = fps.ToString("0.00");
if (fps > peakFpsOut)
peakFpsOut = fps;
float secondsPerFrame = generousTime / (float)frames;
int framesLeft = target - frames;
float eta = framesLeft * secondsPerFrame;
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: ");
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);
try
{
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
if (timeSinceLastPreviewUpdate.IsRunning && timeSinceLastPreviewUpdate.ElapsedMilliseconds < previewUpdateRateMs) return;
Image img = IoUtils.GetImage(latestFramePath, false, false);
SetPreviewImg(img);
}
}
catch (Exception e)
{
//Logger.Log("Error updating preview: " + e.Message, true);
}
}
public static async Task DeleteInterpolatedInputFrames()
{
interpolatedInputFramesCount = 0;
string[] inputFrames = IoUtils.GetFilesSorted(I.current.framesFolder);
for (int i = 0; i < inputFrames.Length; i++)
{
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);
}
}
public static Stopwatch timeSinceLastPreviewUpdate = new Stopwatch();
public static void SetPreviewImg(Image img)
{
if (img == null)
return;
timeSinceLastPreviewUpdate.Restart();
preview.Image = img;
if (bigPreviewForm != null)
bigPreviewForm.SetImage(img);
}
}
}

127
Code/Ui/MainUiFunctions.cs Normal file
View File

@@ -0,0 +1,127 @@
using Flowframes.Media;
using Flowframes.IO;
using Flowframes.Magick;
using Flowframes.Main;
using Flowframes.Os;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Flowframes.Data;
namespace Flowframes.Ui
{
class MainUiFunctions
{
public static async Task InitInput (TextBox outputTbox, TextBox inputTbox, TextBox fpsInTbox, bool start = false)
{
Program.mainForm.SetTab("interpolate");
Program.mainForm.ResetInputInfo();
string path = inputTbox.Text.Trim();
if (Config.GetBool(Config.Key.clearLogOnInput))
Logger.ClearLogBox();
outputTbox.Text = (Config.GetInt("outFolderLoc") == 0) ? inputTbox.Text.Trim().GetParentDir() : Config.Get("custOutDir").Trim();
Program.lastInputPath = path;
Program.lastInputPathIsSsd = OsUtils.DriveIsSSD(path);
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.");
Logger.Log("Loading metadata...");
Program.mainForm.currInDuration = FfmpegCommands.GetDuration(path);
Program.mainForm.currInDurationCut = Program.mainForm.currInDuration;
int frameCount = await GetFrameCountCached.GetFrameCountAsync(path);
string fpsStr = "Not Found";
Fraction fps = (await IoUtils.GetFpsFolderOrVideo(path));
Program.mainForm.currInFpsDetected = fps;
fpsInTbox.Text = fps.GetString();
if (fps.GetFloat() > 0)
fpsStr = $"{fps} (~{fps.GetFloat()})";
Logger.Log($"Video FPS: {fpsStr} - Total Number Of Frames: {frameCount}", false, true);
Program.mainForm.GetInputFpsTextbox().ReadOnly = (fps.GetFloat() > 0 && !Config.GetBool("allowCustomInputRate", false));
Program.mainForm.currInFps = fps;
Program.mainForm.currInFrames = frameCount;
Program.mainForm.UpdateInputInfo();
CheckExistingFolder(path, outputTbox.Text.Trim());
await Task.Delay(10);
await PrintResolution(path);
Dedupe.ClearCache();
await Task.Delay(10);
InterpolationProgress.SetPreviewImg(await GetThumbnail(path));
if (start)
Program.mainForm.runBtn_Click(null, null);
}
static void CheckExistingFolder (string inpath, string outpath)
{
if (Interpolate.current == null || !Interpolate.current.stepByStep) return;
string tmpFolder = InterpolateUtils.GetTempFolderLoc(inpath, outpath);
if (Directory.Exists(tmpFolder))
{
int scnFrmAmount = IoUtils.GetAmountOfFiles(Path.Combine(tmpFolder, Paths.scenesDir), false, "*" + Interpolate.current.interpExt); // TODO: Make this work if the frames extension was changed
string scnFrames = scnFrmAmount > 0 ? $"{scnFrmAmount} scene frames" : "no scene frames";
int srcFrmAmount = IoUtils.GetAmountOfFiles(Path.Combine(tmpFolder, Paths.framesDir), false, "*" + Interpolate.current.interpExt);
string srcFrames = srcFrmAmount > 1 ? $"{srcFrmAmount} source frames" : "no source frames";
int interpFrmAmount = IoUtils.GetAmountOfFiles(Path.Combine(tmpFolder, Paths.interpDir), false);
string interpFrames = interpFrmAmount > 2 ? $"{interpFrmAmount} interpolated frames" : "no interpolated frames";
string msg = $"A temporary folder for this video already exists. It contains {scnFrames}, {srcFrames}, {interpFrames}.";
DialogResult dialogResult = MessageBox.Show($"{msg}\n\nClick \"Yes\" to use the existing files or \"No\" to delete them.", "Use files from existing temp folder?", MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.No)
{
IoUtils.TryDeleteIfExists(tmpFolder);
Logger.Log("Deleted old temp folder.");
}
}
}
static async Task PrintResolution (string path)
{
Size res = new Size();
if(path == Interpolate.current?.inPath)
res = await Interpolate.current.GetInputRes();
else
res = await GetMediaResolutionCached.GetSizeAsync(path);
if (res.Width > 1 && res.Height > 1)
Logger.Log($"Input Resolution: {res.Width}x{res.Height}");
Program.mainForm.currInRes = res;
Program.mainForm.UpdateInputInfo();
}
public static async Task<Image> GetThumbnail (string path)
{
string imgOnDisk = Path.Combine(Paths.GetDataPath(), "thumb-temp.jpg");
try
{
if (!IoUtils.IsPathDirectory(path)) // If path is video - Extract first frame
{
await FfmpegExtract.ExtractSingleFrame(path, imgOnDisk, 1);
return IoUtils.GetImage(imgOnDisk);
}
else // Path is frame folder - Get first frame
{
return IoUtils.GetImage(IoUtils.GetFilesSorted(path)[0]);
}
}
catch (Exception e)
{
Logger.Log("GetThumbnail Error: " + e.Message, true);
return null;
}
}
}
}

View File

@@ -0,0 +1,49 @@
using Flowframes.Media;
using Flowframes.IO;
using Flowframes.Magick;
using Flowframes.Main;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Flowframes.MiscUtils;
namespace Flowframes.Ui
{
class QuickSettingsTab
{
public static bool trimEnabled;
public static bool doTrimEnd;
public static string trimStart;
public static string trimEnd;
public static long trimStartSecs;
public static long trimEndSecs;
public static void UpdateTrim (TextBox trimStartBox, TextBox trimEndBox)
{
trimStart = trimStartBox.Text.Trim();
trimEnd = trimEndBox.Text.Trim();
trimStartSecs = FormatUtils.TimestampToSecs(trimStart, false);
trimEndSecs = FormatUtils.TimestampToSecs(trimEnd, false);
if (trimEndSecs <= trimStartSecs)
trimEndBox.Text = FormatUtils.SecsToTimestamp(trimStartSecs + 1);
long dur = FormatUtils.TimestampToMs(trimEnd, false) - FormatUtils.TimestampToMs(trimStart, false);
Program.mainForm.currInDurationCut = dur;
doTrimEnd = FormatUtils.TimestampToMs(trimEnd, false) != FormatUtils.TimestampToMs(FormatUtils.MsToTimestamp(Program.mainForm.currInDuration), false);
}
public static string GetTrimEndMinusOne ()
{
TimeSpan minusOne = TimeSpan.Parse(trimEnd).Subtract(new TimeSpan(0, 0, 1));
Logger.Log($"returning {minusOne}", true, false, "ffmpeg");
return minusOne.ToString();
}
}
}

79
Code/Ui/UiUtils.cs Normal file
View File

@@ -0,0 +1,79 @@
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.Main;
using Flowframes.Os;
using System;
using System.Windows.Forms;
namespace Flowframes.Ui
{
class UiUtils
{
public static void InitCombox(ComboBox box, int index)
{
if (box.Items.Count >= 1)
{
box.SelectedIndex = index;
box.Text = box.Items[index].ToString();
}
}
public static bool AssignComboxIndexFromText (ComboBox box, string text) // Set index to corresponding text
{
int index = box.Items.IndexOf(text);
if (index == -1) // custom value, index not found
return false;
box.SelectedIndex = index;
return true;
}
public static ComboBox LoadAiModelsIntoGui (ComboBox combox, AI ai)
{
combox.Items.Clear();
try
{
ModelCollection modelCollection = AiModels.GetModels(ai);
for (int i = 0; i < modelCollection.models.Count; i++)
{
ModelCollection.ModelInfo modelInfo = modelCollection.models[i];
if (string.IsNullOrWhiteSpace(modelInfo.name))
continue;
combox.Items.Add(modelInfo.GetUiString());
if (modelInfo.isDefault)
combox.SelectedIndex = i;
}
if (combox.SelectedIndex < 0)
combox.SelectedIndex = 0;
SelectNcnnIfNoCudaAvail(combox);
}
catch (Exception e)
{
Logger.Log($"Failed to load available AI models for {ai.aiName}! {e.Message}");
Logger.Log($"Stack Trace: {e.StackTrace}", true);
}
return combox;
}
static void SelectNcnnIfNoCudaAvail (ComboBox combox)
{
if(NvApi.gpuList.Count < 1)
{
for(int i = 0; i < combox.Items.Count; i++)
{
if (((string)combox.Items[i]).ToUpper().Contains("NCNN"))
combox.SelectedIndex = i;
}
}
}
}
}