mirror of
https://github.com/n00mkrad/flowframes.git
synced 2025-12-16 16:37:48 +01:00
Case-sensitive rename operation (2/2)
This commit is contained in:
31
Code/Data/AI.cs
Normal file
31
Code/Data/AI.cs
Normal 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
18
Code/Data/AudioTrack.cs
Normal 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
14
Code/Data/Filetypes.cs
Normal 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
256
Code/Data/Fraction.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Code/Data/Implementations.cs
Normal file
28
Code/Data/Implementations.cs
Normal 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
252
Code/Data/InterpSettings.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Code/Data/ModelCollection.cs
Normal file
63
Code/Data/ModelCollection.cs
Normal 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
15
Code/Data/Padding.cs
Normal 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
75
Code/Data/Paths.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Code/Data/PseudoUniqueFile.cs
Normal file
20
Code/Data/PseudoUniqueFile.cs
Normal 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
56
Code/Data/ResumeState.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Code/Data/SubtitleTrack.cs
Normal file
20
Code/Data/SubtitleTrack.cs
Normal 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
122
Code/Data/VidExtraData.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
214
Code/Extensions/ExtensionMethods.cs
Normal file
214
Code/Extensions/ExtensionMethods.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Code/Extensions/ProcessExtensions.cs
Normal file
61
Code/Extensions/ProcessExtensions.cs
Normal 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
266
Code/Forms/BatchForm.Designer.cs
generated
Normal 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
181
Code/Forms/BatchForm.cs
Normal 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
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
70
Code/Forms/BigPreviewForm.Designer.cs
generated
Normal 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;
|
||||
}
|
||||
}
|
||||
37
Code/Forms/BigPreviewForm.cs
Normal file
37
Code/Forms/BigPreviewForm.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
3185
Code/Forms/BigPreviewForm.resx
Normal file
3185
Code/Forms/BigPreviewForm.resx
Normal file
File diff suppressed because it is too large
Load Diff
356
Code/Forms/DebugForm.Designer.cs
generated
Normal file
356
Code/Forms/DebugForm.Designer.cs
generated
Normal 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
108
Code/Forms/DebugForm.cs
Normal 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
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
263
Code/Forms/ModelDownloadForm.Designer.cs
generated
Normal 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;
|
||||
}
|
||||
}
|
||||
84
Code/Forms/ModelDownloadForm.cs
Normal file
84
Code/Forms/ModelDownloadForm.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
3193
Code/Forms/ModelDownloadForm.resx
Normal file
3193
Code/Forms/ModelDownloadForm.resx
Normal file
File diff suppressed because it is too large
Load Diff
2538
Code/Forms/SettingsForm.Designer.cs
generated
Normal file
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
286
Code/Forms/SettingsForm.cs
Normal 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
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
118
Code/Forms/TimeoutForm.Designer.cs
generated
Normal 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
77
Code/Forms/TimeoutForm.cs
Normal 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
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
208
Code/Forms/UpdaterForm.Designer.cs
generated
Normal 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
82
Code/Forms/UpdaterForm.cs
Normal 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
2445
Code/Forms/UpdaterForm.resx
Normal file
File diff suppressed because it is too large
Load Diff
15
Code/IO/CfgStrings.cs
Normal file
15
Code/IO/CfgStrings.cs
Normal 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
387
Code/IO/Config.cs
Normal 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
80
Code/IO/ConfigParser.cs
Normal 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
951
Code/IO/IoUtils.cs
Normal 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
162
Code/IO/Logger.cs
Normal 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
280
Code/IO/ModelDownloader.cs
Normal 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
98
Code/IO/Symlinks.cs
Normal 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
166
Code/Magick/Blend.cs
Normal 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
145
Code/Magick/Converter.cs
Normal 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
238
Code/Magick/Dedupe.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
164
Code/Magick/MagickExtensions.cs
Normal file
164
Code/Magick/MagickExtensions.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
94
Code/Magick/SceneDetect.cs
Normal file
94
Code/Magick/SceneDetect.cs
Normal 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
71
Code/Main/AiModels.cs
Normal 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
241
Code/Main/AutoEncode.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
121
Code/Main/BatchProcessing.cs
Normal file
121
Code/Main/BatchProcessing.cs
Normal 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
306
Code/Main/CreateVideo.cs
Normal 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
185
Code/Main/FrameOrder.cs
Normal 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
263
Code/Main/Interpolate.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
147
Code/Main/InterpolateSteps.cs
Normal file
147
Code/Main/InterpolateSteps.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
328
Code/Main/InterpolateUtils.cs
Normal file
328
Code/Main/InterpolateUtils.cs
Normal 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
121
Code/Main/ResumeUtils.cs
Normal 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
211
Code/Media/AvProcess.cs
Normal 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
52
Code/Media/FfmpegAlpha.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
86
Code/Media/FfmpegAudioAndMetadata.cs
Normal file
86
Code/Media/FfmpegAudioAndMetadata.cs
Normal 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
|
||||
}
|
||||
}
|
||||
282
Code/Media/FfmpegCommands.cs
Normal file
282
Code/Media/FfmpegCommands.cs
Normal 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
106
Code/Media/FfmpegEncode.cs
Normal 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
312
Code/Media/FfmpegExtract.cs
Normal 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
301
Code/Media/FfmpegUtils.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
62
Code/Media/GetFrameCountCached.cs
Normal file
62
Code/Media/GetFrameCountCached.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Code/Media/GetMediaResolutionCached.cs
Normal file
58
Code/Media/GetMediaResolutionCached.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
84
Code/Media/GetVideoInfoCached.cs
Normal file
84
Code/Media/GetVideoInfoCached.cs
Normal 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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Code/MiscUtils/BackgroundTaskManager.cs
Normal file
68
Code/MiscUtils/BackgroundTaskManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Code/MiscUtils/Benchmarker.cs
Normal file
24
Code/MiscUtils/Benchmarker.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
182
Code/MiscUtils/FormatUtils.cs
Normal file
182
Code/MiscUtils/FormatUtils.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Code/MiscUtils/FrameRename.cs
Normal file
47
Code/MiscUtils/FrameRename.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
87
Code/MiscUtils/ModelDownloadFormUtils.cs
Normal file
87
Code/MiscUtils/ModelDownloadFormUtils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Code/MiscUtils/NmkdStopwatch.cs
Normal file
22
Code/MiscUtils/NmkdStopwatch.cs
Normal 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
564
Code/Os/AiProcess.cs
Normal 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
114
Code/Os/AiProcessSuspend.cs
Normal 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
131
Code/Os/NvApi.cs
Normal 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
264
Code/Os/OsUtils.cs
Normal 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
183
Code/Os/Python.cs
Normal 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
127
Code/Os/StartupChecks.cs
Normal 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
187
Code/Os/Updater.cs
Normal 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
137
Code/Program.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Code/Ui/ControlExtensions.cs
Normal file
25
Code/Ui/ControlExtensions.cs
Normal 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
120
Code/Ui/DebugFormHelper.cs
Normal 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
93
Code/Ui/GetWebInfo.cs
Normal 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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
169
Code/Ui/InterpolationProgress.cs
Normal file
169
Code/Ui/InterpolationProgress.cs
Normal 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
127
Code/Ui/MainUiFunctions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Code/Ui/QuickSettingsTab.cs
Normal file
49
Code/Ui/QuickSettingsTab.cs
Normal 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
79
Code/Ui/UiUtils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user