Split into legacy (Framework 4.8) and .NET 8 projects, WIP .NET 8 fixes

This commit is contained in:
N00MKRAD
2024-08-12 10:47:19 +02:00
parent 6c406a4846
commit b520d7212d
340 changed files with 50433 additions and 0 deletions

107
Flowframes/Data/AI.cs Normal file
View File

@@ -0,0 +1,107 @@
using Flowframes.IO;
using Flowframes.MiscUtils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Data
{
public class AI
{
public enum AiBackend { Pytorch, Ncnn, Tensorflow, Other }
public AiBackend Backend { get; set; } = AiBackend.Pytorch;
public string NameInternal { get; set; } = "";
public string NameShort { get { return NameInternal.Split(' ')[0].Split('_')[0]; } }
public string NameLong { get; set; } = "";
public string FriendlyName { get { return $"{NameShort} ({GetFrameworkString(Backend)})"; } }
public string Description { get { return $"{GetImplemString(Backend)} of {NameShort}{(Backend == AiBackend.Pytorch ? " (Nvidia Only!)" : "")}"; } }
public string PkgDir { get { return NameInternal.Replace("_", "-").ToLowerInvariant(); } }
public enum InterpFactorSupport { Fixed, AnyPowerOfTwo, AnyInteger, AnyFloat }
public InterpFactorSupport FactorSupport { get; set; } = InterpFactorSupport.Fixed;
public int[] SupportedFactors { get; set; } = new int[0];
public bool Piped { get; set; } = false;
public string LogFilename { get { return PkgDir + "-log"; } }
public AI () { }
public AI(AiBackend backend, string aiName, string longName, InterpFactorSupport factorSupport = InterpFactorSupport.Fixed, int[] supportedFactors = null)
{
Backend = backend;
NameInternal = aiName;
NameLong = longName;
SupportedFactors = supportedFactors;
FactorSupport = factorSupport;
}
public string GetVerboseInfo ()
{
return $"Name:\n{NameShort}\n\n" +
$"Full Name:\n{NameLong}\n\n" +
$"Inference Framework:\n{FormatUtils.CapsIfShort(Backend.ToString(), 5)}\n\n" +
$"Hardware Acceleration:\n{GetHwAccelString(Backend)}\n\n" +
$"Supported Interpolation Factors:\n{GetFactorsString(FactorSupport)}\n\n" +
$"Requires Frame Extraction:\n{(Piped ? "No" : "Yes")}\n\n" +
$"Package Directory/Size:\n{PkgDir} ({FormatUtils.Bytes(IoUtils.GetDirSize(Path.Combine(Paths.GetPkgPath(), PkgDir), true))})";
}
private string GetImplemString (AiBackend backend)
{
if (backend == AiBackend.Pytorch)
return $"CUDA/Pytorch Implementation";
if(backend == AiBackend.Ncnn)
return $"Vulkan/NCNN{(Piped ? "/VapourSynth" : "")} Implementation";
if (backend == AiBackend.Tensorflow)
return $"Tensorflow Implementation";
return "";
}
private string GetFrameworkString(AiBackend backend)
{
if (backend == AiBackend.Pytorch)
return $"CUDA";
if (backend == AiBackend.Ncnn)
return $"NCNN{(Piped ? "/VS" : "")}";
if (backend == AiBackend.Tensorflow)
return $"TF";
return "Custom";
}
private string GetHwAccelString (AiBackend backend)
{
if (Backend == AiBackend.Pytorch)
return $"GPU (Nvidia CUDA)";
if (Backend == AiBackend.Ncnn)
return $"GPU (Vulkan)";
return "Unknown";
}
private string GetFactorsString (InterpFactorSupport factorSupport)
{
if (factorSupport == InterpFactorSupport.Fixed)
return $"{string.Join(", ", SupportedFactors.Select(x => $"{x}x"))}";
if (factorSupport == InterpFactorSupport.AnyPowerOfTwo)
return "Any powers of 2 (2/4/8/16 etc.)";
if (factorSupport == InterpFactorSupport.AnyInteger)
return "Any integer (whole number)";
if (factorSupport == InterpFactorSupport.AnyFloat)
return "Any, including fractional factors";
return "Unknown";
}
}
}

View File

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

View File

@@ -0,0 +1,14 @@
namespace Flowframes.Data
{
public class EncoderInfo
{
public virtual string FfmpegName { get; set; } = "";
public EncoderInfo() { }
public EncoderInfo(string name)
{
FfmpegName = name;
}
}
}

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
using static Flowframes.Data.Enums.Encoding;
namespace Flowframes.Data
{
public class EncoderInfoVideo : EncoderInfo
{
public Codec Codec { get; set; } = (Codec)(-1);
public bool? Lossless { get; set; } = false; // True = Lossless Codec; False = Lossy codec with lossless option; null: Lossy with no lossless option
public bool HwAccelerated { get; set; } = false;
public int Modulo { get; set; } = 2;
public int MaxFramerate { get; set; } = 1000;
public List<PixelFormat> PixelFormats { get; set; } = new List<PixelFormat>();
public PixelFormat PixelFormatDefault { get; set; } = (PixelFormat)(-1);
public bool IsImageSequence { get; set; } = false;
public string OverideExtension { get; set; } = "";
public List<string> QualityLevels { get; set; } = new List<string> ();
public int QualityDefault { get; set; } = 0;
public string Name
{
get
{
return FfmpegName.IsEmpty() ? Codec.ToString().Lower() : FfmpegName;
}
set
{
FfmpegName = value;
}
}
}
}

33
Flowframes/Data/Enums.cs Normal file
View File

@@ -0,0 +1,33 @@
namespace Flowframes.Data
{
public class Enums
{
public class Output
{
public enum Format { Mp4, Mkv, Webm, Mov, Avi, Gif, Images, Realtime };
public enum ImageFormat { Png, Jpeg, Webp, Tiff, Exr };
public enum Dithering { None, Bayer, FloydSteinberg };
}
public class Encoding
{
public enum Codec { H264, H265, AV1, VP9, ProRes, Gif, Png, Jpeg, Webp, Tiff, Exr, Ffv1, Huffyuv, Magicyuv, Rawvideo }
public enum Encoder { X264, X265, SvtAv1, VpxVp9, Nvenc264, Nvenc265, NvencAv1, Amf264, Amf265, Qsv264, Qsv265, ProResKs, Gif, Png, Jpeg, Webp, Tiff, Exr, Ffv1, Huffyuv, Magicyuv, Rawvideo }
public enum PixelFormat
{
Yuv420P, Yuva420P, Yuv420P10Le, Yuv422P, Yuv422P10Le, Yuv444P, Yuv444P10Le, Yuva444P10Le, Yuv444P12Le, Yuv444P16Le, P010Le, P016Le, // YUV & similar
Rgb24, Rgba, Rgb48Le, Rgb48Be, Rgba64Le, Rgba64Be, Pal8, // RGB & other
Gbrpf32Le, Gbrapf32Le, // Float
};
public class Quality
{
public enum Common { Lossless, VeryHigh, High, Medium, Low, VeryLow, Custom }
public enum JpegWebm { ImgMax, ImgHigh, ImgMed, ImgLow, ImgLowest }
public enum ProResProfile { Proxy, Lt, Standard, Hq, Quad4, Quad4Xq }
public enum GifColors { Max256, High128, Medium64, Low32, VeryLow16 }
public enum ExrPrecision { Float, Half }
}
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Data
{
public class OutputSettings
{
public Enums.Output.Format Format { get; set; }
public Enums.Encoding.Encoder Encoder { get; set; }
public Enums.Encoding.PixelFormat PixelFormat { get; set; }
public string Quality { get; set; } = "";
public string CustomQuality { get; set; } = "";
}
}

View File

@@ -0,0 +1,8 @@
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", ".tif", ".tiff", ".exr" };
}
}

View File

@@ -0,0 +1,24 @@
namespace Flowframes.Data
{
public class FpsInfo
{
public Fraction Fps { get; set; }
public Fraction SpecifiedFps { get; set; }
public float VfrRatio { get => Fps.GetFloat() / SpecifiedFps.GetFloat(); }
public float VfrRatioInverse { get => SpecifiedFps.GetFloat() / Fps.GetFloat(); }
public FpsInfo() { }
public FpsInfo(Fraction fps)
{
Fps = fps;
SpecifiedFps = fps;
}
public FpsInfo(Fraction fps, Fraction specifiedFps)
{
Fps = fps;
SpecifiedFps = specifiedFps;
}
}
}

264
Flowframes/Data/Fraction.cs Normal file
View File

@@ -0,0 +1,264 @@
using System;
using System.Windows.Navigation;
namespace Flowframes.Data
{
public struct Fraction
{
public long Numerator;
public long Denominator;
public static Fraction Zero = new Fraction(0, 0);
public Fraction(long numerator, long 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(long numerator, Fraction denominator)
{
//divide the numerator by the denominator fraction
this = new Fraction(numerator, 1) / denominator;
}
public Fraction(Fraction numerator, long 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;
}
}
Console.WriteLine($"Fraction from String: Fraction(\"{text}\") => {Numerator}/{Denominator}", true);
}
private static long GetGreatestCommonDenominator(long a, long b)
{
//Drop negative signs
a = Math.Abs(a);
b = Math.Abs(b);
//Return the greatest common denominator between two longegers
while (a != 0 && b != 0)
{
if (a > b)
a %= b;
else
b %= a;
}
if (a == 0)
return b;
else
return a;
}
private static long GetLeastCommonDenominator(long a, long b)
{
//Return the Least Common Denominator between two longegers
return (a * b) / GetGreatestCommonDenominator(a, b);
}
public Fraction ToDenominator(long 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)
{
long 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
long gcd = 0;
while (Math.Abs(gcd = GetGreatestCommonDenominator(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
long lcd = GetLeastCommonDenominator(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
long lcd = GetLeastCommonDenominator(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, long multi)
{
long numerator = (long)fract.Numerator * (long)multi;
long denomenator = fract.Denominator;
return new Fraction(numerator, denomenator).GetReduced();
}
public static Fraction operator *(Fraction fract, double multi)
{
long numerator = (long)Math.Round((double)(fract.Numerator * (double)multi));
long denomenator = fract.Denominator;
return new Fraction(numerator, denomenator).GetReduced();
}
public static Fraction operator *(Fraction fract, float multi)
{
long numerator = (fract.Numerator * multi).RoundToInt();
long denomenator = fract.Denominator;
return new Fraction(numerator, denomenator).GetReduced();
}
public static Fraction operator *(Fraction fraction1, Fraction fraction2)
{
long numerator = fraction1.Numerator * fraction2.Numerator;
long denomenator = fraction1.Denominator * fraction2.Denominator;
return new Fraction(numerator, denomenator).GetReduced();
}
public static Fraction operator /(Fraction fraction1, Fraction fraction2)
{
return new Fraction(fraction1 * fraction2.GetReciprocal()).GetReduced();
}
public double ToDouble()
{
return (double)this.Numerator / this.Denominator;
}
public override string ToString()
{
return Numerator + "/" + Denominator;
}
public float GetFloat()
{
if (Denominator < 1) // Avoid div by zero
return 0f;
return (float)Numerator / (float)Denominator;
}
public long GetLong()
{
return (long)Numerator / (long)Denominator;
}
public string GetString()
{
return ((float)Numerator / Denominator).ToString();
}
}
}

View File

@@ -0,0 +1,108 @@
using Flowframes.Os;
using System.Collections.Generic;
using System.Linq;
namespace Flowframes.Data
{
class Implementations
{
public static bool DisablePython = false;
public static AI rifeCuda = new AI()
{
Backend = AI.AiBackend.Pytorch,
NameInternal = "RIFE_CUDA",
NameLong = "Real-Time Intermediate Flow Estimation",
FactorSupport = AI.InterpFactorSupport.AnyInteger,
SupportedFactors = new int[] { 2, 3, 4, 5, 6, 7, 8, 9, 10 }
};
public static AI rifeNcnn = new AI()
{
Backend = AI.AiBackend.Ncnn,
NameInternal = "RIFE_NCNN",
NameLong = "Real-Time Intermediate Flow Estimation",
FactorSupport = AI.InterpFactorSupport.AnyFloat,
SupportedFactors = new int[] { 2, 3, 4, 5, 6, 7, 8, 9, 10 },
};
public static AI rifeNcnnVs = new AI()
{
Backend = AI.AiBackend.Ncnn,
NameInternal = "RIFE_NCNN_VS",
NameLong = "Real-Time Intermediate Flow Estimation",
FactorSupport = AI.InterpFactorSupport.AnyFloat,
SupportedFactors = new int[] { 2, 3, 4, 5, 6, 7, 8, 9, 10 },
Piped = true
};
public static AI flavrCuda = new AI()
{
Backend = AI.AiBackend.Pytorch,
NameInternal = "FLAVR_CUDA",
NameLong = "Flow-Agnostic Video Representations",
FactorSupport = AI.InterpFactorSupport.Fixed,
SupportedFactors = new int[] { 2, 4, 8 },
};
public static AI dainNcnn = new AI()
{
Backend = AI.AiBackend.Ncnn,
NameInternal = "DAIN_NCNN",
NameLong = "Depth-Aware Video Frame Interpolation",
FactorSupport = AI.InterpFactorSupport.AnyFloat,
SupportedFactors = new int[] { 2, 3, 4, 5, 6, 7, 8 },
};
public static AI xvfiCuda = new AI()
{
Backend = AI.AiBackend.Pytorch,
NameInternal = "XVFI_CUDA",
NameLong = "eXtreme Video Frame Interpolation",
FactorSupport = AI.InterpFactorSupport.AnyInteger,
SupportedFactors = new int[] { 2, 3, 4, 5, 6, 7, 8, 9, 10 },
};
public static AI ifrnetNcnn = new AI()
{
Backend = AI.AiBackend.Ncnn,
NameInternal = "IFRNet_NCNN",
NameLong = "Intermediate Feature Refine Network",
FactorSupport = AI.InterpFactorSupport.Fixed,
SupportedFactors = new int[] { 2 },
};
public static List<AI> NetworksAll
{
get
{
return new List<AI> { rifeNcnnVs, rifeNcnn, rifeCuda, flavrCuda, dainNcnn, xvfiCuda, /* ifrnetNcnn */ };
}
}
public static List<AI> NetworksAvailable
{
get
{
bool pytorchAvailable = !DisablePython && Python.IsPytorchReady();
if (pytorchAvailable)
return NetworksAll;
return NetworksAll.Where(x => x.Backend != AI.AiBackend.Pytorch).ToList();
}
}
public static AI GetAi(string aiName)
{
foreach (AI ai in NetworksAll)
{
if (ai.NameInternal == aiName)
return ai;
}
Logger.Log($"AI implementation lookup failed! This should not happen! Please tell the developer! (Implementations.cs)");
return NetworksAll[0];
}
}
}

View File

@@ -0,0 +1,251 @@
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;
namespace Flowframes
{
public class InterpSettings
{
public string inPath;
public string outPath;
public string FullOutPath { get; set; } = "";
public AI ai;
public string inPixFmt = "yuv420p";
public Fraction inFps;
public Fraction inFpsDetected;
public Fraction outFps;
public float outItsScale;
public float interpFactor;
public OutputSettings outSettings;
public ModelCollection.ModelInfo model;
public string tempFolder;
public string framesFolder;
public string interpFolder;
public bool inputIsFrames;
private Size _inputResolution;
public Size InputResolution { get { RefreshInputRes(); return _inputResolution; } }
public Size ScaledResolution { get { return InterpolateUtils.GetOutputResolution(InputResolution, false); } }
public Size ScaledPaddedResolution { get { return InterpolateUtils.GetOutputResolution(InputResolution, true); } }
public bool alpha;
public bool stepByStep;
public string framesExt;
public string interpExt;
public InterpSettings() { }
public InterpSettings(string inPathArg, string outPathArg, AI aiArg, Fraction inFpsDetectedArg, Fraction inFpsArg, float interpFactorArg, float itsScale, OutputSettings outSettingsArg, ModelCollection.ModelInfo modelArg)
{
inPath = inPathArg;
outPath = outPathArg;
ai = aiArg;
inFpsDetected = inFpsDetectedArg;
inFps = inFpsArg;
interpFactor = interpFactorArg;
outItsScale = itsScale;
outSettings = outSettingsArg;
model = modelArg;
InitArgs();
}
public void InitArgs ()
{
outFps = inFps * (double)interpFactor;
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);
RefreshExtensions(ai: ai);
}
public InterpSettings (string serializedData)
{
inPath = "";
outPath = "";
ai = null;
inFpsDetected = new Fraction();
inFps = new Fraction();
interpFactor = 0;
outFps = new Fraction();
outSettings = new OutputSettings();
model = null;
alpha = false;
stepByStep = false;
_inputResolution = 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]);
}
// TODO: Rework this ugly stuff, JSON?
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": outSettings.Format = (Enums.Output.Format)Enum.Parse(typeof(Enums.Output.Format), entry.Value); break;
case "MODEL": model = AiModels.GetModelByName(ai, entry.Value); break;
case "INPUTRES": _inputResolution = 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);
}
async Task RefreshInputRes ()
{
if (_inputResolution.IsEmpty)
_inputResolution = await GetMediaResolutionCached.GetSizeAsync(inPath);
}
public void RefreshAlpha ()
{
try
{
bool alphaModel = model.SupportsAlpha;
bool pngOutput = outSettings.Encoder == Enums.Encoding.Encoder.Png;
bool gifOutput = outSettings.Encoder == Enums.Encoding.Encoder.Gif;
bool proResAlpha = outSettings.Encoder == Enums.Encoding.Encoder.ProResKs && OutputUtils.AlphaFormats.Contains(outSettings.PixelFormat);
bool outputSupportsAlpha = pngOutput || gifOutput || proResAlpha;
string ext = inputIsFrames ? Path.GetExtension(IoUtils.GetFilesSorted(inPath).First()).ToLowerInvariant() : Path.GetExtension(inPath).ToLowerInvariant();
alpha = (alphaModel && outputSupportsAlpha && (ext == ".gif" || ext == ".png" || ext == ".apng" || ext == ".mov"));
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, AI ai = null)
{
if(ai == null)
{
if (Interpolate.currentSettings == null)
return;
ai = Interpolate.currentSettings.ai;
}
bool pngOutput = outSettings.Encoder == Enums.Encoding.Encoder.Png;
bool aviHqChroma = outSettings.Format == Enums.Output.Format.Avi && OutputUtils.AlphaFormats.Contains(outSettings.PixelFormat);
bool proresHqChroma = outSettings.Encoder == Enums.Encoding.Encoder.ProResKs && OutputUtils.AlphaFormats.Contains(outSettings.PixelFormat);
bool forceHqChroma = pngOutput || aviHqChroma || proresHqChroma;
bool tiffSupport = !ai.NameInternal.Upper().EndsWith("NCNN"); // NCNN binaries can't load TIFF (unlike OpenCV, ffmpeg etc)
string losslessExt = tiffSupport ? ".tiff" : ".png";
bool allowJpegImport = Config.GetBool(Config.Key.jpegFrames) && !(alpha || forceHqChroma); // Force PNG if alpha is enabled, or output is not 4:2:0 subsampled
bool allowJpegExport = Config.GetBool(Config.Key.jpegInterp) && !(alpha || forceHqChroma);
Logger.Log($"RefreshExtensions({type}) - alpha = {alpha} pngOutput = {pngOutput} aviHqChroma = {aviHqChroma} proresHqChroma = {proresHqChroma}", true);
if (type == FrameType.Both || type == FrameType.Import)
framesExt = allowJpegImport ? ".jpg" : losslessExt;
if (type == FrameType.Both || type == FrameType.Interp)
interpExt = allowJpegExport ? ".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.NameInternal}\n";
s += $"INFPSDETECTED|{inFpsDetected}\n";
s += $"INFPS|{inFps}\n";
s += $"OUTFPS|{outFps}\n";
s += $"INTERPFACTOR|{interpFactor}\n";
s += $"OUTMODE|{outSettings.Format}\n";
s += $"MODEL|{model.Name}\n";
s += $"INPUTRES|{InputResolution.Width}x{InputResolution.Height}\n";
s += $"OUTPUTRES|{ScaledResolution.Width}x{ScaledResolution.Height}\n";
s += $"ALPHA|{alpha}\n";
s += $"STEPBYSTEP|{stepByStep}\n";
s += $"FRAMESEXT|{framesExt}\n";
s += $"INTERPEXT|{interpExt}\n";
return s;
}
}
}

View File

@@ -0,0 +1,176 @@
using Flowframes.Data.Streams;
using Flowframes.Forms;
using Flowframes.IO;
using Flowframes.Main;
using Flowframes.Media;
using Flowframes.MiscUtils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Stream = Flowframes.Data.Streams.Stream;
namespace Flowframes.Data
{
public class MediaFile
{
public bool IsDirectory;
public FileInfo FileInfo;
public DirectoryInfo DirectoryInfo;
public string Name;
public string SourcePath;
public string ImportPath;
public string Format;
public string Title;
public string Language;
public Fraction? InputRate = null;
public long DurationMs;
public int StreamCount;
public int TotalKbits;
public long Size;
public List<Stream> AllStreams = new List<Stream>();
public List<VideoStream> VideoStreams = new List<VideoStream>();
public List<AudioStream> AudioStreams = new List<AudioStream>();
public List<SubtitleStream> SubtitleStreams = new List<SubtitleStream>();
public List<DataStream> DataStreams = new List<DataStream>();
public List<AttachmentStream> AttachmentStreams = new List<AttachmentStream>();
public VideoColorData ColorData = null;
public long CreationTime;
public bool Initialized = false;
public bool SequenceInitialized = false;
public int FileCount = 1;
public int FrameCount { get { return VideoStreams.Count > 0 ? VideoStreams[0].FrameCount : 0; } }
public MediaFile(string path, bool requestFpsInputIfUnset = true)
{
CreationTime = (long)(DateTime.Now - new DateTime(1970, 1, 1)).TotalMilliseconds; // Unix Timestamp as UID
if (IoUtils.IsPathDirectory(path))
{
IsDirectory = true;
DirectoryInfo = new DirectoryInfo(path);
Name = DirectoryInfo.Name;
SourcePath = DirectoryInfo.FullName;
Format = "Folder";
IoUtils.GetFileInfosSorted(SourcePath, false, "*.*").Take(1).ToList().ForEach(f => Format = f.Extension.Trim('.'));
if (requestFpsInputIfUnset && InputRate == null)
InputRate = InterpolateUtils.AskForFramerate(Name);
}
else
{
FileInfo = new FileInfo(path);
Name = FileInfo.Name;
SourcePath = FileInfo.FullName;
ImportPath = FileInfo.FullName;
Format = FileInfo.Extension.Remove(".").ToUpper();
InputRate = new Fraction(-1, 1);
}
Size = GetSize();
}
public async Task InitializeSequence()
{
try
{
if (SequenceInitialized) return;
string seqPath = Path.Combine(Paths.GetFrameSeqPath(), CreationTime.ToString(), "frames.concat");
string chosenExt = IoUtils.GetUniqueExtensions(SourcePath).FirstOrDefault();
int fileCount = FfmpegUtils.CreateConcatFile(SourcePath, seqPath, new List<string> { chosenExt });
ImportPath = seqPath;
FileCount = fileCount;
SequenceInitialized = true;
}
catch (Exception e)
{
Logger.Log($"Error preparing frame sequence: {e.Message}\n{e.StackTrace}");
FileCount = 0;
}
}
public async Task Initialize(bool progressBar = true, bool countFrames = true)
{
Logger.Log($"MediaFile {Name}: Initializing", true);
try
{
if (IsDirectory && !SequenceInitialized)
await InitializeSequence();
await LoadFormatInfo(ImportPath);
AllStreams = await FfmpegUtils.GetStreams(ImportPath, progressBar, StreamCount, InputRate, countFrames);
VideoStreams = AllStreams.Where(x => x.Type == Stream.StreamType.Video).Select(x => (VideoStream)x).ToList();
AudioStreams = AllStreams.Where(x => x.Type == Stream.StreamType.Audio).Select(x => (AudioStream)x).ToList();
SubtitleStreams = AllStreams.Where(x => x.Type == Stream.StreamType.Subtitle).Select(x => (SubtitleStream)x).ToList();
DataStreams = AllStreams.Where(x => x.Type == Stream.StreamType.Data).Select(x => (DataStream)x).ToList();
AttachmentStreams = AllStreams.Where(x => x.Type == Stream.StreamType.Attachment).Select(x => (AttachmentStream)x).ToList();
Logger.Log($"Loaded and sorted streams for {Name}", true);
}
catch (Exception e)
{
Logger.Log($"Failed to initialized MediaFile: {e.Message}", true);
}
Initialized = true;
}
private async Task LoadFormatInfo(string path)
{
Title = await GetVideoInfo.GetFfprobeInfoAsync(path, GetVideoInfo.FfprobeMode.ShowFormat, "TAG:title");
Language = await GetVideoInfo.GetFfprobeInfoAsync(path, GetVideoInfo.FfprobeMode.ShowFormat, "TAG:language");
DurationMs = (await FfmpegCommands.GetDurationMs(path));
StreamCount = await FfmpegUtils.GetStreamCount(path);
TotalKbits = (await GetVideoInfo.GetFfprobeInfoAsync(path, GetVideoInfo.FfprobeMode.ShowFormat, "bit_rate")).GetInt() / 1000;
}
public string GetName()
{
if (IsDirectory)
return DirectoryInfo.Name;
else
return FileInfo.Name;
}
public string GetPath()
{
if (IsDirectory)
return DirectoryInfo.FullName;
else
return FileInfo.FullName;
}
public long GetSize()
{
try
{
if (IsDirectory)
return IoUtils.GetDirSize(GetPath(), true);
else
return FileInfo.Length;
}
catch (Exception ex)
{
Logger.Log($"Failed to get file size of {FileInfo.FullName}: {ex.Message} (Path Length: {FileInfo.FullName.Length})", true);
return 0;
}
}
public bool CheckFiles()
{
if (IsDirectory)
return Directory.Exists(DirectoryInfo.FullName);
else
return File.Exists(FileInfo.FullName);
}
public override string ToString()
{
return $"{GetName()} ({FormatUtils.Bytes(Size)})";
}
}
}

View File

@@ -0,0 +1,77 @@
using Flowframes.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Flowframes.Data
{
public class ModelCollection
{
public AI Ai { get; set; } = null;
public List<ModelInfo> Models { get; set; } = new List<ModelInfo>();
public class ModelInfo
{
public AI Ai { get; set; } = null;
public string Name { get; set; } = "";
public string Desc { get; set; } = "";
public string Dir { get; set; } = "";
public bool SupportsAlpha { get; set; } = false;
public bool IsDefault { get; set; } = false;
private int[] _fixedFactors = null;
public int[] FixedFactors { get { return _fixedFactors == null ? new int[0] : _fixedFactors; } set { _fixedFactors = value; } }
public ModelInfo() { }
public string GetUiString()
{
return $"{Name} - {Desc}{(SupportsAlpha ? " (Supports Transparency)" : "")}{(FixedFactors.Count() > 0 ? $" ({GetFactorsString()})" : "")}{(IsDefault ? " (Recommended)" : "")}";
}
public string GetFactorsString ()
{
return string.Join(", ", FixedFactors.Select(x => $"{x}x"));
}
}
public ModelCollection(AI ai)
{
Ai = ai;
}
public ModelCollection(AI ai, string jsonContentOrPath)
{
Ai = ai;
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);
ModelInfo modelInfo = new ModelInfo()
{
Ai = ai,
Name = (string)item.name,
Desc = (string)item.desc,
Dir = (string)item.dir,
SupportsAlpha = alpha,
IsDefault = def,
FixedFactors = ((JArray)item.fixedFactors)?.Select(x => (int)x).ToArray(),
};
Models.Add(modelInfo);
}
}
}
}

View File

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

103
Flowframes/Data/Paths.cs Normal file
View File

@@ -0,0 +1,103 @@
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 sessionTimestamp;
public static void Init()
{
var n = DateTime.Now;
sessionTimestamp = $"{n.Year}-{n.Month}-{n.Day}-{n.Hour}-{n.Minute}-{n.Second}-{n.Millisecond}";
}
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 GetSessionsPath()
{
string path = Path.Combine(GetDataPath(), "sessions");
Directory.CreateDirectory(path);
return path;
}
public static string GetPkgPath()
{
string path = Path.Combine(GetDataPath(), "pkgs");
Directory.CreateDirectory(path);
return path;
}
public static string GetLogPath(bool noSession = false)
{
string path = Path.Combine(GetDataPath(), "logs", (noSession ? "" : sessionTimestamp));
Directory.CreateDirectory(path);
return path;
}
public static string GetSessionDataPath()
{
string path = Path.Combine(GetSessionsPath(), sessionTimestamp);
Directory.CreateDirectory(path);
return path;
}
public static string GetFrameSeqPath(bool noSession = false)
{
string path = Path.Combine((noSession ? GetDataPath() : GetSessionDataPath()), "frameSequences");
Directory.CreateDirectory(path);
return path;
}
}
}

View File

@@ -0,0 +1,16 @@
namespace Flowframes.Data
{
class QueryInfo
{
public string path;
public long filesize;
public string cmd = null;
public QueryInfo(string path, long filesize, string cmd = null)
{
this.path = path;
this.filesize = filesize;
this.cmd = cmd;
}
}
}

View File

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

View File

@@ -0,0 +1,77 @@
using Flowframes.IO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Flowframes.Data
{
class Servers
{
public static Server hetznerEu = new Server { name = "Germany (Nürnberg)", host = "nmkd-hz.de", pattern = "https://dl.*" };
public static Server contaboUs = new Server { name = "USA (St. Louis)", host = "nmkd-cb.de", pattern = "https://dl.*" };
public static List<Server> serverList = new List<Server> { hetznerEu, contaboUs };
private static Server closestServer = serverList[0];
public class Server
{
public string name = "";
public string host = "";
public string pattern = "*";
public string GetUrl()
{
return pattern.Replace("*", host);
}
}
public static async Task Init(ComboBox comboBox = null)
{
Dictionary<string[], long> serversPings = new Dictionary<string[], long>();
foreach (Server server in serverList)
{
try
{
Ping p = new Ping();
PingReply replyEur = p.Send(server.host, 2000);
serversPings[new string[] { server.name, server.host, server.pattern }] = replyEur.RoundtripTime;
Logger.Log($"[Servers] Ping to {server.host}: {replyEur.RoundtripTime} ms", true);
}
catch (Exception e)
{
Logger.Log($"[Servers] Failed to ping {server.host}: {e.Message}", true);
serversPings[new string[] { server.name, server.host, server.pattern }] = 10000;
}
}
var closest = serversPings.Aggregate((l, r) => l.Value < r.Value ? l : r);
Logger.Log($"[Servers] Closest Server: {closest.Key[0]} ({closest.Value} ms)", true);
closestServer = new Server { name = closest.Key[0], host = closest.Key[1], pattern = closest.Key[2] };
if (comboBox != null)
{
for (int i = 0; i < comboBox.Items.Count; i++)
{
if (comboBox.Items[i].ToString() == closestServer.name)
comboBox.SelectedIndex = i;
}
}
}
public static Server GetServer ()
{
int server = Config.GetInt("serverCombox");
if (server == 0)
return closestServer;
else
return serverList[server - 1];
}
}
}

View File

@@ -0,0 +1,22 @@
namespace Flowframes.Data.Streams
{
public class AttachmentStream : Stream
{
public string Filename { get; } = "";
public string MimeType { get; } = "";
public AttachmentStream(string codec, string codecLong, string filename, string mimeType)
{
base.Type = StreamType.Attachment;
Codec = codec;
CodecLong = codecLong;
Filename = filename;
MimeType = mimeType;
}
public override string ToString()
{
return $"{base.ToString()} - Filename: {Filename} - MIME Type: {MimeType}";
}
}
}

View File

@@ -0,0 +1,29 @@
namespace Flowframes.Data.Streams
{
public class AudioStream : Stream
{
public int Kbits { get; }
public int SampleRate { get; }
public int Channels { get; }
public string Layout { get; }
public AudioStream(string language, string title, string codec, string codecLong, int kbits, int sampleRate, int channels, string layout)
{
base.Type = StreamType.Audio;
Language = language;
Title = title;
Codec = codec;
CodecLong = codecLong;
Kbits = kbits;
SampleRate = sampleRate;
Channels = channels;
Layout = layout;
}
public override string ToString()
{
string title = string.IsNullOrWhiteSpace(Title.Trim()) ? "None" : Title;
return $"{base.ToString()} - Language: {Language} - Title: {title} - Kbps: {Kbits} - SampleRate: {SampleRate} - Channels: {Channels} - Layout: {Layout}";
}
}
}

View File

@@ -0,0 +1,17 @@
namespace Flowframes.Data.Streams
{
public class DataStream : Stream
{
public DataStream(string codec, string codecLong)
{
base.Type = StreamType.Data;
Codec = codec;
CodecLong = codecLong;
}
public override string ToString()
{
return $"{base.ToString()}";
}
}
}

View File

@@ -0,0 +1,19 @@
namespace Flowframes.Data.Streams
{
public class Stream
{
public enum StreamType { Video, Audio, Subtitle, Data, Attachment, Unknown }
public StreamType Type;
public int Index = -1;
public bool IsDefault = false;
public string Codec = "";
public string CodecLong = "";
public string Language = "";
public string Title = "";
public override string ToString()
{
return $"Stream #{Index.ToString().PadLeft(2, '0')}{(IsDefault ? "*" : "")} - {Codec} {Type}";
}
}
}

View File

@@ -0,0 +1,26 @@
using Flowframes.Extensions;
namespace Flowframes.Data.Streams
{
public class SubtitleStream : Stream
{
public bool Bitmap { get; }
public SubtitleStream(string language, string title, string codec, string codecLong, bool bitmap)
{
base.Type = StreamType.Subtitle;
Language = language;
Title = title;
Codec = codec;
CodecLong = codecLong;
Bitmap = bitmap;
}
public override string ToString()
{
string lang = string.IsNullOrWhiteSpace(Language.Trim()) ? "?" : Language;
string ttl = string.IsNullOrWhiteSpace(Title.Trim()) ? "None" : Title;
return $"{base.ToString()} - Language: {lang} - Title: {ttl} - Bitmap-based: {Bitmap.ToString().ToTitleCase()}";
}
}
}

View File

@@ -0,0 +1,37 @@
using System.Drawing;
namespace Flowframes.Data.Streams
{
public class VideoStream : Stream
{
public int FrameCount { get; } = 0;
public string PixelFormat { get; }
public int Kbits { get; }
public Size Resolution { get; }
public Size Sar { get; }
public Size Dar { get; }
public FpsInfo FpsInfo { get; }
public Fraction Rate { get => FpsInfo.Fps; }
public VideoStream(string language, string title, string codec, string codecLong, string pixFmt, int kbits, Size resolution, Size sar, Size dar, FpsInfo fpsInf, int frameCount)
{
base.Type = StreamType.Video;
Codec = codec;
CodecLong = codecLong;
PixelFormat = pixFmt;
Kbits = kbits;
Resolution = resolution;
Sar = sar;
Dar = dar;
FpsInfo = fpsInf;
FrameCount = frameCount;
Language = language;
Title = title;
}
public override string ToString()
{
return $"{base.ToString()} - Language: {Language} - Color Format: {PixelFormat} - Size: {Resolution.Width}x{Resolution.Height} - FPS: {Rate}";
}
}
}

View File

@@ -0,0 +1,94 @@
using System.Collections.Generic;
namespace Flowframes.Data
{
public class Strings
{
public static Dictionary<string, string> OutputFormat = new Dictionary<string, string>
{
{ Enums.Output.Format.Mp4.ToString(), "MP4" },
{ Enums.Output.Format.Mkv.ToString(), "MKV" },
{ Enums.Output.Format.Webm.ToString(), "WEBM" },
{ Enums.Output.Format.Mov.ToString(), "MOV" },
{ Enums.Output.Format.Avi.ToString(), "AVI" },
{ Enums.Output.Format.Gif.ToString(), "GIF" },
{ Enums.Output.Format.Images.ToString(), "Frames" },
{ Enums.Output.Format.Realtime.ToString(), "Realtime" },
};
public static Dictionary<string, string> Encoder = new Dictionary<string, string>
{
{ Enums.Encoding.Encoder.X264.ToString(), "h264" },
{ Enums.Encoding.Encoder.X265.ToString(), "h265" },
{ Enums.Encoding.Encoder.SvtAv1.ToString(), "AV1" },
{ Enums.Encoding.Encoder.VpxVp9.ToString(), "VP9" },
{ Enums.Encoding.Encoder.ProResKs.ToString(), "ProRes" },
{ Enums.Encoding.Encoder.Nvenc264.ToString(), "h264 NVENC" },
{ Enums.Encoding.Encoder.Nvenc265.ToString(), "h265 NVENC" },
{ Enums.Encoding.Encoder.NvencAv1.ToString(), "AV1 NVENC" },
{ Enums.Encoding.Encoder.Amf264.ToString(), "h264 AMF" },
{ Enums.Encoding.Encoder.Amf265.ToString(), "h265 AMF" },
{ Enums.Encoding.Encoder.Qsv264.ToString(), "h264 QuickSync" },
{ Enums.Encoding.Encoder.Qsv265.ToString(), "h265 QuickSync" },
{ Enums.Encoding.Encoder.Gif.ToString(), "GIF" },
{ Enums.Encoding.Encoder.Png.ToString(), "PNG" },
{ Enums.Encoding.Encoder.Jpeg.ToString(), "JPEG" },
{ Enums.Encoding.Encoder.Webp.ToString(), "WEBP" },
{ Enums.Encoding.Encoder.Tiff.ToString(), "TIFF" },
{ Enums.Encoding.Encoder.Exr.ToString(), "EXR" },
{ Enums.Encoding.Encoder.Ffv1.ToString(), "FFV1" },
{ Enums.Encoding.Encoder.Huffyuv.ToString(), "HuffYUV" },
{ Enums.Encoding.Encoder.Magicyuv.ToString(), "MagicYUV" },
{ Enums.Encoding.Encoder.Rawvideo.ToString(), "Raw Video" },
};
public static Dictionary<string, string> PixelFormat = new Dictionary<string, string>
{
{ Enums.Encoding.PixelFormat.Yuv420P.ToString(), "YUV 4:2:0 8-bit" },
{ Enums.Encoding.PixelFormat.Yuva420P.ToString(), "YUVA 4:2:0 8-bit" },
{ Enums.Encoding.PixelFormat.Yuv420P10Le.ToString(), "YUV 4:2:0 10-bit" },
{ Enums.Encoding.PixelFormat.Yuv422P.ToString(), "YUV 4:2:2 8-bit" },
{ Enums.Encoding.PixelFormat.Yuv422P10Le.ToString(), "YUV 4:2:2 10-bit" },
{ Enums.Encoding.PixelFormat.Yuv444P.ToString(), "YUV 4:4:4 8-bit" },
{ Enums.Encoding.PixelFormat.Yuv444P10Le.ToString(), "YUV 4:4:4 10-bit" },
{ Enums.Encoding.PixelFormat.Yuva444P10Le.ToString(), "YUVA 4:4:4 10-bit" },
{ Enums.Encoding.PixelFormat.Yuv444P12Le.ToString(), "YUV 4:4:4 12-bit" },
{ Enums.Encoding.PixelFormat.Yuv444P16Le.ToString(), "YUV 4:4:4 16-bit" },
{ Enums.Encoding.PixelFormat.Rgb24.ToString(), "RGB 8-bit" },
{ Enums.Encoding.PixelFormat.Rgba.ToString(), "RGBA 8-bit" },
{ Enums.Encoding.PixelFormat.Rgb48Le.ToString(), "RGB 12-bit LE" },
{ Enums.Encoding.PixelFormat.Rgb48Be.ToString(), "RGB 12-bit BE" },
{ Enums.Encoding.PixelFormat.Rgba64Le.ToString(), "RGBA 16-bit LE" },
{ Enums.Encoding.PixelFormat.Rgba64Be.ToString(), "RGBA 16-bit BE" },
{ Enums.Encoding.PixelFormat.Pal8.ToString(), "256-color Palette" },
{ Enums.Encoding.PixelFormat.Gbrpf32Le.ToString(), "RGB 32-bit Float" },
{ Enums.Encoding.PixelFormat.Gbrapf32Le.ToString(), "RGBA 32-bit Float" },
};
public static Dictionary<string, string> VideoQuality = new Dictionary<string, string>
{
{ Enums.Encoding.Quality.Common.Lossless.ToString(), "Lossless" },
{ Enums.Encoding.Quality.Common.VeryHigh.ToString(), "Very High" },
{ Enums.Encoding.Quality.Common.High.ToString(), "High" },
{ Enums.Encoding.Quality.Common.Medium.ToString(), "Medium" },
{ Enums.Encoding.Quality.Common.Low.ToString(), "Low" },
{ Enums.Encoding.Quality.Common.VeryLow.ToString(), "Very Low" },
{ Enums.Encoding.Quality.ProResProfile.Proxy.ToString(), "Proxy" },
{ Enums.Encoding.Quality.ProResProfile.Lt.ToString(), "LT" },
{ Enums.Encoding.Quality.ProResProfile.Standard.ToString(), "Standard" },
{ Enums.Encoding.Quality.ProResProfile.Hq.ToString(), "HQ" },
{ Enums.Encoding.Quality.ProResProfile.Quad4.ToString(), "4444" },
{ Enums.Encoding.Quality.ProResProfile.Quad4Xq.ToString(), "4444 XQ" },
{ Enums.Encoding.Quality.GifColors.Max256.ToString(), "Max (256)" },
{ Enums.Encoding.Quality.GifColors.High128.ToString(), "High (128)" },
{ Enums.Encoding.Quality.GifColors.Medium64.ToString(), "Medium (64)" },
{ Enums.Encoding.Quality.GifColors.Low32.ToString(), "Low (32)" },
{ Enums.Encoding.Quality.GifColors.VeryLow16.ToString(), "Very Low (16)" },
{ Enums.Encoding.Quality.JpegWebm.ImgMax.ToString(), "Maximum" },
{ Enums.Encoding.Quality.JpegWebm.ImgHigh.ToString(), "High" },
{ Enums.Encoding.Quality.JpegWebm.ImgMed.ToString(), "Medium" },
{ Enums.Encoding.Quality.JpegWebm.ImgLow.ToString(), "Low" },
{ Enums.Encoding.Quality.JpegWebm.ImgLowest.ToString(), "Lowest" },
};
}
}

View File

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

View File

@@ -0,0 +1,123 @@
using Flowframes.IO;
using System;
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 () { }
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: Ignoring invalid color space '{colorSpace.Trim()}'.", true, false, "ffmpeg");
colorSpace = "";
}
if (colorRange.Trim() == "unknown")
colorRange = "";
if (!validColorSpaces.Contains(colorTransfer.Trim()))
{
Logger.Log($"Warning: Color Transfer '{colorTransfer.Trim()}' not valid.", true, false, "ffmpeg");
colorTransfer = "";
}
else
{
colorTransfer = colorTransfer.Replace("bt470bg", "gamma28").Replace("bt470m", "gamma28"); // https://forum.videohelp.com/threads/394596-Color-Matrix
}
if (!validColorSpaces.Contains(colorPrimaries.Trim()))
{
Logger.Log($"Warning: Color Primaries '{colorPrimaries.Trim()}' not valid.", true, false, "ffmpeg");
colorPrimaries = "";
}
}
public bool HasAnyValues ()
{
if (!string.IsNullOrWhiteSpace(colorSpace))
return true;
if (!string.IsNullOrWhiteSpace(colorRange))
return true;
if (!string.IsNullOrWhiteSpace(colorTransfer))
return true;
if (!string.IsNullOrWhiteSpace(colorPrimaries))
return true;
return false;
}
public bool HasAllValues()
{
if (string.IsNullOrWhiteSpace(colorSpace))
return false;
if (string.IsNullOrWhiteSpace(colorRange))
return false;
if (string.IsNullOrWhiteSpace(colorTransfer))
return false;
if (string.IsNullOrWhiteSpace(colorPrimaries))
return false;
return true;
}
}
}

View File

@@ -0,0 +1,54 @@
using Flowframes.Utilities;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Data
{
public class VideoColorData
{
public int ColorTransfer { get; set; } = 2;
public int ColorMatrixCoeffs { get; set; } = 2;
public int ColorPrimaries { get; set; } = 2;
public int ColorRange { get; set; } = 0;
public string RedX { get; set; } = "";
public string RedY { get; set; } = "";
public string GreenX { get; set; } = "";
public string GreenY { get; set; } = "";
public string BlueX { get; set; } = "";
public string BlueY { get; set; } = "";
public string WhiteX { get; set; } = "";
public string WhiteY { get; set; } = "";
public string LumaMin { get; set; } = "";
public string LumaMax { get; set; } = "";
public string MaxCll { get; set; } = "";
public string MaxFall { get; set; } = "";
public override string ToString()
{
List<string> lines = new List<string>();
try
{
lines.Add($"Color transfer: {ColorTransfer} ({ColorDataUtils.GetColorTransferName(ColorTransfer)})");
lines.Add($"Colour matrix coefficients: {ColorMatrixCoeffs} ({ColorDataUtils.GetColorMatrixCoeffsName(ColorMatrixCoeffs)})");
lines.Add($"Colour primaries: {ColorPrimaries} ({ColorDataUtils.GetColorPrimariesName(ColorPrimaries)})");
lines.Add($"Colour range: {ColorRange} ({ColorDataUtils.GetColorRangeName(ColorRange)})");
if (!string.IsNullOrWhiteSpace(RedX) && !string.IsNullOrWhiteSpace(RedY)) lines.Add($"Red color coordinates X/Y: {RedX}/{RedY}");
if (!string.IsNullOrWhiteSpace(GreenX) && !string.IsNullOrWhiteSpace(GreenY)) lines.Add($"Green color coordinates X/Y: {GreenX}/{GreenY}");
if (!string.IsNullOrWhiteSpace(BlueX) && !string.IsNullOrWhiteSpace(BlueY)) lines.Add($"Blue color coordinates X/Y: {BlueX}/{BlueY}");
if (!string.IsNullOrWhiteSpace(WhiteX) && !string.IsNullOrWhiteSpace(WhiteY)) lines.Add($"White color coordinates X/Y: {WhiteX}/{WhiteY}");
if (!string.IsNullOrWhiteSpace(LumaMin)) lines.Add($"Minimum luminance: {LumaMin}");
if (!string.IsNullOrWhiteSpace(LumaMax)) lines.Add($"Maximum luminance: {LumaMax}");
if (!string.IsNullOrWhiteSpace(MaxCll)) lines.Add($"Maximum Content Light Level: {MaxCll}");
if (!string.IsNullOrWhiteSpace(MaxFall)) lines.Add($"Maximum Frame-Average Light Level: {MaxFall}");
}
catch { }
return string.Join("\n", lines);
}
}
}

View File

@@ -0,0 +1,409 @@
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.Drawing;
using Flowframes.MiscUtils;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json;
using Win32Interop.Structs;
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 == null || str.Length < 1 || str.Contains("\n") || str == "N/A" || !str.Any(char.IsDigit))
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 == null || str.Length < 1)
return 0f;
string num = str.TrimNumbers(true).Replace(",", ".");
float.TryParse(num, out float 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 float Clamp(this float i, float min, float max)
{
if (i < min)
i = min;
if (i > max)
i = max;
return i;
}
public static string[] SplitIntoLines(this string str)
{
if (string.IsNullOrWhiteSpace(str))
return new string[0];
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 "";
return str.Replace(stringToRemove, "");
}
public static string TrimWhitespaces(this string str)
{
if (str == null) return "";
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 int RoundMod(this int n, int mod = 2) // Round to a number that's divisible by 2 (for h264 etc)
{
int a = (n / 2) * 2; // Smaller multiple
int b = a + 2; // Larger multiple
return (n - a > b - n) ? b : a; // Return of closest of two
}
public static string ToTitleCase(this string s)
{
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(s);
}
public static string ToStringShort(this Size s, string separator = "x")
{
return $"{s.Width}{separator}{s.Height}";
}
public static bool IsConcatFile(this string filePath)
{
try
{
return Path.GetExtension(filePath)?.ToLowerInvariant() == ".concat";
}
catch
{
return false;
}
}
public static string GetConcStr(this string filePath, int rate = -1)
{
string rateStr = rate >= 0 ? $"-r {rate} " : "";
return filePath.IsConcatFile() ? $"{rateStr}-safe 0 -f concat " : "";
}
public static string GetFfmpegInputArg(this string filePath)
{
return $"{(filePath.IsConcatFile() ? filePath.GetConcStr() : "")} -i {filePath.Wrap()}";
}
public static string Get(this Dictionary<string, string> dict, string key, bool returnKeyInsteadOfEmptyString = false, bool ignoreCase = false)
{
if (key == null)
key = "";
for (int i = 0; i < dict.Count; i++)
{
if (ignoreCase)
{
if (key.Lower() == dict.ElementAt(i).Key.Lower())
return dict.ElementAt(i).Value;
}
else
{
if (key == dict.ElementAt(i).Key)
return dict.ElementAt(i).Value;
}
}
if (returnKeyInsteadOfEmptyString)
return key;
else
return "";
}
public static void FillFromEnum<TEnum>(this ComboBox comboBox, Dictionary<string, string> stringMap = null, int defaultIndex = -1, List<TEnum> exclusionList = null) where TEnum : Enum
{
if (exclusionList == null)
exclusionList = new List<TEnum>();
var entriesToAdd = Enum.GetValues(typeof(TEnum)).Cast<TEnum>().Except(exclusionList);
var strings = entriesToAdd.Select(x => stringMap.Get(x.ToString(), true));
comboBox.FillFromStrings(strings, stringMap, defaultIndex);
}
public static void FillFromEnum<TEnum>(this ComboBox comboBox, IEnumerable<TEnum> entries, Dictionary<string, string> stringMap = null, int defaultIndex = -1) where TEnum : Enum
{
var strings = entries.Select(x => stringMap.Get(x.ToString(), true));
comboBox.FillFromStrings(strings, stringMap, defaultIndex);
}
public static void FillFromEnum<TEnum>(this ComboBox comboBox, IEnumerable<TEnum> entries, Dictionary<string, string> stringMap, TEnum defaultEntry) where TEnum : Enum
{
if (stringMap == null)
stringMap = new Dictionary<string, string>();
comboBox.Items.Clear();
comboBox.Items.AddRange(entries.Select(x => stringMap.Get(x.ToString(), true)).ToArray());
comboBox.Text = stringMap.Get(defaultEntry.ToString(), true);
}
public static void FillFromStrings(this ComboBox comboBox, IEnumerable<string> entries, Dictionary<string, string> stringMap = null, int defaultIndex = -1, IEnumerable<string> exclusionList = null)
{
if (stringMap == null)
stringMap = new Dictionary<string, string>();
if (exclusionList == null)
exclusionList = new List<string>();
comboBox.Items.Clear();
comboBox.Items.AddRange(entries.Select(x => stringMap.Get(x, true)).Except(exclusionList).ToArray());
if (defaultIndex >= 0 && comboBox.Items.Count > 0)
comboBox.SelectedIndex = defaultIndex;
}
public static void SetIfTextMatches(this ComboBox comboBox, string str, bool ignoreCase = true, Dictionary<string, string> stringMap = null)
{
if (stringMap == null)
stringMap = new Dictionary<string, string>();
str = stringMap.Get(str, true, true);
for (int i = 0; i < comboBox.Items.Count; i++)
{
if (ignoreCase)
{
if (comboBox.Items[i].ToString().Lower() == str.Lower())
{
comboBox.SelectedIndex = i;
return;
}
}
else
{
if (comboBox.Items[i].ToString() == str)
{
comboBox.SelectedIndex = i;
return;
}
}
}
}
public static string Lower(this string s)
{
if (s == null)
return s;
return s.ToLowerInvariant();
}
public static string Upper(this string s)
{
if (s == null)
return s;
return s.ToUpperInvariant();
}
public static EncoderInfoVideo GetInfo (this Enums.Encoding.Encoder enc)
{
return OutputUtils.GetEncoderInfoVideo(enc);
}
public static bool IsEmpty (this string s)
{
return string.IsNullOrWhiteSpace(s);
}
public static bool IsNotEmpty(this string s)
{
return !string.IsNullOrWhiteSpace(s);
}
public static string ToJson(this object o, bool indent = false, bool ignoreErrors = true)
{
var settings = new JsonSerializerSettings();
if (ignoreErrors)
settings.Error = (s, e) => { e.ErrorContext.Handled = true; };
// Serialize enums as strings.
settings.Converters.Add(new StringEnumConverter());
return JsonConvert.SerializeObject(o, indent ? Formatting.Indented : Formatting.None, settings);
}
}
}

View File

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

View File

@@ -0,0 +1,48 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AdamsLair.WinForms" Version="1.1.18" />
<PackageReference Include="AdamsLair.WinForms.PopupControl" Version="1.0.1" />
<PackageReference Include="CircularProgressBar" Version="2.8.0.16" />
<PackageReference Include="Crc32.NET" Version="1.2.0" />
<PackageReference Include="CyotekTabList" Version="2.0.0" />
<PackageReference Include="diskdetector-net" Version="0.3.2" />
<PackageReference Include="HTAlt.Standart" Version="0.1.6" />
<PackageReference Include="HTAlt.WinForms" Version="0.1.6" />
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="7.21.1" />
<PackageReference Include="Magick.NET.Core" Version="4.1.0" />
<PackageReference Include="NDesk.Options" Version="0.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NvAPIWrapper.Net" Version="0.8.1.101" />
<PackageReference Include="PagedControl" Version="2.2.0" />
<PackageReference Include="System.Management" Version="8.0.0" />
<PackageReference Include="TabControl" Version="2.1.2" />
<PackageReference Include="Tulpep.NotificationWindow" Version="1.1.38" />
<PackageReference Include="VulkanSharp" Version="0.1.10" />
<PackageReference Include="Win32Interop.Dwmapi" Version="1.0.1" />
<PackageReference Include="Win32Interop.Gdi32" Version="1.0.1" />
<PackageReference Include="Win32Interop.Kernel32" Version="1.0.1" />
<PackageReference Include="Win32Interop.User32" Version="1.0.1" />
<PackageReference Include="Win32Interop.Uxtheme" Version="1.0.1" />
<PackageReference Include="WindowsAPICodePack-Core" Version="1.1.1" />
<PackageReference Include="WindowsAPICodePack-Shell" Version="1.1.1" />
<PackageReference Include="WinFormAnimation" Version="1.6.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\nmkd-utils\NmkdUtils\NmkdUtils.csproj" />
</ItemGroup>
<PropertyGroup>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
</Project>

31
Flowframes/Flowframes.sln Normal file
View File

@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.35122.118
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flowframes", "Flowframes.csproj", "{AA0C3B3E-824B-4298-9F3A-05C4FAF08404}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NmkdUtils", "..\..\nmkd-utils\NmkdUtils\NmkdUtils.csproj", "{213246BA-7BF7-411F-A786-7641FFB19CC4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AA0C3B3E-824B-4298-9F3A-05C4FAF08404}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA0C3B3E-824B-4298-9F3A-05C4FAF08404}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA0C3B3E-824B-4298-9F3A-05C4FAF08404}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA0C3B3E-824B-4298-9F3A-05C4FAF08404}.Release|Any CPU.Build.0 = Release|Any CPU
{213246BA-7BF7-411F-A786-7641FFB19CC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{213246BA-7BF7-411F-A786-7641FFB19CC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{213246BA-7BF7-411F-A786-7641FFB19CC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{213246BA-7BF7-411F-A786-7641FFB19CC4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {08B3FCDB-6273-415E-99BD-E99219BA0A16}
EndGlobalSection
EndGlobal

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

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

View File

@@ -0,0 +1,185 @@
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 outFormat = Strings.OutputFormat.Get(entry.outSettings.Format.ToString());
string inPath = string.IsNullOrWhiteSpace(entry.inPath) ? "No Path" : Path.GetFileName(entry.inPath).Trunc(40);
string str = $"#{i+1}: {inPath} - {entry.inFps.GetFloat()} FPS => {entry.interpFactor}x {entry.ai.NameShort} ({entry.model.Name}) => {outFormat}";
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;
if(current.inFps.GetFloat() <= 0)
current.inFps = InterpolateUtils.AskForFramerate(new DirectoryInfo(path).Name, false);
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);
}
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,64 @@
using Flowframes.Ui;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace Flowframes.Forms
{
public class CustomForm : Form
{
public Control FocusedControl { get { return this.GetControls().Where(c => c.Focused).FirstOrDefault(); } }
public bool AllowTextboxTab { get; set; } = true;
public bool AllowEscClose { get; set; } = true;
private List<Control> _tabOrderedControls;
public void TabOrderInit(List<Control> tabOrderedControls, int defaultFocusIndex = 0)
{
_tabOrderedControls = tabOrderedControls;
this.GetControls().ForEach(control => control.TabStop = false);
if (defaultFocusIndex >= 0 && tabOrderedControls != null && tabOrderedControls.Count > 0)
tabOrderedControls[defaultFocusIndex].Focus();
}
public void TabOrderNext()
{
if (_tabOrderedControls == null || _tabOrderedControls.Count <= 0)
return;
var focused = FocusedControl;
if (_tabOrderedControls.Contains(focused))
{
int index = _tabOrderedControls.IndexOf(focused);
Control next = null;
while (_tabOrderedControls.Where(x => x.Visible && x.Enabled).Any() && (next == null || !next.Visible || !next.Enabled))
{
index++;
next = _tabOrderedControls.ElementAt(index >= _tabOrderedControls.Count ? 0 : index);
}
if (next != null)
next.Focus();
return;
}
_tabOrderedControls.First().Focus();
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Escape && AllowEscClose)
Close();
if (keyData == Keys.Tab && !(FocusedControl is TextBox && AllowTextboxTab))
TabOrderNext();
return base.ProcessCmdKey(ref msg, keyData);
}
}
}

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

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

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

View File

@@ -0,0 +1,109 @@
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 = UiUtils.ShowMessageBox($"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;
}
}
}

File diff suppressed because it is too large Load Diff

1572
Flowframes/Forms/Main/Form1.Designer.cs generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
using Flowframes.Data;
using Flowframes.Main;
using Flowframes.MiscUtils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Forms.Main
{
partial class Form1
{
Enums.Output.Format OutputFormat { get { return ParseUtils.GetEnum<Enums.Output.Format>(comboxOutputFormat.Text, true, Strings.OutputFormat); } }
Enums.Encoding.Encoder CurrentEncoder
{
get
{
if (comboxOutputEncoder.Visible)
return ParseUtils.GetEnum<Enums.Encoding.Encoder>(comboxOutputEncoder.Text, true, Strings.Encoder);
else
return (Enums.Encoding.Encoder)(-1);
}
}
Enums.Encoding.PixelFormat CurrentPixFmt
{
get
{
if (comboxOutputColors.Visible)
return ParseUtils.GetEnum<Enums.Encoding.PixelFormat>(comboxOutputColors.Text, true, Strings.PixelFormat);
else
return (Enums.Encoding.PixelFormat)(-1);
}
}
public ModelCollection.ModelInfo GetModel(AI currentAi)
{
try
{
return AiModels.GetModels(currentAi).Models[aiModel.SelectedIndex];
}
catch
{
return null;
}
}
public AI GetAi()
{
try
{
foreach (AI ai in Implementations.NetworksAll)
{
if (GetAiComboboxName(ai) == aiCombox.Text)
return ai;
}
return Implementations.NetworksAvailable[0];
}
catch
{
return null;
}
}
public OutputSettings GetOutputSettings()
{
string custQ = textboxOutputQualityCust.Visible ? textboxOutputQualityCust.Text.Trim() : "";
return new OutputSettings() { Encoder = CurrentEncoder, Format = OutputFormat, PixelFormat = CurrentPixFmt, Quality = comboxOutputQuality.Text, CustomQuality = custQ };
}
}
}

View File

@@ -0,0 +1,860 @@
using Flowframes.Forms;
using Flowframes.IO;
using Flowframes.Main;
using Flowframes.Os;
using Flowframes.Ui;
using Microsoft.WindowsAPICodePack.Dialogs;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using HTAlt.WinForms;
using Flowframes.Data;
using Microsoft.WindowsAPICodePack.Taskbar;
using Flowframes.MiscUtils;
using System.Threading.Tasks;
using System.Linq;
using System.Runtime.InteropServices;
#pragma warning disable IDE1006
namespace Flowframes.Forms.Main
{
public partial class Form1 : Form
{
[Flags]
public enum EXECUTION_STATE : uint { ES_AWAYMODE_REQUIRED = 0x00000040, ES_CONTINUOUS = 0x80000000, ES_DISPLAY_REQUIRED = 0x00000002, ES_SYSTEM_REQUIRED = 0x00000001 }
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags); // PREVENT WINDOWS FROM GOING TO SLEEP
private bool _initialized = false;
private bool _mainTabInitialized = false;
private bool _quickSettingsInitialized = false;
public Form1()
{
InitializeComponent();
}
private async void Form1_Load(object sender, EventArgs e)
{
CheckForIllegalCrossThreadCalls = false;
AutoScaleMode = AutoScaleMode.None;
}
private async void Form1_Shown(object sender, EventArgs e)
{
Refresh();
await Task.Delay(1);
StartupChecks.CheckOs();
// Main Tab
UiUtils.InitCombox(interpFactorCombox, 0);
UiUtils.InitCombox(outSpeedCombox, 0);
UiUtils.InitCombox(aiModel, 2);
// Video Utils
UiUtils.InitCombox(trimCombox, 0);
Program.mainForm = this;
Logger.textbox = logBox;
VulkanUtils.Init();
NvApi.Init();
InterpolationProgress.preview = previewPicturebox;
RemovePreviewIfDisabled();
await Checks();
InitOutputUi();
InitAis();
UpdateStepByStepControls();
Initialized();
HandleArgs();
if (Program.args.Contains("show-model-downloader"))
new ModelDownloadForm().ShowDialog();
if (Debugger.IsAttached)
{
Logger.Log("Debugger is attached - Flowframes seems to be running within VS.");
}
completionAction.SelectedIndex = 0;
}
private void InitOutputUi()
{
comboxOutputFormat.FillFromEnum<Enums.Output.Format>(Strings.OutputFormat, 0);
UpdateOutputUi();
if (Debugger.IsAttached)
{
Logger.Log($"Formats: {string.Join(", ", Enum.GetValues(typeof(Enums.Output.Format)).Cast<Enums.Output.Format>().Select(e => Strings.OutputFormat.Get(e.ToString())))}", true);
Logger.Log($"Encoders: {string.Join(", ", Enum.GetValues(typeof(Enums.Encoding.Encoder)).Cast<Enums.Encoding.Encoder>().Select(e => Strings.Encoder.Get(e.ToString())))}", true);
Logger.Log($"Pixel Formats: {string.Join(", ", Enum.GetValues(typeof(Enums.Encoding.PixelFormat)).Cast<Enums.Encoding.PixelFormat>().Select(e => Strings.PixelFormat.Get(e.ToString())))}", true);
}
}
public async void ResetOutputUi()
{
comboxOutputEncoder.Items.Clear();
Config.Set(Config.Key.PerformedHwEncCheck, false.ToString());
await StartupChecks.DetectHwEncoders();
UpdateOutputUi();
}
private void UpdateOutputUi()
{
var outMode = ParseUtils.GetEnum<Enums.Output.Format>(comboxOutputFormat.Text, true, Strings.OutputFormat);
comboxOutputEncoder.FillFromEnum(OutputUtils.GetAvailableEncoders(outMode), Strings.Encoder, 0);
comboxOutputEncoder.Visible = comboxOutputEncoder.Items.Count > 0;
UpdateOutputEncodingUi();
}
private void UpdateOutputEncodingUi()
{
var infoStrings = new List<string>() { "Format" };
var encoder = ParseUtils.GetEnum<Enums.Encoding.Encoder>(comboxOutputEncoder.Text, true, Strings.Encoder);
bool hasEncoder = (int)encoder != -1;
comboxOutputQuality.Visible = hasEncoder;
comboxOutputColors.Visible = hasEncoder;
if (hasEncoder)
{
infoStrings.Add("Codec");
EncoderInfoVideo info = OutputUtils.GetEncoderInfoVideo(encoder);
bool adjustableQuality = !(info.Lossless == true) && info.QualityLevels != null && info.QualityLevels.Count > 0;
comboxOutputQuality.Visible = adjustableQuality;
comboxOutputQuality.Items.Clear();
if (info.QualityLevels.Count > 0)
{
infoStrings.Add("Quality");
var exclude = info.Lossless == null ? new List<string> { Enums.Encoding.Quality.Common.Lossless.ToString() } : null;
comboxOutputQuality.FillFromStrings(info.QualityLevels, Strings.VideoQuality, info.QualityDefault, exclude);
}
var pixelFormats = info.PixelFormats;
comboxOutputColors.Visible = pixelFormats.Count > 0;
int defaultPixFmt = (int)info.PixelFormatDefault != -1 ? info.PixelFormats.IndexOf(info.PixelFormatDefault) : 0;
comboxOutputColors.FillFromEnum(pixelFormats, Strings.PixelFormat, defaultPixFmt);
comboxOutputColors.Width = adjustableQuality ? 117 : 223;
if (pixelFormats.Count > 0)
infoStrings.Add("Pixel Format");
}
labelOutput.Text = $"Set {string.Join(", ", infoStrings)}";
}
async Task Checks()
{
try
{
Task.Run(() => Updater.UpdateModelList());
Task.Run(() => Updater.AsyncUpdateCheck());
Task.Run(() => GetWebInfo.LoadNews(newsLabel));
Task.Run(() => GetWebInfo.LoadPatronListCsv(patronsLabel));
Task.Run(() => Servers.Init());
await Python.CheckCompression();
await StartupChecks.SymlinksCheck();
await StartupChecks.DetectHwEncoders();
}
catch (Exception e)
{
Logger.Log("Non-critical error while performing online checks. See logs for details.");
Logger.Log($"{e.Message}\n{e.StackTrace}", true);
}
}
void HandleArgs()
{
if (Program.fileArgs.Length > 0)
DragDropHandler(Program.fileArgs.Where(x => IoUtils.IsFileValid(x)).ToArray());
foreach (string arg in Program.args)
{
if (arg.StartsWith("out="))
outputTbox.Text = arg.Split('=').Last().Trim();
if (arg.StartsWith("factor="))
{
float factor = arg.Split('=').Last().GetFloat();
interpFactorCombox.Text = factor.ToString();
}
if (arg.StartsWith("ai="))
aiCombox.SelectedIndex = arg.Split('=').Last().GetInt();
if (arg.StartsWith("model="))
aiModel.SelectedIndex = arg.Split('=').Last().GetInt();
if (arg.StartsWith("output-mode="))
comboxOutputFormat.SelectedIndex = arg.Split('=').Last().GetInt();
}
}
void RemovePreviewIfDisabled()
{
Logger.Log($"TODO: Fix RemovePreviewIfDisabled");
return;
if (Config.GetBool(Config.Key.disablePreview))
mainTabControl.TabPages.Cast<TabPage>().Where(p => p.Name == previewTab.Name).ToList().ForEach(t => mainTabControl.TabPages.Remove(t));
}
public HTTabControl GetMainTabControl() { return mainTabControl; }
public TextBox GetInputFpsTextbox() { return fpsInTbox; }
public Button GetPauseBtn() { return pauseBtn; }
public bool IsInFocus() { return ActiveForm == this; }
public void SetTab(string tabName)
{
var targetTab = mainTabControl.TabPages.Cast<TabPage>().Where(p => p.Name == tabName).FirstOrDefault();
if (targetTab == null)
return;
mainTabControl.SelectedTab = targetTab;
mainTabControl.Refresh();
mainTabControl.Update();
}
public InterpSettings GetCurrentSettings()
{
SetTab(interpOptsTab.Name);
AI ai = GetAi();
var s = new InterpSettings()
{
inPath = inputTbox.Text.Trim(),
outPath = outputTbox.Text.Trim(),
ai = ai,
inFpsDetected = currInFpsDetected,
inFps = currInFps,
interpFactor = interpFactorCombox.GetFloat(),
outItsScale = outSpeedCombox.GetInt().Clamp(1, 64),
outSettings = GetOutputSettings(),
model = GetModel(ai),
};
s.InitArgs();
return s;
}
public InterpSettings UpdateCurrentSettings(InterpSettings settings)
{
SetTab(interpOptsTab.Name);
string inPath = inputTbox.Text.Trim();
if (settings.inPath != inPath) // If path changed, get new instance
{
Logger.Log($"settings.inPath ({settings.inPath}) mismatches GUI inPath ({settings.inPath} - Returning fresh instance", true);
return GetCurrentSettings();
}
settings.inPath = inPath;
settings.ai = GetAi();
settings.inFpsDetected = currInFpsDetected;
settings.inFps = currInFps;
settings.interpFactor = interpFactorCombox.GetFloat();
settings.outFps = settings.inFps * settings.interpFactor;
settings.outSettings = GetOutputSettings();
settings.model = GetModel(GetAi());
return settings;
}
public void LoadBatchEntry(InterpSettings entry)
{
inputTbox.Text = entry.inPath;
MainUiFunctions.SetOutPath(outputTbox, entry.outPath);
fpsInTbox.Text = entry.inFps.ToString();
interpFactorCombox.Text = entry.interpFactor.ToString();
aiCombox.SelectedIndex = Implementations.NetworksAvailable.IndexOf(Implementations.NetworksAvailable.Where(x => x.NameInternal == entry.ai.NameInternal).FirstOrDefault());
SetFormat(entry.outSettings.Format);
}
public void SetStatus(string str)
{
Logger.Log(str, true);
statusLabel.Text = str;
}
public string GetStatus()
{
return statusLabel.Text;
}
public void SetProgress(int percent)
{
percent = percent.Clamp(0, 100);
TaskbarManager.Instance.SetProgressValue(percent, 100);
longProgBar.Value = percent;
longProgBar.Refresh();
}
public Size currInRes;
public Fraction currInFpsDetected;
public Fraction currInFps;
public int currInFrames;
public long currInDuration;
public long currInDurationCut;
public void UpdateInputInfo()
{
string str = $"Size: {(!currInRes.IsEmpty ? $"{currInRes.Width}x{currInRes.Height}" : "Unknown")} - ";
str += $"FPS: {(currInFpsDetected.GetFloat() > 0f ? FormatUtils.Fraction(currInFpsDetected) : "Unknown")} - ";
str += $"Frames: {(currInFrames > 0 ? $"{currInFrames}" : "Unknown")} - ";
str += $"Duration: {(currInDuration > 0 ? FormatUtils.MsToTimestamp(currInDuration) : "Unknown")}";
inputInfo.Text = str;
}
public void InterpolationDone()
{
SetStatus("Done interpolating!");
if (!BatchProcessing.busy)
CompletionAction();
}
public void CompletionAction()
{
if (Program.args.Contains("quit-when-done"))
Application.Exit();
if (completionAction.SelectedIndex == 1)
new TimeoutForm(completionAction.Text, Application.Exit).ShowDialog();
if (completionAction.SelectedIndex == 2)
new TimeoutForm(completionAction.Text, OsUtils.Sleep).ShowDialog();
if (completionAction.SelectedIndex == 3)
new TimeoutForm(completionAction.Text, OsUtils.Hibernate).ShowDialog();
if (completionAction.SelectedIndex == 4)
new TimeoutForm(completionAction.Text, OsUtils.Shutdown).ShowDialog();
}
public void ResetInputInfo()
{
currInRes = new Size();
currInFpsDetected = new Fraction();
currInFps = new Fraction();
currInFrames = 0;
currInDuration = 0;
currInDurationCut = 0;
UpdateInputInfo();
}
void InitAis()
{
bool pytorchAvailable = Python.IsPytorchReady();
foreach (AI ai in Implementations.NetworksAvailable)
aiCombox.Items.Add(GetAiComboboxName(ai));
string lastUsedAiName = Config.Get(Config.Key.lastUsedAiName);
aiCombox.SelectedIndex = Implementations.NetworksAvailable.IndexOf(Implementations.NetworksAvailable.Where(x => x.NameInternal == lastUsedAiName).FirstOrDefault());
if (aiCombox.SelectedIndex < 0) aiCombox.SelectedIndex = 0;
Config.Set(Config.Key.lastUsedAiName, GetAi().NameInternal);
}
private string GetAiComboboxName(AI ai)
{
return ai.FriendlyName + " - " + ai.Description;
}
private void InitializeMainTab()
{
if (_mainTabInitialized)
return;
LoadOutputSettings();
_mainTabInitialized = true;
}
public void Initialized()
{
_initialized = true;
runBtn.Enabled = true;
SetStatus("Ready");
}
private void browseInputBtn_Click(object sender, EventArgs e)
{
CommonOpenFileDialog dialog = new CommonOpenFileDialog { InitialDirectory = inputTbox.Text.Trim(), IsFolderPicker = true };
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
DragDropHandler(new string[] { dialog.FileName });
}
private void browseInputFileBtn_Click(object sender, EventArgs e)
{
CommonOpenFileDialog dialog = new CommonOpenFileDialog { InitialDirectory = inputTbox.Text.Trim(), IsFolderPicker = false };
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
DragDropHandler(new string[] { dialog.FileName });
}
private void browseOutBtn_Click(object sender, EventArgs e)
{
CommonOpenFileDialog dialog = new CommonOpenFileDialog { InitialDirectory = outputTbox.Text.Trim(), IsFolderPicker = true };
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
outputTbox.Text = dialog.FileName;
}
public async void runBtn_Click(object sender, EventArgs e)
{
if (Interpolate.currentMediaFile == null || !Interpolate.currentMediaFile.Initialized)
{
SetTab(interpOptsTab.Name);
return;
}
ValidateFactor();
if (!BatchProcessing.busy) // Don't load values from GUI if batch processing is used
Interpolate.currentSettings = GetCurrentSettings();
AiProcessSuspend.Reset();
SaveOutputSettings();
if (Interpolate.currentSettings.outSettings.Format == Enums.Output.Format.Realtime)
{
await Interpolate.Realtime();
SetProgress(0);
}
else
{
await Interpolate.Start();
}
}
private void SaveOutputSettings()
{
var strings = new List<string>();
if (comboxOutputFormat.Visible) strings.Add(comboxOutputFormat.Text);
if (comboxOutputEncoder.Visible) strings.Add(comboxOutputEncoder.Text);
if (comboxOutputQuality.Visible) strings.Add(comboxOutputQuality.Text);
if (comboxOutputColors.Visible) strings.Add(comboxOutputColors.Text);
Config.Set(Config.Key.lastOutputSettings, string.Join(",", strings));
}
private void LoadOutputSettings()
{
string[] strings = Config.Get(Config.Key.lastOutputSettings).Split(',');
if (strings.Length < 4)
return;
if (comboxOutputFormat.Visible) comboxOutputFormat.Text = strings[0];
if (comboxOutputEncoder.Visible) comboxOutputEncoder.Text = strings[1];
if (comboxOutputQuality.Visible) comboxOutputQuality.Text = strings[2];
if (comboxOutputColors.Visible) comboxOutputColors.Text = strings[3];
}
public void SetFormat(Enums.Output.Format format)
{
comboxOutputFormat.Text = Strings.OutputFormat.Get(format.ToString());
}
void inputTbox_DragEnter(object sender, DragEventArgs e) { e.Effect = DragDropEffects.Copy; }
private void inputTbox_DragDrop(object sender, DragEventArgs e)
{
DragDropHandler((string[])e.Data.GetData(DataFormats.FileDrop));
}
void outputTbox_DragEnter(object sender, DragEventArgs e) { e.Effect = DragDropEffects.Copy; }
private void outputTbox_DragDrop(object sender, DragEventArgs e)
{
if (Program.busy) return;
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
outputTbox.Text = files[0];
}
private void fpsInTbox_TextChanged(object sender, EventArgs e)
{
UpdateUiFps();
}
public void UpdateUiFps()
{
if (fpsInTbox.Text.Contains("/")) // Parse fraction
{
string[] split = fpsInTbox.Text.Split('/');
Fraction frac = new Fraction(split[0].GetInt(), split[1].GetInt());
fpsOutTbox.Text = (frac * interpFactorCombox.GetFloat()).ToString() + " FPS";
if (!fpsInTbox.ReadOnly)
currInFps = frac;
}
else // Parse float
{
fpsInTbox.Text = fpsInTbox.Text.TrimNumbers(true);
fpsOutTbox.Text = (fpsInTbox.GetFloat() * interpFactorCombox.GetFloat()).ToString() + " FPS";
if (!fpsInTbox.ReadOnly)
currInFps = new Fraction(fpsInTbox.GetFloat());
}
}
private void interpFactorCombox_SelectedIndexChanged(object sender, EventArgs e)
{
UpdateUiFps();
}
public void ValidateFactor()
{
interpFactorCombox.Text = $"x{MainUiFunctions.ValidateInterpFactor(interpFactorCombox.GetFloat())}";
}
public void SetWorking(bool state, bool allowCancel = true)
{
Logger.Log($"SetWorking({state})", true);
SetProgress(-1);
Control[] controlsToDisable = new Control[] { runBtn, runStepBtn, stepSelector, settingsBtn };
Control[] controlsToHide = new Control[] { runBtn, runStepBtn, stepSelector };
progressCircle.Visible = state;
busyControlsPanel.Visible = state;
foreach (Control c in controlsToDisable)
c.Enabled = !state;
foreach (Control c in controlsToHide)
c.Visible = !state;
busyControlsPanel.Enabled = allowCancel;
Program.busy = state;
Program.mainForm.UpdateStepByStepControls();
}
string lastAiComboxStr = "";
private void aiCombox_SelectedIndexChanged(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(aiCombox.Text) || aiCombox.Text == lastAiComboxStr) return;
lastAiComboxStr = aiCombox.Text;
UpdateAiModelCombox();
interpFactorCombox.Items.Clear();
foreach (int factor in GetAi().SupportedFactors)
interpFactorCombox.Items.Add($"x{factor}");
interpFactorCombox.SelectedIndex = 0;
if (_initialized)
Config.Set(Config.Key.lastUsedAiName, GetAi().NameInternal);
interpFactorCombox_SelectedIndexChanged(null, null);
fpsOutTbox.ReadOnly = GetAi().FactorSupport != AI.InterpFactorSupport.AnyFloat;
}
public void UpdateAiModelCombox()
{
aiModel = UiUtils.LoadAiModelsIntoGui(aiModel, GetAi());
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (!Program.busy && !BackgroundTaskManager.IsBusy())
return;
string reason = "";
if (BackgroundTaskManager.IsBusy())
reason = "Some background tasks have not finished yet.";
if (Program.busy)
reason = "The program is still busy.";
DialogResult dialog = UiUtils.ShowMessageBox($"Are you sure you want to exit the program?\n\n{reason}", "Are you sure?", MessageBoxButtons.YesNo);
if (dialog == DialogResult.No)
e.Cancel = true;
Program.Cleanup();
}
private void licenseBtn_Click(object sender, EventArgs e)
{
Process.Start("explorer.exe", Path.Combine(Paths.GetPkgPath(), Paths.licensesDir));
}
private void Form1_DragEnter(object sender, DragEventArgs e) { e.Effect = DragDropEffects.Copy; }
private void Form1_DragDrop(object sender, DragEventArgs e)
{
DragDropHandler((string[])e.Data.GetData(DataFormats.FileDrop));
}
public void DragDropHandler(string[] files)
{
if (Program.busy) return;
bool start = Program.initialRun && Program.args.Contains("start");
if (files.Length > 1)
{
SetTab(interpOptsTab.Name);
queueBtn_Click(null, null);
if (BatchProcessing.currentBatchForm != null)
BatchProcessing.currentBatchForm.LoadDroppedPaths(files, start);
}
else
{
SetTab(interpOptsTab.Name);
Logger.Log("Selected video/directory: " + Path.GetFileName(files[0]), true);
inputTbox.Text = files[0];
bool resume = Directory.Exists(files[0]) && IoUtils.GetAmountOfFiles(Path.Combine(files[0], Paths.resumeDir), true, "*.json") > 0;
AutoEncodeResume.resumeNextRun = resume;
if (resume)
AutoEncodeResume.LoadTempFolder(files[0]);
trimCombox.SelectedIndex = 0;
MainUiFunctions.InitInput(outputTbox, inputTbox, fpsInTbox, start);
}
}
private void cancelBtn_Click(object sender, EventArgs e)
{
DialogResult dialog = UiUtils.ShowMessageBox($"Are you sure you want to cancel the interpolation?", "Are you sure?", MessageBoxButtons.YesNo);
if (dialog == DialogResult.Yes)
{
SetTab(interpOptsTab.Name);
Interpolate.Cancel();
}
}
private void discordBtn_Click(object sender, EventArgs e)
{
Process.Start("https://discord.gg/eJHD2NSJRe");
}
private void paypalBtn_Click(object sender, EventArgs e)
{
Process.Start("https://www.paypal.com/paypalme/nmkd/10");
}
private void patreonBtn_Click(object sender, EventArgs e)
{
Process.Start("https://patreon.com/n00mkrad");
}
private void settingsBtn_Click(object sender, EventArgs e)
{
new SettingsForm().ShowDialog();
}
private void queueBtn_Click(object sender, EventArgs e)
{
ValidateFactor();
if (BatchProcessing.currentBatchForm != null)
{
BatchProcessing.currentBatchForm.WindowState = FormWindowState.Normal;
BatchProcessing.currentBatchForm.BringToFront();
}
else
{
new BatchForm().Show();
}
}
private void previewPicturebox_MouseClick(object sender, MouseEventArgs e)
{
if (InterpolationProgress.bigPreviewForm == null)
{
InterpolationProgress.bigPreviewForm = new BigPreviewForm();
InterpolationProgress.bigPreviewForm.Show();
InterpolationProgress.bigPreviewForm.SetImage(previewPicturebox.Image);
}
}
private async void updateBtn_Click(object sender, EventArgs e)
{
new UpdaterForm().ShowDialog();
}
private void welcomeLabel2_Click(object sender, EventArgs e)
{
SetTab(interpOptsTab.Name);
}
public void UpdateStepByStepControls()
{
if (stepSelector.SelectedIndex < 0)
stepSelector.SelectedIndex = 0;
bool stepByStep = Config.GetInt(Config.Key.processingMode) == 1;
runBtn.Visible = !stepByStep && !Program.busy;
}
private async void runStepBtn_Click(object sender, EventArgs e)
{
ValidateFactor();
SetTab(interpOptsTab.Name);
await InterpolateSteps.Run(stepSelector.Text);
}
private void mainTabControl_SelectedIndexChanged(object sender, EventArgs e)
{
if (!_initialized) return;
if (mainTabControl.SelectedTab == interpOptsTab)
{
aiCombox_SelectedIndexChanged(null, null);
InitializeMainTab();
}
}
private void trimCombox_SelectedIndexChanged(object sender, EventArgs e)
{
QuickSettingsTab.trimEnabled = trimCombox.SelectedIndex > 0;
trimPanel.Visible = QuickSettingsTab.trimEnabled;
if (trimCombox.SelectedIndex == 1)
{
trimStartBox.Text = "00:00:00";
trimEndBox.Text = FormatUtils.MsToTimestamp(currInDuration);
}
}
private void trimResetBtn_Click(object sender, EventArgs e)
{
trimCombox_SelectedIndexChanged(null, null);
}
private void trimBox_TextChanged(object sender, EventArgs e)
{
QuickSettingsTab.UpdateTrim(trimStartBox, trimEndBox);
}
#region Quick Settings
public void SaveQuickSettings(object sender, EventArgs e)
{
if (!_quickSettingsInitialized) return;
if (Program.busy)
LoadQuickSettings(); // Discard any changes if busy
ConfigParser.SaveGuiElement(maxVidHeight, ConfigParser.StringMode.Int);
ConfigParser.SaveComboxIndex(dedupMode);
ConfigParser.SaveComboxIndex(mpdecimateMode);
ConfigParser.SaveGuiElement(dedupThresh);
ConfigParser.SaveGuiElement(enableLoop);
ConfigParser.SaveGuiElement(scnDetect);
ConfigParser.SaveGuiElement(scnDetectValue);
ConfigParser.SaveGuiElement(maxFps);
}
public void LoadQuickSettings(object sender = null, EventArgs e = null)
{
ConfigParser.LoadGuiElement(maxVidHeight);
ConfigParser.LoadComboxIndex(dedupMode);
ConfigParser.LoadComboxIndex(mpdecimateMode);
ConfigParser.LoadGuiElement(dedupThresh);
ConfigParser.LoadGuiElement(enableLoop);
ConfigParser.LoadGuiElement(scnDetect);
ConfigParser.LoadGuiElement(scnDetectValue);
ConfigParser.LoadGuiElement(maxFps);
_quickSettingsInitialized = true;
}
private void dedupMode_SelectedIndexChanged(object sender, EventArgs e)
{
dedupeSensLabel.Visible = dedupMode.SelectedIndex != 0;
magickDedupePanel.Visible = dedupMode.SelectedIndex == 1;
mpDedupePanel.Visible = dedupMode.SelectedIndex == 2;
SaveQuickSettings(null, null);
}
private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
if (Program.busy) return;
new SettingsForm().ShowDialog();
}
#endregion
private void pauseBtn_Click(object sender, EventArgs e)
{
AiProcessSuspend.SuspendResumeAi(!AiProcessSuspend.aiProcFrozen);
}
private void debugBtn_Click(object sender, EventArgs e)
{
new DebugForm().ShowDialog();
}
private void fpsOutTbox_Leave(object sender, EventArgs e)
{
float inFps = fpsInTbox.GetFloat();
float outFps = fpsOutTbox.GetFloat();
var targetFactorRounded = Math.Round((Decimal)(outFps / inFps), 3, MidpointRounding.AwayFromZero);
interpFactorCombox.Text = $"{targetFactorRounded}";
ValidateFactor();
fpsOutTbox.Text = $"{inFps * interpFactorCombox.GetFloat()} FPS";
}
private void aiInfoBtn_Click(object sender, EventArgs e)
{
var ai = GetAi();
if (ai != null)
UiUtils.ShowMessageBox(ai.GetVerboseInfo(), UiUtils.MessageType.Message);
}
private void comboxOutputFormat_SelectedIndexChanged(object sender, EventArgs e)
{
UpdateOutputUi();
}
private void comboxOutputEncoder_SelectedIndexChanged(object sender, EventArgs e)
{
UpdateOutputEncodingUi();
}
private void queueBtn_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
menuStripQueue.Show(Cursor.Position);
}
private void addCurrentConfigurationToQueueToolStripMenuItem_Click(object sender, EventArgs e)
{
Program.batchQueue.Enqueue(Program.mainForm.GetCurrentSettings());
Application.OpenForms.Cast<Form>().Where(f => f is BatchForm).Select(f => (BatchForm)f).ToList().ForEach(f => f.RefreshGui());
}
private void comboxOutputQuality_SelectedIndexChanged(object sender, EventArgs e)
{
var qualityPreset = ParseUtils.GetEnum<Enums.Encoding.Quality.Common>(comboxOutputQuality.Text, true, Strings.VideoQuality);
bool cust = qualityPreset == Enums.Encoding.Quality.Common.Custom;
textboxOutputQualityCust.Visible = cust;
comboxOutputQuality.Margin = new System.Windows.Forms.Padding(0, 0, cust ? 0 : 6, 0);
comboxOutputQuality.Width = cust ? 70 : 100;
if (!cust)
textboxOutputQualityCust.Text = "";
else
textboxOutputQualityCust.Focus();
Refresh();
}
}
}

File diff suppressed because it is too large Load Diff

122
Flowframes/Forms/MessageForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,122 @@
namespace Flowframes.Forms
{
partial class MessageForm
{
/// <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.textLabel = new System.Windows.Forms.Label();
this.btn1 = new HTAlt.WinForms.HTButton();
this.btn2 = new HTAlt.WinForms.HTButton();
this.btn3 = new HTAlt.WinForms.HTButton();
this.SuspendLayout();
//
// textLabel
//
this.textLabel.AutoSize = true;
this.textLabel.ForeColor = System.Drawing.Color.White;
this.textLabel.Location = new System.Drawing.Point(15, 15);
this.textLabel.Margin = new System.Windows.Forms.Padding(8, 0, 3, 0);
this.textLabel.Name = "textLabel";
this.textLabel.Size = new System.Drawing.Size(0, 13);
this.textLabel.TabIndex = 8;
//
// btn1
//
this.btn1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btn1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.btn1.FlatAppearance.BorderSize = 0;
this.btn1.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btn1.ForeColor = System.Drawing.Color.White;
this.btn1.Location = new System.Drawing.Point(232, 126);
this.btn1.Name = "btn1";
this.btn1.Size = new System.Drawing.Size(100, 23);
this.btn1.TabIndex = 46;
this.btn1.Text = "OK";
this.btn1.UseVisualStyleBackColor = false;
this.btn1.Click += new System.EventHandler(this.btn1_Click);
//
// btn2
//
this.btn2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btn2.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.btn2.FlatAppearance.BorderSize = 0;
this.btn2.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btn2.ForeColor = System.Drawing.Color.White;
this.btn2.Location = new System.Drawing.Point(126, 126);
this.btn2.Name = "btn2";
this.btn2.Size = new System.Drawing.Size(100, 23);
this.btn2.TabIndex = 47;
this.btn2.Text = "OK";
this.btn2.UseVisualStyleBackColor = false;
this.btn2.Click += new System.EventHandler(this.btn2_Click);
//
// btn3
//
this.btn3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btn3.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.btn3.FlatAppearance.BorderSize = 0;
this.btn3.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btn3.ForeColor = System.Drawing.Color.White;
this.btn3.Location = new System.Drawing.Point(20, 126);
this.btn3.Name = "btn3";
this.btn3.Size = new System.Drawing.Size(100, 23);
this.btn3.TabIndex = 48;
this.btn3.Text = "OK";
this.btn3.UseVisualStyleBackColor = false;
this.btn3.Click += new System.EventHandler(this.btn3_Click);
//
// MessageForm
//
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.btn3);
this.Controls.Add(this.btn2);
this.Controls.Add(this.btn1);
this.Controls.Add(this.textLabel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "MessageForm";
this.ShowIcon = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MessageForm_FormClosing);
this.Load += new System.EventHandler(this.MessageForm_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label textLabel;
private HTAlt.WinForms.HTButton btn1;
private HTAlt.WinForms.HTButton btn2;
private HTAlt.WinForms.HTButton btn3;
}
}

View File

@@ -0,0 +1,116 @@
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 MessageForm : Form
{
private string _text = "";
private string _title = "";
private MessageBoxButtons _btns;
private bool _dialogResultSet = false;
public MessageForm(string text, string title, MessageBoxButtons buttons = MessageBoxButtons.OK)
{
_text = text;
_title = title;
_btns = buttons;
InitializeComponent();
}
private void MessageForm_Load(object sender, EventArgs e)
{
Text = _title;
textLabel.Text = _text;
if(_btns == MessageBoxButtons.OK)
{
SetButtons(true, false, false);
btn1.Text = "OK";
AcceptButton = btn1;
}
else if(_btns == MessageBoxButtons.YesNo)
{
SetButtons(true, true, false);
btn1.Text = "No";
btn2.Text = "Yes";
AcceptButton = btn2;
CancelButton = btn1;
}
else if (_btns == MessageBoxButtons.YesNoCancel)
{
SetButtons(true, true, true);
btn1.Text = "Cancel";
btn2.Text = "No";
btn3.Text = "Yes";
AcceptButton = btn3;
CancelButton = btn1;
}
Size labelSize = GetLabelSize(textLabel);
Size = new Size((labelSize.Width + 60).Clamp(360, Program.mainForm.Size.Width), (labelSize.Height + 120).Clamp(200, Program.mainForm.Size.Height));
CenterToScreen();
}
private Size GetLabelSize(Label label)
{
return TextRenderer.MeasureText(label.Text, label.Font, label.ClientSize, TextFormatFlags.WordBreak | TextFormatFlags.TextBoxControl);
}
private void SetButtons(bool b1, bool b2, bool b3)
{
btn1.Visible = b1;
btn2.Visible = b2;
btn3.Visible = b3;
}
private void btn1_Click(object sender, EventArgs e)
{
if (_btns == MessageBoxButtons.OK) // OK Button
DialogResult = DialogResult.OK;
else if (_btns == MessageBoxButtons.YesNo) // No Button
DialogResult = DialogResult.No;
else if (_btns == MessageBoxButtons.YesNoCancel) // Cancel Button
DialogResult = DialogResult.Cancel;
_dialogResultSet = true;
Close();
}
private void btn2_Click(object sender, EventArgs e)
{
if (_btns == MessageBoxButtons.YesNo) // Yes Button
DialogResult = DialogResult.Yes;
else if (_btns == MessageBoxButtons.YesNoCancel) // No Button
DialogResult = DialogResult.No;
_dialogResultSet = true;
Close();
}
private void btn3_Click(object sender, EventArgs e)
{
if (_btns == MessageBoxButtons.YesNoCancel) // Yes Button
DialogResult = DialogResult.Yes;
_dialogResultSet = true;
Close();
}
private void MessageForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (_btns != MessageBoxButtons.OK && !_dialogResultSet)
e.Cancel = true;
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,279 @@

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.xvfiCuda = 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;
//
// xvfiCuda
//
this.xvfiCuda.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.xvfiCuda.AutoSize = true;
this.xvfiCuda.Checked = true;
this.xvfiCuda.CheckState = System.Windows.Forms.CheckState.Checked;
this.xvfiCuda.Location = new System.Drawing.Point(382, 203);
this.xvfiCuda.Name = "xvfiCuda";
this.xvfiCuda.Size = new System.Drawing.Size(82, 17);
this.xvfiCuda.TabIndex = 99;
this.xvfiCuda.Text = "XVFI CUDA";
this.xvfiCuda.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.xvfiCuda);
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;
private System.Windows.Forms.CheckBox xvfiCuda;
}
}

View File

@@ -0,0 +1,85 @@
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;
bool xvfiC = xvfiCuda.Checked;
ModelDownloadFormUtils.DownloadModels(rifeC, rifeN, dainN, flavrC, xvfiC);
}
private void cancelBtn_Click(object sender, EventArgs e)
{
ModelDownloadFormUtils.Cancel();
}
private void closeBtn_Click(object sender, EventArgs e)
{
ModelDownloadFormUtils.Cancel();
Close();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
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 PromptForm : Form
{
public string EnteredText { get; set; }
public PromptForm(string title, string message, string defaultText)
{
InitializeComponent();
Text = title;
msgLabel.Text = message;
textBox.Text = defaultText;
AcceptButton = confirmBtn;
}
private void PromptForm_Load(object sender, EventArgs e)
{
}
private void confirmBtn_Click(object sender, EventArgs e)
{
EnteredText = textBox.Text.Trim();
DialogResult = DialogResult.OK;
Close();
Program.mainForm.BringToFront();
}
}
}

102
Flowframes/Forms/PromptForm.designer.cs generated Normal file
View File

@@ -0,0 +1,102 @@

namespace Flowframes.Forms
{
partial class PromptForm
{
/// <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(PromptForm));
this.msgLabel = new System.Windows.Forms.Label();
this.textBox = new System.Windows.Forms.TextBox();
this.confirmBtn = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// msgLabel
//
this.msgLabel.ForeColor = System.Drawing.Color.White;
this.msgLabel.Location = new System.Drawing.Point(13, 13);
this.msgLabel.Margin = new System.Windows.Forms.Padding(4, 4, 4, 11);
this.msgLabel.Name = "msgLabel";
this.msgLabel.Size = new System.Drawing.Size(318, 30);
this.msgLabel.TabIndex = 18;
this.msgLabel.Text = "The Text Here";
this.msgLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter;
//
// textBox
//
this.textBox.AllowDrop = true;
this.textBox.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.textBox.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.textBox.ForeColor = System.Drawing.Color.White;
this.textBox.Location = new System.Drawing.Point(16, 57);
this.textBox.MinimumSize = new System.Drawing.Size(4, 23);
this.textBox.Name = "textBox";
this.textBox.Size = new System.Drawing.Size(315, 21);
this.textBox.TabIndex = 55;
//
// confirmBtn
//
this.confirmBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.confirmBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.confirmBtn.FlatStyle = System.Windows.Forms.FlatStyle.Popup;
this.confirmBtn.Location = new System.Drawing.Point(12, 106);
this.confirmBtn.Name = "confirmBtn";
this.confirmBtn.Size = new System.Drawing.Size(320, 23);
this.confirmBtn.TabIndex = 56;
this.confirmBtn.Text = "OK";
this.confirmBtn.UseVisualStyleBackColor = false;
this.confirmBtn.Click += new System.EventHandler(this.confirmBtn_Click);
//
// PromptForm
//
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, 141);
this.Controls.Add(this.confirmBtn);
this.Controls.Add(this.textBox);
this.Controls.Add(this.msgLabel);
this.ForeColor = System.Drawing.Color.White;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "PromptForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Prompt";
this.Load += new System.EventHandler(this.PromptForm_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label msgLabel;
private System.Windows.Forms.TextBox textBox;
private System.Windows.Forms.Button confirmBtn;
}
}

File diff suppressed because it is too large Load Diff

1979
Flowframes/Forms/SettingsForm.Designer.cs generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,254 @@
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.Media;
using Flowframes.MiscUtils;
using Flowframes.Ui;
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());
InitServers();
LoadSettings();
initialized = true;
Task.Run(() => CheckModelCacheSize());
}
void InitServers()
{
serverCombox.Items.Clear();
serverCombox.Items.Add($"Automatic (Closest)");
foreach (Servers.Server srv in Servers.serverList)
serverCombox.Items.Add(srv.name);
serverCombox.SelectedIndex = 0;
}
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 ()
{
// 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(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(loopMode);
ConfigParser.SaveGuiElement(fixOutputDuration);
// Debugging
ConfigParser.SaveComboxIndex(cmdDebugMode);
ConfigParser.SaveComboxIndex(serverCombox);
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(keepTempFolder);
ConfigParser.LoadGuiElement(exportNamePattern);
ConfigParser.LoadGuiElement(exportNamePatternLoop);
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(loopMode);
ConfigParser.LoadGuiElement(fixOutputDuration);
// Debugging
ConfigParser.LoadComboxIndex(cmdDebugMode);
ConfigParser.LoadComboxIndex(serverCombox);
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)
UiUtils.ShowMessageBox("If you enable this, you need to close the CMD window manually after the process has finished, otherwise processing will be paused!", UiUtils.MessageType.Warning);
}
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();
}
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 = UiUtils.ShowMessageBox($"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);
}
private void btnResetHwEnc_Click(object sender, EventArgs e)
{
Close();
Program.mainForm.ResetOutputUi();
}
}
}

File diff suppressed because it is too large Load Diff

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

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

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

View File

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

File diff suppressed because it is too large Load Diff

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

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

369
Flowframes/IO/Config.cs Normal file
View File

@@ -0,0 +1,369 @@
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
{
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).", true);
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 Get(key, Type.Float).GetFloat();
}
public static float GetFloat(Key key, float defaultVal)
{
WriteIfDoesntExist(key.ToString(), defaultVal.ToStringDot());
return Get(key, Type.Float).GetFloat();
}
public static float GetFloat(string key)
{
return Get(key, Type.Float).GetFloat();
}
public static float GetFloat(string key, float defaultVal)
{
WriteIfDoesntExist(key.ToString(), defaultVal.ToStringDot());
return Get(key, Type.Float).GetFloat();
}
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.disablePreview) return WriteDefault(key, "True");
if (key == Key.maxVidHeight) return WriteDefault(key, "2160");
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.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.gifDitherType) return WriteDefault(key, "bayer");
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, "4");
if (key == Key.dainNcnnTilesize) return WriteDefault(key, "768");
// Debug / Other / Experimental
if (key == Key.ffEncPreset) return WriteDefault(key, "fast");
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,
aiCombox,
allowConsecutiveSceneChanges,
allowCustomInputRate,
allowOpusInMp4,
allowSymlinkEncoding,
allowSymlinkImport,
alwaysWaitForAutoEnc,
askedForDevModeVersion,
autoEncBackupMode,
autoEncDebug,
autoEncMode,
autoEncSafeBufferCuda,
autoEncSafeBufferNcnn,
clearLogOnInput,
cmdDebugMode,
compressedPyVersion,
customServer,
dainNcnnTilesize,
dedupMode,
dedupThresh,
disablePreview,
dupeScanDebug,
enableLoop,
exportNamePattern,
exportNamePatternLoop,
fetchModelsFromRepo,
ffEncArgs,
ffEncPreset,
ffEncThreads,
ffprobeFrameCount,
fixOutputDuration,
frameOrderDebug,
gifDitherType,
imgSeqSampleCount,
jpegFrames,
jpegInterp,
keepAspectRatio,
keepAudio,
keepColorSpace,
keepMeta,
keepSubs,
keepTempFolder,
lastUsedAiName,
loopMode,
lowDiskSpaceCancelGb,
lowDiskSpacePauseGb,
maxFps,
maxFpsMode,
maxVidHeight,
minOutVidLength,
minVidLength,
mpdecimateMode,
ncnnGpus,
ncnnThreads,
opusBitrate,
processingMode,
rifeCudaBufferSize,
rifeCudaFp16,
rifeNcnnUseTta,
sbsAllowAutoEnc,
sbsRunPreviousStepIfNeeded,
sceneChangeFillMode,
scnDetect,
scnDetectValue,
silentDevmodeCheck,
tempDirCustom,
tempFolderLoc,
torchGpus,
uhdThresh,
vsRtShowOsd,
vsUseLsmash,
lastOutputSettings,
PerformedHwEncCheck,
SupportedHwEncoders,
}
}
}

View File

@@ -0,0 +1,76 @@
using System;
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);
}
}
}

1046
Flowframes/IO/IoUtils.cs Normal file

File diff suppressed because it is too large Load Diff

193
Flowframes/IO/Logger.cs Normal file
View File

@@ -0,0 +1,193 @@
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;
private static Dictionary<string, string> sessionLogs = new Dictionary<string, string>();
private static string _lastUi = "";
public static string LastUiLine { get { return _lastUi; } }
private static string _lastLog = "";
public static string LastLogLine { get { return _lastLog; } }
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;
string msg = entry.logMessage;
if (msg == LastUiLine)
entry.hidden = true; // Never show the same line twice in UI, but log it to file
_lastLog = msg;
if (!entry.hidden)
_lastUi = msg;
Console.WriteLine(msg);
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 { }
msg = msg.Replace("\n", Environment.NewLine);
if (!entry.hidden && textbox != null)
textbox.AppendText((textbox.Text.Length > 1 ? Environment.NewLine : "") + msg);
if (entry.replaceLastLine)
{
textbox.Resume();
msg = "[REPL] " + msg;
}
if (!entry.hidden)
msg = "[UI] " + msg;
LogToFile(msg, 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 = DateTime.Now.ToString("MM-dd-yyyy HH:mm:ss");
try
{
string appendStr = noLineBreak ? $" {logStr}" : $"{Environment.NewLine}[{id.ToString().PadLeft(8, '0')}] [{time}]: {logStr}";
sessionLogs[filename] = (sessionLogs.ContainsKey(filename) ? sessionLogs[filename] : "") + appendStr;
File.AppendAllText(file, appendStr);
id++;
}
catch
{
// this if fine, i forgot why
}
}
public static string GetSessionLog(string filename)
{
if (!filename.Contains(".txt"))
filename = Path.ChangeExtension(filename, "txt");
if (sessionLogs.ContainsKey(filename))
return sessionLogs[filename];
else
return "";
}
public static List<string> GetSessionLogLastLines(string filename, int linesCount = 5)
{
string log = GetSessionLog(filename);
string[] lines = log.SplitIntoLines();
return lines.Reverse().Take(linesCount).Reverse().ToList();
}
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(bool includeHidden = false)
{
return includeHidden ? _lastLog : _lastUi;
}
public static void RemoveLastLine()
{
textbox.Text = textbox.Text.Remove(textbox.Text.LastIndexOf(Environment.NewLine));
}
}
}

View File

@@ -0,0 +1,287 @@
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 custServer = Config.Get(Config.Key.customServer);
string server = custServer.Trim().Length > 3 ? custServer : Servers.GetServer().GetUrl();
string baseUrl = $"{server}/flowframes/mdl/";
return Path.Combine(baseUrl, ai.ToLowerInvariant(), 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);
if (data == null)
return modelFiles;
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.NameInternal}, 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);
string remoteDir = aiDir.EndsWith("-ncnn-vs") ? aiDir.Remove(aiDir.Length - 3) : aiDir;
await DownloadTo(GetMdlFileUrl(remoteDir, 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(remoteDir, 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.NetworksAll)
{
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;
}
}
}

96
Flowframes/IO/Symlinks.cs Normal file
View File

@@ -0,0 +1,96 @@
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
Flowframes/Magick/Blend.cs Normal file
View File

@@ -0,0 +1,166 @@
using Flowframes.Data;
using Flowframes.MiscUtils;
using ImageMagick;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Flowframes.IO;
using Padding = Flowframes.Data.Padding;
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...");
string[] frames = FrameRename.framesAreRenamed ? new string[0] : IoUtils.GetFilesSorted(Interpolate.currentSettings.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[] values = trimmedLine.Split('>');
string frameFrom = FrameRename.framesAreRenamed ? values[0] : frames[values[0].GetInt()];
string frameTo = FrameRename.framesAreRenamed ? values[1] : frames[values[1].GetInt()];
int amountOfBlendFrames = values.Count() == 3 ? values[2].GetInt() : (int)Interpolate.currentSettings.interpFactor - 1;
string img1 = Path.Combine(Interpolate.currentSettings.framesFolder, frameFrom);
string img2 = Path.Combine(Interpolate.currentSettings.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.currentSettings.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 {values[0]} > {values[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}\n{e.StackTrace}", 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);
}
}
}
}

View File

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

313
Flowframes/Magick/Dedupe.cs Normal file
View File

@@ -0,0 +1,313 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;
using Flowframes.IO;
using ImageMagick;
using Newtonsoft.Json;
using Flowframes.Os;
using System.Windows.Controls;
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));
}
static MagickImage GetImage(string path)
{
return new MagickImage(path);
}
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 statsFramesKept = framePaths.Length > 0 ? 1 : 0; // always keep at least one frame
int statsFramesDeleted = 0;
Mutex mtx_framesToDelete = new Mutex();
Mutex mtx_debugLog = new Mutex();
Task[] workTasks = new Task[Environment.ProcessorCount];
bool threadAbort = false;
Action<int, int> lamProcessFrames = (indStart, indEnd) =>
{
MagickImage img1 = null;
MagickImage img2 = null;
for (int i = indStart; i < indEnd; i++) // Loop through frames
{
string frame1_name = framePaths[i].FullName;
// its likely we carried over an already loaded image from a previous iteration
if (!(img1 != null && img1.FileName == frame1_name))
img1 = GetImage(framePaths[i].FullName);
if (img1 == null) continue;
for (int j = i + 1; j < framePaths.Length; j++)
{
if (threadAbort || Interpolate.canceled) return;
//if (j % 3 == 0)
//await Task.Delay(1);
string frame2_name = framePaths[j].FullName;
if (j >= indEnd)
{
// if we are already extending outside of this thread's range and j is already flagged, then we need to abort
bool isFlaggedForDeletion = false;
mtx_framesToDelete.WaitOne();
isFlaggedForDeletion = framesToDelete.Contains(frame2_name);
mtx_framesToDelete.ReleaseMutex();
if (isFlaggedForDeletion)
return;
}
img2 = GetImage(framePaths[j].FullName);
if (img2 == null) continue;
float diff = GetDifference(img1, img2);
if (diff < threshold) // Is a duped frame.
{
if (!testRun)
{
mtx_framesToDelete.WaitOne();
framesToDelete.Add(frame2_name);
mtx_framesToDelete.ReleaseMutex();
if (debugLog)
{
mtx_debugLog.WaitOne();
Logger.Log("Deduplication: Deleted " + Path.GetFileName(frame2_name));
mtx_debugLog.ReleaseMutex();
}
}
Interlocked.Increment(ref statsFramesDeleted);
if (j + 1 == framePaths.Length)
return;
continue; // test next frame
}
Interlocked.Increment(ref statsFramesKept);
// this frame is different, stop testing agaisnt 'i'
// all the frames between i and j are dupes, we can skip them
i = j - 1;
// keep the currently loaded in img for the next iteration
img1 = img2;
break;
}
}
};
Action lamUpdateInfoBox = () =>
{
int framesProcessed = statsFramesKept + statsFramesDeleted;
Logger.Log($"Deduplication: Running de-duplication ({framesProcessed}/{framePaths.Length}), deleted {statsFramesDeleted} ({(((float)statsFramesDeleted / framePaths.Length) * 100f).ToString("0")}%) duplicate frames so far...", false, true);
Program.mainForm.SetProgress((int)Math.Round(((float)framesProcessed / framePaths.Length) * 100f));
};
// start the worker threads
for (int i = 0; i < workTasks.Length; i++)
{
int chunkSize = framePaths.Length / workTasks.Length;
int indStart = chunkSize * i;
int indEnd = indStart + chunkSize;
if (i + 1 == workTasks.Length) indEnd = framePaths.Length;
workTasks[i] = Task.Run(() => lamProcessFrames(indStart, indEnd));
}
// wait for all the worker threads to finish and update the info box
while (!Interpolate.canceled)
{
await Task.Delay(5);
bool anyThreadStillWorking = false;
// wait for the threads to finish
for (int i = 0; i < workTasks.Length; i++)
{
if (!workTasks[i].IsCompleted) anyThreadStillWorking = true;
}
if (sw.ElapsedMilliseconds >= 250 || !anyThreadStillWorking) // Print every 0.25s (or when done)
{
sw.Restart();
lamUpdateInfoBox();
}
if (!anyThreadStillWorking) break;
}
threadAbort = true;
for (int i = 0; i < workTasks.Length; i++)
await workTasks[i];
lamUpdateInfoBox();
// 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;
// }
foreach (string frame in framesToDelete)
IoUtils.TryDeleteIfExists(frame);
string testStr = testRun ? "[TESTRUN] " : "";
if (Interpolate.canceled) return;
int framesLeft = IoUtils.GetAmountOfFiles(path, false, "*" + Interpolate.currentSettings.framesExt);
int framesDeleted = framePaths.Length - framesLeft;
float percentDeleted = ((float)framesDeleted / framePaths.Length) * 100f;
string keptPercent = $"{(100f - percentDeleted).ToString("0.0")}%";
if (framesDeleted <= 0)
{
Logger.Log($"Deduplication: No duplicate frames detected on this video.", false, true);
}
else if (statsFramesKept <= 0)
{
Interpolate.Cancel("No frames were left after de-duplication!\n\nTry lowering the de-duplication threshold.");
}
else
{
Logger.Log($"{testStr}Deduplication: Kept {framesLeft} ({keptPercent}) frames, deleted {framesDeleted} frames.", false, true);
}
}
static float GetDifference(MagickImage img1, MagickImage img2)
{
double err = img1.Compare(img2, ErrorMetric.Fuzz);
float errPercent = (float)err * 100f;
return errPercent;
}
static float GetDifference(string img1Path, string img2Path)
{
return GetDifference(GetImage(img1Path), GetImage(img2Path));
}
public static async Task CreateDupesFile(string framesPath, string ext)
{
bool debug = Config.GetBool("dupeScanDebug", false);
FileInfo[] frameFiles = IoUtils.GetFileInfosSorted(framesPath, false, "*" + ext);
if (debug)
Logger.Log($"Running CreateDupesFile for '{framesPath}' ({frameFiles.Length} files), ext = {ext}.", true, false, "dupes");
Dictionary<string, List<string>> frames = new Dictionary<string, List<string>>();
for (int i = 0; i < frameFiles.Length; i++)
{
bool isLastItem = (i + 1) == frameFiles.Length;
String fnameCur = Path.GetFileNameWithoutExtension(frameFiles[i].Name);
int frameNumCur = fnameCur.GetInt();
frames[fnameCur] = new List<string>();
if (!isLastItem)
{
String fnameNext = Path.GetFileNameWithoutExtension(frameFiles[i + 1].Name);
int frameNumNext = fnameNext.GetInt();
for (int j = frameNumCur + 1; j < frameNumNext; j++)
{
frames[fnameCur].Add(j.ToString().PadLeft(9, '0'));
}
}
}
File.WriteAllText(Path.Combine(framesPath.GetParentDir(), "dupes.json"), frames.ToJson(true));
}
public static async Task CreateFramesFileVideo(string videoPath, bool loop)
{
if (!Directory.Exists(Interpolate.currentSettings.tempFolder))
Directory.CreateDirectory(Interpolate.currentSettings.tempFolder);
Process ffmpeg = OsUtils.NewProcess(true);
string baseCmd = $"/C cd /D {Path.Combine(IO.Paths.GetPkgPath(), IO.Paths.audioVideoDir).Wrap()}";
string mpDec = FfmpegCommands.GetMpdecimate((int)FfmpegCommands.MpDecSensitivity.Normal, false);
ffmpeg.StartInfo.Arguments = $"{baseCmd} & ffmpeg -loglevel debug -y -i {videoPath.Wrap()} -fps_mode vfr -vf {mpDec} -f null NUL 2>&1 | findstr keep_count:";
List<string> ffmpegOutputLines = (await Task.Run(() => OsUtils.GetProcStdOut(ffmpeg, true))).SplitIntoLines().Where(l => l.IsNotEmpty()).ToList();
var frames = new Dictionary<int, List<int>>();
var frameNums = new List<int>();
int lastKeepFrameNum = 0;
for (int frameIdx = 0; frameIdx < ffmpegOutputLines.Count; frameIdx++)
{
string line = ffmpegOutputLines[frameIdx];
bool drop = frameIdx != 0 && line.Contains(" drop ") && !line.Contains(" keep ");
// Console.WriteLine($"[Frame {frameIdx.ToString().PadLeft(6, '0')}] {(drop ? "DROP" : "KEEP")}");
// frameNums.Add(lastKeepFrameNum);
if (!drop)
{
if (!frames.ContainsKey(frameIdx) || frames[frameIdx] == null)
{
frames[frameIdx] = new List<int>();
}
lastKeepFrameNum = frameIdx;
}
else
{
frames[lastKeepFrameNum].Add(frameIdx);
}
}
var inputFrames = new List<int>(frames.Keys);
if (loop)
{
inputFrames.Add(inputFrames.First());
}
File.WriteAllText(Path.Combine(Interpolate.currentSettings.tempFolder, "input.json"), inputFrames.ToJson(true));
File.WriteAllText(Path.Combine(Interpolate.currentSettings.tempFolder, "dupes.test.json"), frames.ToJson(true));
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,78 @@
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");
if (!File.Exists(modelsFile))
{
Logger.Log($"Error: '{modelsFile}' is missing for {ai.NameInternal}, can't load AI models for this implementation!", true);
return new ModelCollection(ai);
}
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 = ai, Name = name, Desc = "Custom Model", Dir = customModel, SupportsAlpha = alpha, IsDefault = 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;
}
}
}

View File

@@ -0,0 +1,246 @@
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;
using Padding = Flowframes.Data.Padding;
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.currentSettings.framesFolder, false, "*" + Interpolate.currentSettings.framesExt) * Interpolate.currentSettings.interpFactor).RoundToInt());
safetyBufferFrames = 90;
if (Interpolate.currentSettings.ai.Backend == AI.AiBackend.Ncnn)
safetyBufferFrames = Config.GetInt(Config.Key.autoEncSafeBufferNcnn, 150);
if (Interpolate.currentSettings.ai.Backend == AI.AiBackend.Pytorch)
safetyBufferFrames = Config.GetInt(Config.Key.autoEncSafeBufferCuda, 90);
}
public static async Task MainLoop(string interpFramesPath)
{
if(!AutoEncodeResume.resumeNextRun)
AutoEncodeResume.Reset();
debug = Config.GetBool("autoEncDebug", false);
try
{
UpdateChunkAndBufferSizes();
bool imgSeq = Interpolate.currentSettings.outSettings.Encoder.GetInfo().IsImageSequence;
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 chunkNo = AutoEncodeResume.encodedChunks + 1;
string encFile = Path.Combine(interpFramesPath.GetParentDir(), Paths.GetFrameOrderFilename(Interpolate.currentSettings.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(1000);
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 && OsUtils.IsProcessHidden(AiProcess.lastAiProcess))
{
string dirSize = FormatUtils.Bytes(IoUtils.GetDirSize(Interpolate.currentSettings.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", $"{chunkNo.ToString().PadLeft(4, '0')}{FfmpegUtils.GetExt(Interpolate.currentSettings.outSettings)}");
string firstFile = Path.GetFileName(interpFramesLines[frameLinesToEncode.First()].Trim());
string lastFile = Path.GetFileName(interpFramesLines[frameLinesToEncode.Last()].Trim());
Logger.Log($"[AE] Encoding Chunk #{chunkNo} to using line {frameLinesToEncode.First()} ({firstFile}) through {frameLinesToEncode.Last()} ({lastFile}) - {unencodedFrameLines.Count} unencoded frames left in total", true, false, "ffmpeg");
await Export.EncodeChunk(outpath, Interpolate.currentSettings.interpFolder, chunkNo, Interpolate.currentSettings.outSettings, frameLinesToEncode.First(), 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 #" + chunkNo, true, false, "ffmpeg");
lastEncodedFrameNum = (frameLinesToEncode.Last() + 1);
chunkNo++;
AutoEncodeResume.Save();
if(!imgSeq && Config.GetInt(Config.Key.autoEncBackupMode) > 0)
{
if (aiRunning && (currentMuxTask == null || (currentMuxTask != null && currentMuxTask.IsCompleted)))
currentMuxTask = Task.Run(() => Export.ChunksToVideo(Interpolate.currentSettings.tempFolder, videoChunksFolder, Interpolate.currentSettings.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);
if (imgSeq)
return;
await Export.ChunksToVideo(Interpolate.currentSettings.tempFolder, videoChunksFolder, Interpolate.currentSettings.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.currentSettings.interpExt);
}
}
}

View File

@@ -0,0 +1,145 @@
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;
using Newtonsoft.Json;
namespace Flowframes.Main
{
class AutoEncodeResume
{
public static List<string> processedInputFrames = new List<string>();
public static int encodedChunks = 0;
public static int encodedFrames = 0;
public static bool resumeNextRun;
public static string interpSettingsFilename = "settings.json";
public static string chunksFilename = "chunks.json";
public static string inputFramesFilename = "input-frames.json";
public static void Reset ()
{
processedInputFrames = new List<string>();
encodedChunks = 0;
encodedFrames = 0;
}
public static void Save ()
{
string saveDir = Path.Combine(I.currentSettings.tempFolder, Paths.resumeDir);
Directory.CreateDirectory(saveDir);
string chunksJsonPath = Path.Combine(saveDir, chunksFilename);
Dictionary<string, string> saveData = new Dictionary<string, string>();
saveData.Add("encodedChunks", encodedChunks.ToString());
saveData.Add("encodedFrames", encodedFrames.ToString());
File.WriteAllText(chunksJsonPath, JsonConvert.SerializeObject(saveData, Formatting.Indented));
string inputFramesJsonPath = Path.Combine(saveDir, inputFramesFilename);
File.WriteAllText(inputFramesJsonPath, JsonConvert.SerializeObject(processedInputFrames, Formatting.Indented));
string settingsJsonPath = Path.Combine(saveDir, interpSettingsFilename);
File.WriteAllText(settingsJsonPath, JsonConvert.SerializeObject(I.currentSettings, Formatting.Indented));
}
public static void LoadTempFolder(string tempFolderPath)
{
try
{
string resumeFolderPath = Path.Combine(tempFolderPath, Paths.resumeDir);
string settingsJsonPath = Path.Combine(resumeFolderPath, interpSettingsFilename);
InterpSettings interpSettings = JsonConvert.DeserializeObject<InterpSettings>(File.ReadAllText(settingsJsonPath));
Program.mainForm.LoadBatchEntry(interpSettings);
}
catch(Exception e)
{
Logger.Log($"Failed to load resume data: {e.Message}\n{e.StackTrace}");
resumeNextRun = false;
}
}
public static async Task<bool> PrepareResumedRun() // Remove already interpolated frames, return true if interpolation should be skipped
{
if (!resumeNextRun) return false;
try
{
string chunkJsonPath = Path.Combine(I.currentSettings.tempFolder, Paths.resumeDir, chunksFilename);
string inFramesJsonPath = Path.Combine(I.currentSettings.tempFolder, Paths.resumeDir, inputFramesFilename);
dynamic chunksData = JsonConvert.DeserializeObject(File.ReadAllText(chunkJsonPath));
encodedChunks = chunksData.encodedChunks;
encodedFrames = chunksData.encodedFrames;
List<string> processedInputFrames = JsonConvert.DeserializeObject<List<string>>(File.ReadAllText(inFramesJsonPath));
int uniqueInputFrames = processedInputFrames.Distinct().Count();
foreach (string inputFrameName in processedInputFrames)
{
string inputFrameFullPath = Path.Combine(I.currentSettings.tempFolder, Paths.framesDir, inputFrameName);
IoUtils.TryDeleteIfExists(inputFrameFullPath);
}
string videoChunksFolder = Path.Combine(I.currentSettings.tempFolder, Paths.chunksDir);
FileInfo[] invalidChunks = IoUtils.GetFileInfosSorted(videoChunksFolder, true, "????.*").Skip(encodedChunks).ToArray();
foreach (FileInfo chunk in invalidChunks)
chunk.Delete();
int inputFramesLeft = IoUtils.GetAmountOfFiles(Path.Combine(I.currentSettings.tempFolder, Paths.framesDir), false);
Logger.Log($"Resume: Already encoded {encodedFrames} frames in {encodedChunks} chunks. There are now {inputFramesLeft} input frames left to interpolate.");
if(inputFramesLeft < 2)
{
if(IoUtils.GetAmountOfFiles(videoChunksFolder, true, "*.*") > 0)
{
Logger.Log($"No more frames left to interpolate - Merging existing video chunks instead.");
await Export.ChunksToVideo(I.currentSettings.tempFolder, videoChunksFolder, I.currentSettings.outPath);
await I.Done();
}
else
{
I.Cancel("There are no more frames left to interpolate in this temp folder!");
}
return true;
}
return false;
}
catch (Exception e)
{
Logger.Log($"Failed to prepare resumed run: {e.Message}\n{e.StackTrace}");
I.Cancel("Failed to resume interpolation. Check the logs for details.");
resumeNextRun = false;
return true;
}
// 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
}
}
}

View File

@@ -0,0 +1,137 @@
using Flowframes.Data;
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 (busy)
{
Logger.Log("Queue: Start() has been called, but I'm already busy - Returning!", true);
return;
}
SetBusy(true);
if (Config.GetBool(Config.Key.clearLogOnInput))
Logger.ClearLogBox();
stopped = false;
Program.mainForm.SetTab(Program.mainForm.previewTab.Name);
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(500);
}
Logger.Log("Queue: Finished queue processing.");
OsUtils.ShowNotificationIfInBackground("Flowframes Queue", "Finished queue processing.");
SetBusy(false);
Program.mainForm.SetWorking(false);
Program.mainForm.SetTab(Program.mainForm.interpOptsTab.Name);
Program.mainForm.CompletionAction();
}
public static void Stop()
{
stopped = true;
}
static async Task RunEntry(InterpSettings entry)
{
SetBusy(true);
Program.mainForm.SetWorking(true);
if (!EntryIsValid(entry))
{
Logger.Log("Queue: Skipping entry because it's invalid.");
Program.batchQueue.Dequeue();
return;
}
MediaFile mf = new MediaFile(entry.inPath, false);
mf.InputRate = entry.inFps;
await mf.Initialize();
Interpolate.currentMediaFile = mf;
Logger.Log($"Queue: Processing {mf.Name} ({entry.interpFactor}x {entry.ai.NameShort}).");
Program.mainForm.LoadBatchEntry(entry); // Load entry into GUI
Interpolate.currentSettings = entry;
Program.mainForm.runBtn_Click(null, null);
while (Program.busy)
await Task.Delay(500);
Program.batchQueue.Dequeue();
Program.mainForm.SetWorking(false);
Logger.Log($"Queue: Done processing {mf.Name} ({entry.interpFactor}x {entry.ai.NameShort}).");
}
static void SetBusy(bool state)
{
busy = state;
if (currentBatchForm != null)
currentBatchForm.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) && Config.GetInt("outFolderLoc") != 1))
{
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;
}
}
}

397
Flowframes/Main/Export.cs Normal file
View File

@@ -0,0 +1,397 @@
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 Flowframes.MiscUtils;
using Flowframes.Os;
using System.Collections.Generic;
using Newtonsoft.Json;
using Flowframes.Ui;
namespace Flowframes.Main
{
class Export
{
public static async Task ExportFrames(string path, string outFolder, OutputSettings exportSettings, bool stepByStep)
{
if (Config.GetInt(Config.Key.sceneChangeFillMode) == 1)
{
string frameFile = Path.Combine(I.currentSettings.tempFolder, Paths.GetFrameOrderFilename(I.currentSettings.interpFactor));
await Blend.BlendSceneChanges(frameFile);
}
if (exportSettings.Encoder.GetInfo().IsImageSequence) // Copy interp frames out of temp folder and skip video export for image seq export
{
try
{
await ExportImageSequence(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.currentSettings.interpExt) <= 1)
{
I.Cancel("Output folder does not contain frames - An error must have occured during interpolation!", AiProcess.hasShownError);
return;
}
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.currentSettings.outFps.GetFloat() > maxFps.GetFloat();
bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt(Config.Key.maxFpsMode) == 0;
if (!dontEncodeFullFpsVid)
await Encode(exportSettings, path, Path.Combine(outFolder, await IoUtils.GetCurrentExportFilename(false, true)), I.currentSettings.outFps, new Fraction());
if (fpsLimit)
await Encode(exportSettings, path, Path.Combine(outFolder, await IoUtils.GetCurrentExportFilename(true, true)), I.currentSettings.outFps, maxFps);
}
catch (Exception e)
{
Logger.Log("FramesToVideo Error: " + e.Message, false);
UiUtils.ShowMessageBox("An error occured while trying to convert the interpolated frames to a video.\nCheck the log for details.", UiUtils.MessageType.Error);
}
}
public static async Task<string> GetPipedFfmpegCmd(bool ffplay = false)
{
InterpSettings s = I.currentSettings;
string encArgs = FfmpegUtils.GetEncArgs(s.outSettings, (s.ScaledResolution.IsEmpty ? s.InputResolution : s.ScaledResolution), s.outFps.GetFloat(), true).FirstOrDefault();
string max = Config.Get(Config.Key.maxFps);
Fraction maxFps = max.Contains("/") ? new Fraction(max) : new Fraction(max.GetFloat());
bool fpsLimit = maxFps.GetFloat() > 0f && s.outFps.GetFloat() > maxFps.GetFloat();
// Logger.Log($"VFR Ratio: {I.currentMediaFile.VideoStreams.First().FpsInfo.VfrRatio} ({I.currentMediaFile.VideoStreams.First().FpsInfo.Fps} FPS Specified, {I.currentMediaFile.VideoStreams.First().FpsInfo.SpecifiedFps} FPS Avg)");
bool gifInput = I.currentMediaFile.Format.Upper() == "GIF"; // If input is GIF, we don't need to check the color space etc
VidExtraData extraData = gifInput ? new VidExtraData() : await FfmpegCommands.GetVidExtraInfo(s.inPath);
string extraArgsIn = await FfmpegEncode.GetFfmpegExportArgsIn(s.outFps, s.outItsScale);
string extraArgsOut = await FfmpegEncode.GetFfmpegExportArgsOut(fpsLimit ? maxFps : new Fraction(), extraData, s.outSettings);
if(s.outSettings.Encoder == Enums.Encoding.Encoder.Exr)
{
extraArgsIn += " -color_trc bt709 -color_primaries bt709 -colorspace bt709";
}
if (ffplay)
{
bool useNutPipe = true; // TODO: Make this bool a config flag
encArgs = useNutPipe ? "-c:v rawvideo -pix_fmt rgba" : $"-pix_fmt yuv444p16";
string format = useNutPipe ? "nut" : "yuv4mpegpipe";
return
$"{extraArgsIn} -i pipe: {encArgs} {extraArgsOut} -f {format} - | ffplay - " +
$"-autoexit -seek_interval {VapourSynthUtils.GetSeekSeconds(Program.mainForm.currInDuration)} " +
$"-window_title \"Flowframes Realtime Interpolation ({s.inFps.GetString()} FPS x{s.interpFactor} = {s.outFps.GetString()} FPS) ({s.model.Name})\" ";
}
else
{
bool imageSequence = s.outSettings.Encoder.GetInfo().IsImageSequence;
s.FullOutPath = Path.Combine(s.outPath, await IoUtils.GetCurrentExportFilename(fpsLimit, !imageSequence));
IoUtils.RenameExistingFileOrDir(s.FullOutPath);
if (imageSequence)
{
Directory.CreateDirectory(s.FullOutPath);
s.FullOutPath += $"/%{Padding.interpFrames}d.{s.outSettings.Encoder.GetInfo().OverideExtension}";
}
return $"{extraArgsIn} -i pipe: {extraArgsOut} {encArgs} {s.FullOutPath.Wrap()}";
}
}
static async Task ExportImageSequence(string framesPath, bool stepByStep)
{
Program.mainForm.SetStatus("Copying output frames...");
Enums.Encoding.Encoder desiredFormat = I.currentSettings.outSettings.Encoder;
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.currentSettings.outFps.GetFloat() > maxFps.GetFloat();
bool dontEncodeFullFpsSeq = fpsLimit && Config.GetInt(Config.Key.maxFpsMode) == 0;
string framesFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(I.currentSettings.interpFactor));
if (!dontEncodeFullFpsSeq)
{
string outputFolderPath = Path.Combine(I.currentSettings.outPath, await IoUtils.GetCurrentExportFilename(false, false));
IoUtils.RenameExistingFolder(outputFolderPath);
Logger.Log($"Exporting {desiredFormat.ToString().Upper()} frames to '{Path.GetFileName(outputFolderPath)}'...");
if (desiredFormat.GetInfo().OverideExtension.ToUpper() == availableFormat.ToUpper()) // Move if frames are already in the desired format
await CopyOutputFrames(framesPath, framesFile, outputFolderPath, 1, fpsLimit, false);
else // Encode if frames are not in desired format
await FfmpegEncode.FramesToFrames(framesFile, outputFolderPath, 1, I.currentSettings.outFps, new Fraction(), desiredFormat, OutputUtils.GetImgSeqQ(I.currentSettings.outSettings));
}
if (fpsLimit)
{
string outputFolderPath = Path.Combine(I.currentSettings.outPath, await IoUtils.GetCurrentExportFilename(true, false));
Logger.Log($"Exporting {desiredFormat.ToString().Upper()} frames to '{Path.GetFileName(outputFolderPath)}' (Resampled to {maxFps} FPS)...");
await FfmpegEncode.FramesToFrames(framesFile, outputFolderPath, 1, I.currentSettings.outFps, maxFps, desiredFormat, OutputUtils.GetImgSeqQ(I.currentSettings.outSettings));
}
if (!stepByStep)
await IoUtils.DeleteContentsOfDirAsync(I.currentSettings.interpFolder);
}
static async Task CopyOutputFrames(string framesPath, string framesFile, string outputFolderPath, int startNo, bool dontMove, bool hideLog)
{
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, startNo.ToString().PadLeft(Padding.interpFrames, '0')) + Path.GetExtension(framePath);
startNo++;
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}", hideLog, true);
await Task.Delay(1);
}
}
}
static async Task Encode(OutputSettings settings, string framesPath, string outPath, Fraction fps, Fraction resampleFps)
{
string framesFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(I.currentSettings.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 (settings.Format == Enums.Output.Format.Gif)
{
int paletteColors = OutputUtils.GetGifColors(ParseUtils.GetEnum<Enums.Encoding.Quality.GifColors>(settings.Quality, true, Strings.VideoQuality));
await FfmpegEncode.FramesToGifConcat(framesFile, outPath, fps, true, paletteColors, resampleFps, I.currentSettings.outItsScale);
}
else
{
VidExtraData extraData = await FfmpegCommands.GetVidExtraInfo(I.currentSettings.inPath);
await FfmpegEncode.FramesToVideo(framesFile, outPath, settings, fps, resampleFps, I.currentSettings.outItsScale, extraData);
await MuxOutputVideo(I.currentSettings.inPath, outPath);
await Loop(outPath, await GetLoopTimes());
}
}
public static async Task MuxPipedVideo(string inputVideo, string outputPath)
{
await MuxOutputVideo(inputVideo, Path.Combine(outputPath, outputPath));
await Loop(outputPath, await GetLoopTimes());
}
public static async Task ChunksToVideo(string tempFolder, string chunksFolder, string baseOutPath, bool isBackup = false)
{
if (IoUtils.GetAmountOfFiles(chunksFolder, true, "*" + FfmpegUtils.GetExt(I.currentSettings.outSettings)) < 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)
UiUtils.ShowMessageBox("An error occured while trying to merge the video chunks.\nCheck the log for details.", UiUtils.MessageType.Error);
}
Logger.Log($"Merged video chunks in {sw}", 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.currentSettings.inPath, outPath, isBackup, !isBackup);
if (!isBackup)
await Loop(outPath, await GetLoopTimes());
}
public static async Task EncodeChunk(string outPath, string interpDir, int chunkNo, OutputSettings settings, int firstFrameNum, int framesAmount)
{
string framesFileFull = Path.Combine(I.currentSettings.tempFolder, Paths.GetFrameOrderFilename(I.currentSettings.interpFactor));
string concatFile = Path.Combine(I.currentSettings.tempFolder, Paths.GetFrameOrderFilenameChunk(firstFrameNum, firstFrameNum + framesAmount));
File.WriteAllLines(concatFile, IoUtils.ReadLines(framesFileFull).Skip(firstFrameNum).Take(framesAmount));
List<string> inputFrames = JsonConvert.DeserializeObject<List<string>>(File.ReadAllText(framesFileFull + ".inputframes.json")).Skip(firstFrameNum).Take(framesAmount).ToList();
if (Config.GetInt(Config.Key.sceneChangeFillMode) == 1)
await Blend.BlendSceneChanges(concatFile, 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.currentSettings.outFps.GetFloat() > maxFps.GetFloat();
VidExtraData extraData = await FfmpegCommands.GetVidExtraInfo(I.currentSettings.inPath);
bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt(Config.Key.maxFpsMode) == 0;
if (settings.Encoder.GetInfo().IsImageSequence) // Image Sequence output mode, not video
{
string desiredFormat = settings.Encoder.GetInfo().OverideExtension;
string availableFormat = Path.GetExtension(IoUtils.GetFilesSorted(interpDir)[0]).Remove(".").ToUpper();
if (!dontEncodeFullFpsVid)
{
string outFolderPath = Path.Combine(I.currentSettings.outPath, await IoUtils.GetCurrentExportFilename(false, false));
int startNo = IoUtils.GetAmountOfFiles(outFolderPath, false) + 1;
if (chunkNo == 1) // Only check for existing folder on first chunk, otherwise each chunk makes a new folder
IoUtils.RenameExistingFolder(outFolderPath);
if (desiredFormat.ToUpper() == availableFormat.ToUpper()) // Move if frames are already in the desired format
await CopyOutputFrames(interpDir, concatFile, outFolderPath, startNo, fpsLimit, true);
else // Encode if frames are not in desired format
await FfmpegEncode.FramesToFrames(concatFile, outFolderPath, startNo, I.currentSettings.outFps, new Fraction(), settings.Encoder, OutputUtils.GetImgSeqQ(settings), AvProcess.LogMode.Hidden);
}
if (fpsLimit)
{
string outputFolderPath = Path.Combine(I.currentSettings.outPath, await IoUtils.GetCurrentExportFilename(true, false));
int startNumber = IoUtils.GetAmountOfFiles(outputFolderPath, false) + 1;
await FfmpegEncode.FramesToFrames(concatFile, outputFolderPath, startNumber, I.currentSettings.outFps, maxFps, settings.Encoder, OutputUtils.GetImgSeqQ(settings), AvProcess.LogMode.Hidden);
}
}
else
{
if (!dontEncodeFullFpsVid)
await FfmpegEncode.FramesToVideo(concatFile, outPath, settings, I.currentSettings.outFps, new Fraction(), I.currentSettings.outItsScale, 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(concatFile, outPath, settings, I.currentSettings.outFps, maxFps, I.currentSettings.outItsScale, extraData, AvProcess.LogMode.Hidden, true); // Encode with limited fps
}
}
AutoEncodeResume.encodedChunks += 1;
AutoEncodeResume.encodedFrames += framesAmount;
AutoEncodeResume.processedInputFrames.AddRange(inputFrames);
}
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.currentSettings.outFps.GetFloat()).RoundToInt();
int outFrames = (I.currentMediaFile.FrameCount * I.currentSettings.interpFactor).RoundToInt();
if (outFrames / I.currentSettings.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.currentSettings.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.currentSettings.tempFolder, shortest);
}
catch (Exception e)
{
Logger.Log("Failed to merge audio/subtitles with output video!", !showLog);
Logger.Log("MergeAudio() Exception: " + e.Message, true);
}
}
}
}

View File

@@ -0,0 +1,413 @@
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.MiscUtils;
using Flowframes.Os;
using Flowframes.Properties;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Padding = Flowframes.Data.Padding;
namespace Flowframes.Main
{
class FrameOrder
{
private static Stopwatch benchmark = new Stopwatch();
private static FileInfo[] frameFiles;
private static FileInfo[] frameFilesWithoutLast;
private static List<string> sceneFrames = new List<string>();
private static Dictionary<int, string> frameFileContents = new Dictionary<int, string>();
private static List<string> inputFilenames = new List<string>();
private static int lastOutFileCount;
public static async Task CreateFrameOrderFile(string tempFolder, bool loopEnabled, float interpFactor)
{
Logger.Log("Generating frame order information...");
try
{
foreach (FileInfo file in IoUtils.GetFileInfosSorted(tempFolder, false, $"{Paths.frameOrderPrefix}*.*"))
file.Delete();
benchmark.Restart();
if (Interpolate.currentSettings.ai.NameInternal == Implementations.rifeNcnnVs.NameInternal)
CreateFramesFileVid(Interpolate.currentSettings.inPath, Interpolate.currentSettings.tempFolder, loopEnabled, interpFactor);
else
await CreateFramesFileImgSeq(tempFolder, loopEnabled, interpFactor);
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, List<string>> dupesDict = new Dictionary<string, List<string>>();
static void LoadDupesFile(string path)
{
dupesDict = JsonConvert.DeserializeObject<Dictionary<string, List<string>>>(File.ReadAllText(path));
}
public static void CreateFramesFileVid(string vidPath, string tempFolder, bool loop, float interpFactor)
{
if (Interpolate.canceled) return;
Logger.Log($"Generating frame order information for {interpFactor}x...", false, true);
// frameFileContents.Clear();
int lastOutFileCount = 0;
string inputJsonPath = Path.Combine(tempFolder, "input.json");
List<int> inputFrames = JsonConvert.DeserializeObject<List<int>>(File.ReadAllText(inputJsonPath));
int frameCount = Interpolate.currentMediaFile.FrameCount;
frameCount = inputFrames.Count;
// if (loop)
// {
// frameCount++;
// }
int frameCountWithoutLast = frameCount - 1;
string dupesFile = Path.Combine(tempFolder, "dupes.test.json");
var dupes = JsonConvert.DeserializeObject<Dictionary<int, List<int>>>(File.ReadAllText(dupesFile));
bool debug = Config.GetBool("frameOrderDebug", false);
int targetFrameCount = (frameCount * interpFactor).RoundToInt() - InterpolateUtils.GetRoundedInterpFramesPerInputFrame(interpFactor);
Fraction step = new Fraction(frameCount, targetFrameCount + InterpolateUtils.GetRoundedInterpFramesPerInputFrame(interpFactor));
var framesList = new List<int>();
for (int i = 0; i < targetFrameCount; i++)
{
float currentFrameTime = 1 + (step * i).GetFloat();
int sourceFrameIdx = (int)Math.Floor(currentFrameTime) - 1;
framesList.Add(i);
Console.WriteLine($"Frame: #{i} - Idx: {sourceFrameIdx} - [Time: {currentFrameTime}]");
if (sourceFrameIdx < dupes.Count)
{
bool last = i == lastOutFileCount;
if (last && loop)
continue;
for (int dupeNum = 0; dupeNum < dupes.ElementAt(sourceFrameIdx).Value.Count; dupeNum++)
{
framesList.Add(framesList.Last());
Console.WriteLine($"Frame: #{i} - Idx: {sourceFrameIdx} - (Dupe {dupeNum + 1}/{dupes.ElementAt(sourceFrameIdx).Value.Count})");
}
}
}
// if (loop)
// {
// framesList.Add(framesList.First());
// }
//for (int x = 0; x < frameFileContents.Count; x++)
// fileContent += frameFileContents[x];
lastOutFileCount++;
if (Config.GetBool(Config.Key.fixOutputDuration)) // Match input duration by padding duping last frame until interp frames == (inputframes * factor)
{
int neededFrames = (frameCount * interpFactor).RoundToInt() - framesList.Count;
for (int i = 0; i < neededFrames; i++)
framesList.Add(framesList.Last());
}
if (loop)
framesList.RemoveAt(framesList.Count() - 1);
string framesFileVs = Path.Combine(tempFolder, "frames.vs.json");
// List<int> frameNums = new List<int>();
//
// foreach (string line in fileContent.SplitIntoLines().Where(x => x.StartsWith("file ")))
// frameNums.Add(line.Split('/')[1].Split('.')[0].GetInt() - 1); // Convert filename to 0-indexed number
File.WriteAllText(framesFileVs, JsonConvert.SerializeObject(framesList, Formatting.Indented));
}
public static async Task CreateFramesFileImgSeq(string tempFolder, bool loop, float interpFactor)
{
// await CreateFramesFileVideo(Interpolate.currentSettings.inPath, loop, interpFactor);
if (Interpolate.canceled) return;
Logger.Log($"Generating frame order information for {interpFactor}x...", false, true);
bool sceneDetection = true;
string ext = Interpolate.currentSettings.interpExt;
frameFileContents.Clear();
lastOutFileCount = 0;
string framesDir = Path.Combine(tempFolder, Paths.framesDir);
frameFiles = new DirectoryInfo(framesDir).GetFiles("*" + Interpolate.currentSettings.framesExt);
frameFilesWithoutLast = frameFiles;
Array.Resize(ref frameFilesWithoutLast, frameFilesWithoutLast.Length - 1);
string framesFile = Path.Combine(tempFolder, Paths.GetFrameOrderFilename(interpFactor));
string fileContent = "";
string dupesFile = Path.Combine(tempFolder, "dupes.json");
LoadDupesFile(dupesFile);
string scnFramesPath = Path.Combine(tempFolder, Paths.scenesDir);
sceneFrames.Clear();
if (Directory.Exists(scnFramesPath))
sceneFrames = Directory.GetFiles(scnFramesPath).Select(file => GetNameNoExt(file)).ToList();
inputFilenames.Clear();
bool debug = true; // Config.GetBool("frameOrderDebug", false);
List<Task> tasks = new List<Task>();
int linesPerTask = (400 / interpFactor).RoundToInt();
int num = 0;
int targetFrameCount = (frameFiles.Length * interpFactor).RoundToInt() - InterpolateUtils.GetRoundedInterpFramesPerInputFrame(interpFactor);
if (interpFactor == (int)interpFactor) // Use old multi-threaded code if factor is not fractional
{
for (int i = 0; i < frameFilesWithoutLast.Length; i += linesPerTask)
{
tasks.Add(GenerateFrameLines(num, i, linesPerTask, (int)interpFactor, sceneDetection, debug));
num++;
}
}
else
{
await GenerateFrameLinesFloat(frameFiles.Length, targetFrameCount, interpFactor, sceneDetection, debug);
}
await Task.WhenAll(tasks);
for (int x = 0; x < frameFileContents.Count; x++)
fileContent += frameFileContents[x];
lastOutFileCount++;
if (Config.GetBool(Config.Key.fixOutputDuration)) // Match input duration by padding duping last frame until interp frames == (inputframes * factor)
{
int neededFrames = (frameFiles.Length * interpFactor).RoundToInt() - fileContent.SplitIntoLines().Where(x => x.StartsWith("'file ")).Count();
for (int i = 0; i < neededFrames; i++)
fileContent += fileContent.SplitIntoLines().Where(x => x.StartsWith("'file ")).Last();
}
if (loop)
fileContent = fileContent.Remove(fileContent.LastIndexOf("\n"));
File.WriteAllText(framesFile, fileContent);
File.WriteAllText(framesFile + ".inputframes.json", JsonConvert.SerializeObject(inputFilenames, Formatting.Indented));
string framesFileVs = Path.Combine(tempFolder, "frames.vs.json");
List<int> frameNums = new List<int>();
foreach (string line in fileContent.SplitIntoLines().Where(x => x.StartsWith("file ")))
frameNums.Add(line.Split('/')[1].Split('.')[0].GetInt() - 1); // Convert filename to 0-indexed number
File.WriteAllText(framesFileVs, JsonConvert.SerializeObject(frameNums, Formatting.Indented));
}
class FrameFileLine
{
public string OutFileName { get; set; } = "";
public string InFileNameFrom { get; set; } = "";
public string InFileNameTo { get; set; } = "";
public string InFileNameFromNext { get; set; } = "";
public string InFileNameToNext { get; set; } = "";
public float Timestep { get; set; } = -1;
public bool Discard { get; set; } = false;
public bool DiscardNext { get; set; } = false;
public int DiscardedFrames { get; set; } = 0;
public FrameFileLine(string outFileName, string inFilenameFrom, string inFilenameTo, string inFilenameToNext, float timestep, bool discard = false, bool discardNext = false, int discardedFrames = 0)
{
OutFileName = outFileName;
InFileNameFrom = inFilenameFrom;
InFileNameTo = inFilenameTo;
InFileNameFromNext = inFilenameTo;
InFileNameToNext = inFilenameToNext;
Timestep = timestep;
Discard = discard;
DiscardNext = discardNext;
DiscardedFrames = discardedFrames;
}
public override string ToString()
{
List<string> strings = new List<string>();
if (!string.IsNullOrWhiteSpace(InFileNameTo)) strings.Add($"to {InFileNameTo}");
if (Timestep >= 0f) strings.Add($"@ {Timestep.ToString("0.000000").Split('.').Last()}");
if (Discard) strings.Add("[Discard]");
if (DiscardNext) strings.Add($"SCN:{InFileNameFromNext}>{InFileNameToNext}>{DiscardedFrames}");
return $"file '{OutFileName}' # => {InFileNameFrom} {string.Join(" ", strings)}\n";
}
}
static async Task GenerateFrameLinesFloat(int sourceFrameCount, int targetFrameCount, float factor, bool sceneDetection, bool debug)
{
int totalFileCount = 0;
bool blendSceneChances = Config.GetInt(Config.Key.sceneChangeFillMode) > 0;
string ext = Interpolate.currentSettings.interpExt;
Fraction step = new Fraction(sourceFrameCount, targetFrameCount + InterpolateUtils.GetRoundedInterpFramesPerInputFrame(factor));
List<FrameFileLine> lines = new List<FrameFileLine>();
string lastUndiscardFrame = "";
for (int i = 0; i < targetFrameCount; i++)
{
if (Interpolate.canceled) return;
float currentFrameTime = 1 + (step * i).GetFloat();
int sourceFrameIdx = (int)Math.Floor(currentFrameTime) - 1;
float timestep = (currentFrameTime - (int)Math.Floor(currentFrameTime));
bool sceneChange = (sceneDetection && (sourceFrameIdx + 1) < FrameRename.importFilenames.Length && sceneFrames.Contains(GetNameNoExt(FrameRename.importFilenames[sourceFrameIdx + 1])));
string filename = $"{Paths.interpDir}/{(i + 1).ToString().PadLeft(Padding.interpFrames, '0')}{ext}";
if (string.IsNullOrWhiteSpace(lastUndiscardFrame))
lastUndiscardFrame = filename;
if (!sceneChange)
lastUndiscardFrame = filename;
string inputFilenameFrom = frameFiles[sourceFrameIdx].Name;
string inputFilenameTo = (sourceFrameIdx + 1 >= frameFiles.Length) ? "" : frameFiles[sourceFrameIdx + 1].Name;
string inputFilenameToNext = (sourceFrameIdx + 2 >= frameFiles.Length) ? "" : frameFiles[sourceFrameIdx + 2].Name;
Console.WriteLine($"Frame: Idx {sourceFrameIdx} - {(sceneChange && !blendSceneChances ? lastUndiscardFrame : filename)}");
lines.Add(new FrameFileLine(sceneChange && !blendSceneChances ? lastUndiscardFrame : filename, inputFilenameFrom, inputFilenameTo, inputFilenameToNext, timestep, sceneChange));
string inputFilenameNoExtRenamed = Path.GetFileNameWithoutExtension(FrameRename.importFilenames[sourceFrameIdx]);
if (!dupesDict.ContainsKey(inputFilenameNoExtRenamed))
continue;
foreach (string s in dupesDict[inputFilenameNoExtRenamed])
{
string fname = sceneChange && !blendSceneChances ? lastUndiscardFrame : filename;
Console.WriteLine($"Frame: Idx {sourceFrameIdx} - Dupe {dupesDict[inputFilenameNoExtRenamed].IndexOf(s)}/{dupesDict[inputFilenameNoExtRenamed].Count} {fname}");
lines.Add(new FrameFileLine(fname, inputFilenameFrom, inputFilenameTo, inputFilenameToNext, timestep, sceneChange));
}
}
if (totalFileCount > lastOutFileCount)
lastOutFileCount = totalFileCount;
for (int lineIdx = 0; lineIdx < lines.Count; lineIdx++)
{
bool discardNext = lineIdx > 0 && (lineIdx + 1) < lines.Count && !lines.ElementAt(lineIdx).Discard && lines.ElementAt(lineIdx + 1).Discard;
int discardedFramesCount = 0;
if (discardNext)
{
for (int idx = lineIdx + 1; idx < lines.Count; idx++)
{
if (lines.ElementAt(idx).Discard)
discardedFramesCount++;
else
break;
}
}
lines.ElementAt(lineIdx).DiscardNext = discardNext;
lines.ElementAt(lineIdx).DiscardedFrames = discardedFramesCount;
}
frameFileContents[0] = String.Join("", lines);
}
static async Task GenerateFrameLines(int number, int startIndex, int count, int factor, bool sceneDetection, bool debug)
{
int totalFileCount = (startIndex) * factor;
int interpFramesAmount = factor;
string ext = Interpolate.currentSettings.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].Count : 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}")}]");
}
inputFilenames.Add(frameFilesWithoutLast[i].Name);
}
}
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);
}
}
}

View File

@@ -0,0 +1,319 @@
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 static bool currentlyUsingAutoEnc;
public static InterpSettings currentSettings;
public static MediaFile currentMediaFile;
public static bool canceled = false;
public static float InterpProgressMultiplier = 1f;
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(currentSettings)) return; // General input checks
if (!Utils.CheckPathValid(currentSettings.inPath)) return; // Check if input path/file is valid
if (!Utils.CheckAiAvailable(currentSettings.ai, currentSettings.model)) return; // Check if selected AI pkg is installed
if (!AutoEncodeResume.resumeNextRun && !Utils.CheckDeleteOldTempFolder()) return; // Try to delete temp folder if an old one exists
if (!(await Utils.CheckEncoderValid())) return; // Check encoder compat
Utils.ShowWarnings(currentSettings.interpFactor, currentSettings.ai);
currentSettings.stepByStep = false;
Program.mainForm.SetStatus("Starting...");
sw.Restart();
if (!AutoEncodeResume.resumeNextRun && !(currentSettings.ai.Piped && !currentSettings.inputIsFrames /* && Config.GetInt(Config.Key.dedupMode) == 0) */))
{
await GetFrames();
if (canceled) return;
await PostProcessFrames(false);
}
if (canceled) return;
bool skip = await AutoEncodeResume.PrepareResumedRun();
if (skip || canceled) return;
await RunAi(currentSettings.interpFolder, currentSettings.ai);
if (canceled) return;
Program.mainForm.SetProgress(100);
if (!currentlyUsingAutoEnc)
{
if (currentSettings.ai.Piped)
{
if(!currentSettings.outSettings.Encoder.GetInfo().IsImageSequence)
await Export.MuxPipedVideo(currentSettings.inPath, currentSettings.FullOutPath);
}
else
{
await Export.ExportFrames(currentSettings.interpFolder, currentSettings.outPath, currentSettings.outSettings, false);
}
}
if (!AutoEncodeResume.resumeNextRun && Config.GetBool(Config.Key.keepTempFolder) && IoUtils.GetAmountOfFiles(currentSettings.framesFolder, false) > 0)
await Task.Run(async () => { await FrameRename.Unrename(); });
IoUtils.DeleteIfSmallerThanKb(currentSettings.FullOutPath);
await Done();
}
public static async Task Done()
{
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 Realtime ()
{
canceled = false;
Program.mainForm.SetWorking(true);
if(currentSettings.ai.NameInternal != Implementations.rifeNcnnVs.NameInternal)
Cancel($"Real-time interpolation is only available when using {Implementations.rifeNcnnVs.FriendlyName}.");
if (canceled) return;
Program.mainForm.SetStatus("Downloading models...");
await ModelDownloader.DownloadModelFiles(currentSettings.ai, currentSettings.model.Dir);
if (canceled) return;
Program.mainForm.SetStatus("Running real-time interpolation...");
await AiProcess.RunRifeNcnnVs(currentSettings.framesFolder, "", currentSettings.interpFactor, currentSettings.model.Dir, true);
Program.mainForm.SetStatus("Ready");
Program.mainForm.SetWorking(false);
}
public static async Task GetFrames()
{
currentSettings.RefreshAlpha();
currentSettings.RefreshExtensions(InterpSettings.FrameType.Import);
if (Config.GetBool(Config.Key.scnDetect) && !currentSettings.ai.Piped)
{
Program.mainForm.SetStatus("Extracting scenes from video...");
await FfmpegExtract.ExtractSceneChanges(currentSettings.inPath, Path.Combine(currentSettings.tempFolder, Paths.scenesDir), currentSettings.inFpsDetected, currentSettings.inputIsFrames, currentSettings.framesExt);
}
if (!currentSettings.inputIsFrames) // Extract if input is video, import if image sequence
await ExtractFrames(currentSettings.inPath, currentSettings.framesFolder, currentSettings.alpha);
else
await FfmpegExtract.ImportImagesCheckCompat(currentSettings.inPath, currentSettings.framesFolder, currentSettings.alpha, currentSettings.ScaledResolution, true, currentSettings.framesExt);
}
public static async Task ExtractFrames(string inPath, string outPath, bool alpha)
{
if (canceled) return;
Program.mainForm.SetStatus("Extracting frames from video...");
currentSettings.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, currentSettings.inFpsDetected, mpdecimate, false, res, currentSettings.framesExt);
if (mpdecimate)
{
int framesLeft = IoUtils.GetAmountOfFiles(outPath, false, "*" + currentSettings.framesExt);
int framesDeleted = currentMediaFile.FrameCount - framesLeft;
float percentDeleted = ((float)framesDeleted / currentMediaFile.FrameCount) * 100f;
string keptPercent = $"{(100f - percentDeleted).ToString("0.0")}%";
if (framesDeleted > 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(currentSettings.tempFolder, Paths.scenesDir), currentSettings.framesFolder);
}
public static async Task PostProcessFrames(bool stepByStep)
{
if (canceled) return;
Program.mainForm.SetStatus("Processing frames...");
int extractedFrames = IoUtils.GetAmountOfFiles(currentSettings.framesFolder, false, "*" + currentSettings.framesExt);
if (!Directory.Exists(currentSettings.framesFolder) || currentMediaFile.FrameCount <= 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!\nExtracted {extractedFrames} frames - current.framesFolder exists: {Directory.Exists(currentSettings.framesFolder)} - currentInputFrameCount = {currentMediaFile.FrameCount} - extractedFrames = {extractedFrames}.\n\nYour input file might be incompatible.");
}
if (Config.GetInt(Config.Key.dedupMode) == 1)
await Dedupe.Run(currentSettings.framesFolder);
if (!Config.GetBool(Config.Key.enableLoop))
{
// await Utils.CopyLastFrame(currentMediaFile.FrameCount);
}
else
{
FileInfo[] frameFiles = IoUtils.GetFileInfosSorted(currentSettings.framesFolder);
string ext = frameFiles.First().Extension;
int lastNum = frameFiles.Last().Name.GetInt() + 1;
string loopFrameTargetPath = Path.Combine(currentSettings.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;
bool dedupe = Config.GetInt(Config.Key.dedupMode) != 0;
if (!ai.Piped || (ai.Piped && currentSettings.inputIsFrames))
{
await Task.Run(async () => { await Dedupe.CreateDupesFile(currentSettings.framesFolder, currentSettings.framesExt); });
await Task.Run(async () => { await FrameRename.Rename(); });
}
else if (ai.Piped && dedupe)
{
await Task.Run(async () => { await Dedupe.CreateFramesFileVideo(currentSettings.inPath, Config.GetBool(Config.Key.enableLoop)); });
}
if (!ai.Piped || (ai.Piped && dedupe))
await Task.Run(async () => { await FrameOrder.CreateFrameOrderFile(currentSettings.tempFolder, Config.GetBool(Config.Key.enableLoop), currentSettings.interpFactor); });
if (currentSettings.model.FixedFactors.Count() > 0 && (currentSettings.interpFactor != (int)currentSettings.interpFactor || !currentSettings.model.FixedFactors.Contains(currentSettings.interpFactor.RoundToInt())))
Cancel($"The selected model does not support {currentSettings.interpFactor}x interpolation.\n\nSupported Factors: {currentSettings.model.GetFactorsString()}");
if (canceled) return;
Program.mainForm.SetStatus("Downloading models...");
await ModelDownloader.DownloadModelFiles(ai, currentSettings.model.Dir);
if (canceled) return;
currentlyUsingAutoEnc = Utils.CanUseAutoEnc(stepByStep, currentSettings);
IoUtils.CreateDir(outpath);
List<Task> tasks = new List<Task>();
if (ai.NameInternal == Implementations.rifeCuda.NameInternal)
tasks.Add(AiProcess.RunRifeCuda(currentSettings.framesFolder, currentSettings.interpFactor, currentSettings.model.Dir));
if (ai.NameInternal == Implementations.rifeNcnn.NameInternal)
tasks.Add(AiProcess.RunRifeNcnn(currentSettings.framesFolder, outpath, currentSettings.interpFactor, currentSettings.model.Dir));
if (ai.NameInternal == Implementations.rifeNcnnVs.NameInternal)
tasks.Add(AiProcess.RunRifeNcnnVs(currentSettings.framesFolder, outpath, currentSettings.interpFactor, currentSettings.model.Dir));
if (ai.NameInternal == Implementations.flavrCuda.NameInternal)
tasks.Add(AiProcess.RunFlavrCuda(currentSettings.framesFolder, currentSettings.interpFactor, currentSettings.model.Dir));
if (ai.NameInternal == Implementations.dainNcnn.NameInternal)
tasks.Add(AiProcess.RunDainNcnn(currentSettings.framesFolder, outpath, currentSettings.interpFactor, currentSettings.model.Dir, Config.GetInt(Config.Key.dainNcnnTilesize, 512)));
if (ai.NameInternal == Implementations.xvfiCuda.NameInternal)
tasks.Add(AiProcess.RunXvfiCuda(currentSettings.framesFolder, currentSettings.interpFactor, currentSettings.model.Dir));
if(ai.NameInternal == Implementations.ifrnetNcnn.NameInternal)
tasks.Add(AiProcess.RunIfrnetNcnn(currentSettings.framesFolder, outpath, currentSettings.interpFactor, currentSettings.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 (currentSettings == null)
return;
canceled = true;
Program.mainForm.SetStatus("Canceled.");
Program.mainForm.SetProgress(0);
AiProcess.Kill();
AvProcess.Kill();
if (!currentSettings.stepByStep && !Config.GetBool(Config.Key.keepTempFolder))
{
if (!BatchProcessing.busy && IoUtils.GetAmountOfFiles(Path.Combine(currentSettings.tempFolder, Paths.resumeDir), true) > 0)
{
DialogResult dialogResult = UiUtils.ShowMessageBox($"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(currentSettings.tempFolder); });
}
else
{
Task.Run(async () => { await IoUtils.TryDeleteIfExistsAsync(currentSettings.tempFolder); });
}
}
AutoEncode.busy = false;
Program.mainForm.SetWorking(false);
Program.mainForm.SetTab(Program.mainForm.interpOptsTab.Name);
Logger.LogIfLastLineDoesNotContainMsg("Canceled interpolation.");
if (!string.IsNullOrWhiteSpace(reason) && !noMsgBox)
UiUtils.ShowMessageBox($"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(currentSettings.tempFolder, true); });
}
catch (Exception e)
{
Logger.Log("Cleanup Error: " + e.Message, true);
if (retriesLeft > 0)
{
await Task.Delay(1000);
await Cleanup(ignoreKeepSetting, retriesLeft - 1, true);
}
}
}
}
}

View File

@@ -0,0 +1,144 @@
using Flowframes.Media;
using Flowframes.IO;
using System;
using System.IO;
using System.Threading.Tasks;
using Flowframes.MiscUtils;
using System.Windows.Forms;
using Flowframes.Ui;
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(currentSettings == null)
{
Logger.Log($"[SBS] Getting new current settings", true);
currentSettings = Program.mainForm.GetCurrentSettings();
}
else
{
Logger.Log($"[SBS] Updating current settings", true);
currentSettings = Program.mainForm.UpdateCurrentSettings(currentSettings);
}
currentSettings.RefreshAlpha();
currentSettings.stepByStep = true;
if (!InterpolateUtils.InputIsValid(currentSettings)) return; // General input checks
if (!InterpolateUtils.CheckPathValid(currentSettings.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(currentSettings.framesFolder)))
{
UiUtils.ShowMessageBox("Failed to delete existing frames folder - Make sure no file is opened in another program!", UiUtils.MessageType.Error);
return;
}
await GetFrames();
await PostProcessFrames(true);
}
public static async Task InterpolateStep()
{
if (!InterpolateUtils.CheckAiAvailable(currentSettings.ai, currentSettings.model)) return;
currentSettings.framesFolder = Path.Combine(currentSettings.tempFolder, Paths.framesDir);
if (IoUtils.GetAmountOfFiles(currentSettings.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(currentSettings.framesFolder, false, "*") < 2)
{
UiUtils.ShowMessageBox("There are no extracted frames that can be interpolated!\nDid you run the extraction step?", UiUtils.MessageType.Error);
return;
}
}
if (!(await IoUtils.TryDeleteIfExistsAsync(currentSettings.interpFolder)))
{
UiUtils.ShowMessageBox("Failed to delete existing frames folder - Make sure no file is opened in another program!", UiUtils.MessageType.Error);
return;
}
if (Config.GetBool(Config.Key.sbsAllowAutoEnc) && !(await InterpolateUtils.CheckEncoderValid())) return;
if (canceled) return;
Program.mainForm.SetStatus("Running AI...");
await RunAi(currentSettings.interpFolder, currentSettings.ai, true);
await Task.Run(async () => { await FrameRename.Unrename(); }); // Get timestamps back
Program.mainForm.SetProgress(0);
}
public static async Task CreateOutputVid()
{
if (IoUtils.GetAmountOfFiles(currentSettings.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(currentSettings.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(currentSettings.interpFolder, currentSettings.interpExt);
if (outFrames.Length > 0 && !IoUtils.CheckImageValid(outFrames[0]))
{
UiUtils.ShowMessageBox("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.", UiUtils.MessageType.Error);
return;
}
await Export.ExportFrames(currentSettings.interpFolder, currentSettings.outPath, currentSettings.outSettings, true);
}
public static async Task Reset()
{
DialogResult dialog = UiUtils.ShowMessageBox($"Are you sure you want to remove all temporary files?", "Are you sure?", MessageBoxButtons.YesNo);
if (dialog == DialogResult.Yes)
await Cleanup(true);
}
}
}

View File

@@ -0,0 +1,384 @@
using Flowframes.Media;
using Flowframes.Data;
using Flowframes.Forms;
using Flowframes.IO;
using Flowframes.Os;
using Flowframes.Ui;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
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.currentSettings.inPath);
string targetPath = Path.Combine(I.currentSettings.framesFolder, lastFrameNum.ToString().PadLeft(Padding.inputFrames, '0') + I.currentSettings.framesExt);
if (File.Exists(targetPath)) return;
Size res = IoUtils.GetImage(IoUtils.GetFilesSorted(I.currentSettings.framesFolder, false).First()).Size;
if (frameFolderInput)
{
string lastFramePath = IoUtils.GetFilesSorted(I.currentSettings.inPath, false).Last();
await FfmpegExtract.ExtractLastFrame(lastFramePath, targetPath, res);
}
else
{
await FfmpegExtract.ExtractLastFrame(I.currentSettings.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 = Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"), "Temp", "Flowframes");
int tempFolderLoc = Config.GetInt(Config.Key.tempFolderLoc);
switch (tempFolderLoc)
{
case 1:
basePath = inPath.GetParentDir();
break;
case 2:
basePath = outPath;
break;
case 3:
basePath = Paths.GetSessionDataPath();
break;
case 4:
string custPath = Config.Get(Config.Key.tempDirCustom);
if (IoUtils.IsDirValid(custPath))
{
basePath = custPath;
}
break;
}
string folderName = Path.GetFileNameWithoutExtension(inPath).StripBadChars().Remove(" ").Trunc(30, false) + ".tmp";
return Path.Combine(basePath, folderName);
}
public static bool InputIsValid(InterpSettings s)
{
try
{
bool passes = true;
bool isFile = !IoUtils.IsPathDirectory(s.inPath);
if ((passes && isFile && !IoUtils.IsFileValid(s.inPath)) || (!isFile && !IoUtils.IsDirValid(s.inPath)))
{
UiUtils.ShowMessageBox("Input path is not valid!");
passes = false;
}
if (passes && !IoUtils.IsDirValid(s.outPath))
{
UiUtils.ShowMessageBox("Output path is not valid!");
passes = false;
}
if (passes && s.tempFolder.StartsWith(@"\\"))
{
UiUtils.ShowMessageBox("Flowframes does not support UNC/Network paths as a temp folder!\nPlease use a local path instead.");
passes = false;
}
string fpsLimitValue = Config.Get(Config.Key.maxFps);
float fpsLimit = (fpsLimitValue.Contains("/") ? new Fraction(fpsLimitValue).GetFloat() : fpsLimitValue.GetFloat());
int maxFps = s.outSettings.Encoder.GetInfo().MaxFramerate;
if (passes && s.outFps.GetFloat() < 1f || (s.outFps.GetFloat() > maxFps && !(fpsLimit > 0 && fpsLimit <= maxFps)))
{
string imgSeqNote = isFile ? "" : "\n\nWhen using an image sequence as input, you always have to specify the frame rate manually.";
UiUtils.ShowMessageBox($"Invalid output frame rate ({s.outFps.GetFloat()}).\nMust be 1-{maxFps}. Either lower the interpolation factor or use the \"Maximum Output Frame Rate\" option.{imgSeqNote}");
passes = false;
}
float fpsLimitFloat = fpsLimitValue.GetFloat();
if (fpsLimitFloat > 0 && fpsLimitFloat < s.outFps.GetFloat())
Interpolate.InterpProgressMultiplier = s.outFps.GetFloat() / fpsLimitFloat;
else
Interpolate.InterpProgressMultiplier = 1f;
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, ModelCollection.ModelInfo model)
{
if (IoUtils.GetAmountOfFiles(Path.Combine(Paths.GetPkgPath(), ai.PkgDir), true) < 1)
{
UiUtils.ShowMessageBox("The selected AI is not installed!", UiUtils.MessageType.Error);
I.Cancel("Selected AI not available.", true);
return false;
}
if (model == null || model.Dir.Trim() == "")
{
UiUtils.ShowMessageBox("No valid AI model has been selected!", UiUtils.MessageType.Error);
I.Cancel("No valid model selected.", true);
return false;
}
if (I.currentSettings.ai.NameInternal.ToUpper().Contains("CUDA") && NvApi.gpuList.Count < 1)
{
UiUtils.ShowMessageBox("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.", UiUtils.MessageType.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.currentSettings.tempFolder))
{
UiUtils.ShowMessageBox("Failed to remove an existing temp folder of this video!\nMake sure you didn't open any frames in an editor.", UiUtils.MessageType.Error);
I.Cancel();
return false;
}
return true;
}
public static void ShowWarnings(float factor, AI ai)
{
if (Config.GetInt(Config.Key.cmdDebugMode) > 0)
Logger.Log($"Warning: The CMD window for interpolation is enabled. This will disable Auto-Encode and the progress bar!");
}
public static bool CheckPathValid(string path)
{
if (path.StartsWith(@"\\"))
{
UiUtils.ShowMessageBox("Input path is not valid.\nFlowframes does not support UNC/Network paths.");
I.Cancel();
return false;
}
if (IoUtils.IsPathDirectory(path))
{
if (!IoUtils.IsDirValid(path))
{
UiUtils.ShowMessageBox("Input directory is not valid.\nMake sure it still exists and hasn't been renamed or moved!");
I.Cancel();
return false;
}
}
else
{
if (!IsVideoValid(path))
{
UiUtils.ShowMessageBox("Input video file is not valid.\nMake sure it still exists and hasn't been renamed or moved!");
return false;
}
}
return true;
}
public static async Task<bool> CheckEncoderValid()
{
string enc = I.currentSettings.outSettings.Encoder.GetInfo().Name;
if (enc.ToLowerInvariant().Contains("nvenc") && !(await FfmpegCommands.IsEncoderCompatible(enc)))
{
UiUtils.ShowMessageBox("NVENC encoding is not available on your hardware!\nPlease use a different encoder.", UiUtils.MessageType.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 async Task<Size> GetOutputResolution(string inputPath, bool pad, bool print = false)
{
Size resolution = await GetMediaResolutionCached.GetSizeAsync(inputPath);
return GetOutputResolution(resolution, pad, print);
}
public static Size GetOutputResolution(Size inputRes, bool pad, bool print = false)
{
Size res = new Size(inputRes.Width, inputRes.Height);
int maxHeight = Config.GetInt(Config.Key.maxVidHeight);
int mod = pad ? FfmpegCommands.GetModulo() : 1;
float factor = res.Height > maxHeight ? (float)maxHeight / res.Height : 1f; // Calculate downscale factor if bigger than max, otherwise just use 1x
Logger.Log($"Un-rounded downscaled size: {(res.Width * factor).ToString("0.###")}x{(res.Height * factor).ToString("0.###")}", true);
int width = RoundDivisibleBy((res.Width * factor).RoundToInt(), mod);
int height = RoundDivisibleBy((res.Height * factor).RoundToInt(), mod);
res = new Size(width, height);
if (print && factor < 1f)
Logger.Log($"Video is bigger than the maximum - Downscaling to {width}x{height}.");
if (res != inputRes)
Logger.Log($"Scaled {inputRes.Width}x{inputRes.Height} to {res.Width}x{res.Height}", true);
return res;
}
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.ai.Piped)
{
Logger.Log($"Not Using AutoEnc: Using piped encoding.", true);
return false;
}
if (Config.GetInt(Config.Key.cmdDebugMode) > 0)
{
Logger.Log($"Not Using AutoEnc: CMD window is shown (cmdDebugMode > 0)", true);
return false;
}
if (current.outSettings.Format == Enums.Output.Format.Gif)
{
Logger.Log($"Not Using AutoEnc: Using GIF output", 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 UseUhd(await GetOutputResolution(I.currentSettings.inPath, false));
}
public static bool UseUhd(Size outputRes)
{
return outputRes.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.currentSettings.framesExt));
}
public static int GetRoundedInterpFramesPerInputFrame(float factor, bool roundDown = true)
{
if (roundDown)
return (int)Math.Floor(factor) - 1;
else
return factor.RoundToInt();
}
public static Fraction AskForFramerate(string mediaName, bool isImageSequence = true)
{
string text = $"Please enter an input frame rate to use for{(isImageSequence ? " the image sequence" : "")} '{mediaName.Trunc(80)}'.";
PromptForm form = new PromptForm("Enter Frame Rate", text, "15");
form.ShowDialog();
return new Fraction(form.EnteredText);
}
}
}

View File

@@ -0,0 +1,121 @@
using Flowframes.Data;
using Flowframes.IO;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Flowframes.MiscUtils;
using Flowframes.Ui;
using I = Flowframes.Interpolate;
namespace Flowframes.Main
{
class ResumeUtils
{
// public static bool resumeNextRun;
public static float timeBetweenSaves = 10;
public static int minFrames = 100;
public static int safetyDelayFrames = 50;
public static string resumeFilename = "resume.ini";
public static string interpSettingsFilename = "interpSettings.ini";
public static string filenameMapFilename = "frameFilenames.ini";
public static Stopwatch timeSinceLastSave = new Stopwatch();
public static void Save ()
{
if (timeSinceLastSave.IsRunning && timeSinceLastSave.ElapsedMilliseconds < (timeBetweenSaves * 1000f).RoundToInt()) return;
int frames = (int)Math.Round((float)InterpolationProgress.interpolatedInputFramesCount / I.currentSettings.interpFactor) - safetyDelayFrames;
if (frames < 1) return;
timeSinceLastSave.Restart();
Directory.CreateDirectory(Path.Combine(I.currentSettings.tempFolder, Paths.resumeDir));
SaveState(frames);
SaveInterpSettings();
SaveFilenameMap();
}
static void SaveState (int frames)
{
ResumeState state = new ResumeState(I.currentlyUsingAutoEnc, frames);
string filePath = Path.Combine(I.currentSettings.tempFolder, Paths.resumeDir, resumeFilename);
File.WriteAllText(filePath, state.ToString());
}
static async Task SaveFilenameMap ()
{
string filePath = Path.Combine(I.currentSettings.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.currentSettings.tempFolder, Paths.resumeDir, interpSettingsFilename);
File.WriteAllText(filepath, I.currentSettings.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.currentSettings.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();
}
}
}

View File

@@ -0,0 +1,134 @@
using Flowframes.Forms.Main;
using Flowframes.MiscUtils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using static Flowframes.AvProcess;
using static NmkdUtils.StringExtensions;
namespace Flowframes.Media
{
class AvOutputHandler
{
public static readonly string prefix = "[ffmpeg]";
public static void LogOutput(string line, ref string appendStr, string logFilename, LogMode logMode, bool showProgressBar)
{
if (Interpolate.canceled || string.IsNullOrWhiteSpace(line) || line.Trim().Length < 1)
return;
bool hidden = logMode == LogMode.Hidden;
if (HideMessage(line)) // Don't print certain warnings
hidden = true;
bool replaceLastLine = logMode == LogMode.OnlyLastLine;
if (line.Contains("time=") && (line.StartsWith("frame=") || line.StartsWith("size=")))
line = FormatUtils.BeautifyFfmpegStats(line);
appendStr += Environment.NewLine + line;
Logger.Log($"{prefix} {line}", hidden, replaceLastLine, logFilename);
if (!hidden && showProgressBar && line.Contains("Time:"))
{
Regex timeRegex = new Regex("(?<=Time:).*(?= )");
UpdateFfmpegProgress(timeRegex.Match(line).Value);
}
if (line.Contains("Unable to"))
{
Interpolate.Cancel($"Error: {line}");
return;
}
if (line.Contains("Could not open file"))
{
Interpolate.Cancel($"Error: {line}");
return;
}
if (line.Contains("No NVENC capable devices found") || line.MatchesWildcard("*nvcuda.dll*"))
{
Interpolate.Cancel($"Error: {line}\n\nMake sure you have an NVENC-capable Nvidia GPU.");
return;
}
if (line.Contains("not currently supported in container") || line.Contains("Unsupported codec id"))
{
Interpolate.Cancel($"Error: {line}\n\nIt looks like you are trying to copy a stream into a container that doesn't support this codec.");
return;
}
if (line.Contains("Subtitle encoding currently only possible from text to text or bitmap to bitmap"))
{
Interpolate.Cancel($"Error: {line}\n\nYou cannot encode image-based subtitles into text-based subtitles. Please use the Copy Subtitles option instead, with a compatible container.");
return;
}
if (line.Contains("Only VP8 or VP9 or AV1 video and Vorbis or Opus audio and WebVTT subtitles are supported for WebM"))
{
Interpolate.Cancel($"Error: {line}\n\nIt looks like you are trying to copy an unsupported stream into WEBM!");
return;
}
if (line.MatchesWildcard("*codec*not supported*"))
{
Interpolate.Cancel($"Error: {line}\n\nTry using a different codec.");
return;
}
if (line.Contains("GIF muxer supports only a single video GIF stream"))
{
Interpolate.Cancel($"Error: {line}\n\nYou tried to mux a non-GIF stream into a GIF file.");
return;
}
if (line.Contains("Width and height of input videos must be same"))
{
Interpolate.Cancel($"Error: {line}");
return;
}
}
public static void UpdateFfmpegProgress(string ffmpegTime)
{
try
{
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);
}
catch (Exception e)
{
Logger.Log($"Failed to get ffmpeg progress: {e.Message}", true);
}
}
static bool HideMessage(string msg)
{
string[] hiddenMsgs = new string[] { "can produce invalid output", "pixel format", "provided invalid", "Non-monotonous", "not enough frames to estimate rate", "invalid dropping", "message repeated" };
foreach (string str in hiddenMsgs)
if (msg.MatchesWildcard($"*{str}*"))
return true;
return false;
}
}
}

View File

@@ -0,0 +1,217 @@
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;
using Flowframes.Media;
using System.Windows.Input;
namespace Flowframes
{
class AvProcess
{
public static Process lastAvProcess;
public static Stopwatch timeSinceLastOutput = new Stopwatch();
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<string> RunFfmpeg(string args, LogMode logMode, bool reliableOutput = true, bool progressBar = false)
{
return await RunFfmpeg(args, "", logMode, defLogLevel, reliableOutput, progressBar);
}
public static async Task<string> RunFfmpeg(string args, LogMode logMode, string loglevel, bool reliableOutput = true, bool progressBar = false)
{
return await RunFfmpeg(args, "", logMode, loglevel, reliableOutput, progressBar);
}
public static async Task<string> RunFfmpeg(string args, string workingDir, LogMode logMode, bool reliableOutput = true, bool progressBar = false)
{
return await RunFfmpeg(args, workingDir, logMode, defLogLevel, reliableOutput, progressBar);
}
public static async Task<string> RunFfmpeg(string args, string workingDir, LogMode logMode, string loglevel, bool reliableOutput = true, bool progressBar = false)
{
bool show = Config.GetInt(Config.Key.cmdDebugMode) > 0;
string processOutput = "";
Process ffmpeg = OsUtils.NewProcess(!show);
NmkdStopwatch timeSinceLastOutput = new NmkdStopwatch();
lastAvProcess = ffmpeg;
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 {beforeArgs} {args}";
if (logMode != LogMode.Hidden) Logger.Log("Running FFmpeg...", false);
Logger.Log($"ffmpeg {beforeArgs} {args}", true, false, "ffmpeg");
if (!show)
{
ffmpeg.OutputDataReceived += (sender, outLine) => { AvOutputHandler.LogOutput(outLine.Data, ref processOutput, "ffmpeg", logMode, progressBar); timeSinceLastOutput.Sw.Restart(); };
ffmpeg.ErrorDataReceived += (sender, outLine) => { AvOutputHandler.LogOutput(outLine.Data, ref processOutput, "ffmpeg", logMode, progressBar); timeSinceLastOutput.Sw.Restart(); };
}
ffmpeg.Start();
ffmpeg.PriorityClass = ProcessPriorityClass.BelowNormal;
if (!show)
{
ffmpeg.BeginOutputReadLine();
ffmpeg.BeginErrorReadLine();
}
while (!ffmpeg.HasExited) await Task.Delay(10);
while (reliableOutput && timeSinceLastOutput.ElapsedMs < 200) await Task.Delay(50);
if (progressBar)
Program.mainForm.SetProgress(0);
return processOutput;
}
public static string RunFfmpegSync(string args, string workingDir = "", LogMode logMode = LogMode.Hidden, string loglevel = "warning")
{
Process ffmpeg = OsUtils.NewProcess(true);
lastAvProcess = ffmpeg;
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 {beforeArgs} {args}";
if (logMode != LogMode.Hidden) Logger.Log("Running FFmpeg...", false);
Logger.Log($"ffmpeg {beforeArgs} {args}", true, false, "ffmpeg");
ffmpeg.StartInfo.Arguments += " 2>&1";
ffmpeg.Start();
ffmpeg.PriorityClass = ProcessPriorityClass.BelowNormal;
string output = ffmpeg.StandardOutput.ReadToEnd();
ffmpeg.WaitForExit();
Logger.Log($"Synchronous ffmpeg output:\n{output}", true, false, "ffmpeg");
return output;
}
public static string GetFfmpegDefaultArgs(string loglevel = "warning")
{
return $"-hide_banner -stats -loglevel {loglevel} -y";
}
public class FfprobeSettings
{
public string Args { get; set; } = "";
public LogMode LoggingMode { get; set; } = LogMode.Hidden;
public string LogLevel { get; set; } = "panic";
public bool SetBusy { get; set; } = false;
}
public static async Task<string> RunFfprobe(FfprobeSettings settings, bool asyncOutput = false)
{
bool show = Config.GetInt(Config.Key.cmdDebugMode) > 0;
string processOutput = "";
Process ffprobe = OsUtils.NewProcess(!show);
NmkdStopwatch timeSinceLastOutput = new NmkdStopwatch();
bool concat = settings.Args.Split(" \"").Last().Remove("\"").Trim().EndsWith(".concat");
string args = $"-v {settings.LogLevel} {(concat ? "-f concat -safe 0 " : "")}{settings.Args}";
ffprobe.StartInfo.Arguments = $"{GetCmdArg()} cd /D {GetAvDir().Wrap()} & ffprobe {args}";
if (settings.LoggingMode != LogMode.Hidden) Logger.Log("Running FFprobe...", false);
Logger.Log($"ffprobe {args}", true, false, "ffmpeg");
if (!asyncOutput)
return await Task.Run(() => OsUtils.GetProcStdOut(ffprobe));
if (!show)
{
string[] ignore = new string[0];
ffprobe.OutputDataReceived += (sender, outLine) => { processOutput += outLine + Environment.NewLine; };
ffprobe.ErrorDataReceived += (sender, outLine) => { processOutput += outLine + Environment.NewLine; };
}
ffprobe.Start();
ffprobe.PriorityClass = ProcessPriorityClass.BelowNormal;
if (!show)
{
ffprobe.BeginOutputReadLine();
ffprobe.BeginErrorReadLine();
}
while (!ffprobe.HasExited) await Task.Delay(10);
while (timeSinceLastOutput.ElapsedMs < 200) await Task.Delay(50);
return processOutput;
}
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;
}
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);
}
}
}

View File

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

View File

@@ -0,0 +1,92 @@
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
{
public static async Task MergeStreamsFromInput (string inputVideo, string interpVideo, string tempFolder, bool shortest)
{
if (!File.Exists(inputVideo) && !I.currentSettings.inputIsFrames)
{
Logger.Log("Warning: Input video file not found, can't copy audio/subtitle streams to output video!");
return;
}
Data.Enums.Output.Format format = I.currentSettings.outSettings.Format;
if (format == Data.Enums.Output.Format.Gif || format == Data.Enums.Output.Format.Images)
{
Logger.Log("Warning: Output format does not support audio.");
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.currentSettings.outSettings.Format, GetAudioCodecs(inputVideo));
bool slowmo = I.currentSettings.outItsScale != 0 && I.currentSettings.outItsScale != 1;
string audioArgs = audioCompat && !slowmo ? "" : await Utils.GetAudioFallbackArgs(inputVideo, I.currentSettings.outSettings.Format, I.currentSettings.outItsScale);
if (!audioCompat && !slowmo)
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.currentSettings.outSettings.Format == Data.Enums.Output.Format.Mkv;
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
}
}
}
}

View File

@@ -0,0 +1,312 @@
using Flowframes.Media;
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.MiscUtils;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using static Flowframes.AvProcess;
namespace Flowframes
{
class FfmpegCommands
{
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 enum MpDecSensitivity { Normal = 4, High = 20, VeryHigh = 32, Extreme = 40 }
public static string GetMpdecimate (int sensitivity = 4, bool wrap = true)
{
string mpd = $"mpdecimate=hi=64*1024:lo=64*{sensitivity}:frac=1.0";
return wrap ? mpd.Wrap() : mpd;
}
public static string GetMpdecimate(bool wrap = true)
{
int mpdValIndex = Config.GetInt(Config.Key.mpdecimateMode);
var mpdVal = ((MpDecSensitivity[])Enum.GetValues(typeof(MpDecSensitivity)))[mpdValIndex];
string mpd = $"mpdecimate=hi=64*1024:lo=64*{(int)mpdVal}:frac=1.0";
return wrap ? mpd.Wrap() : mpd;
}
public static int GetModulo ()
{
if (Interpolate.currentSettings.ai.NameInternal == Implementations.flavrCuda.NameInternal)
return 8;
return Interpolate.currentSettings.outSettings.Encoder.GetInfo().Modulo;
}
public static string GetPadFilter ()
{
int mod = GetModulo();
if (mod < 2)
return "";
return $"pad=width=ceil(iw/{mod})*{mod}:height=ceil(ih/{mod})*{mod}: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.RenameExistingFileOrDir(outPath);
string loopStr = (looptimes > 0) ? $"-stream_loop {looptimes}" : "";
string vfrFilename = Path.GetFileName(concatFile);
string args = $" {loopStr} -f concat -i {vfrFilename} -fps_mode cfr -c copy -movflags +faststart -fflags +genpts {outPath.Wrap()}";
await RunFfmpeg(args, concatFile.GetParentDir(), LogMode.Hidden);
}
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.RenameExistingFileOrDir(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 async Task<long> GetDurationMs(string inputFile)
{
Logger.Log($"GetDuration({inputFile}) - Reading Duration using ffprobe.", true, false, "ffmpeg");
string args = $"-select_streams v:0 -show_entries format=duration -of csv=s=x:p=0 -sexagesimal {inputFile.Wrap()}";
FfprobeSettings settings = new FfprobeSettings() { Args = args };
string output = await RunFfprobe(settings);
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 GetVideoInfo.GetFfprobeInfoAsync(inputFile, GetVideoInfo.FfprobeMode.ShowStreams, "r_frame_rate");
string fpsStr = ffprobeOutput.SplitIntoLines().First();
string[] numbers = fpsStr.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 GetVideoInfo.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 - Trying ffprobe packet counting first (fastest).", true, false, "ffmpeg");
int frames = await ReadFrameCountFfprobePacketCount(inputFile); // Try reading frame count with ffprobe packet counting
if (frames > 0) return frames;
Logger.Log($"GetFrameCountAsync - Trying ffmpeg demuxing.", true, false, "ffmpeg");
frames = await ReadFrameCountFfmpegAsync(inputFile); // Try reading frame count with ffmpeg
if (frames > 0) return frames;
Logger.Log($"GetFrameCountAsync - Trying ffprobe demuxing.", true, false, "ffmpeg");
frames = await ReadFrameCountFfprobe(inputFile); // Try reading frame count with ffprobe decoding
if (frames > 0) return frames;
Logger.Log("Failed to get total frame count of video.", true);
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;
}
public static async Task<int> ReadFrameCountFfprobePacketCount(string filePath)
{
FfprobeSettings settings = new FfprobeSettings() { Args = $"-select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0 {filePath.Wrap()}", LoggingMode = LogMode.Hidden, LogLevel = "error" };
string output = await RunFfprobe(settings);
string[] lines = output.SplitIntoLines().Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
if (lines == null || lines.Length < 1)
return 0;
return lines.Last().GetInt();
}
public static async Task<int> ReadFrameCountFfprobe(string filePath)
{
FfprobeSettings s = new FfprobeSettings() { Args = $"-threads 0 -select_streams v:0 -show_entries stream=nb_frames -of default=noprint_wrappers=1 {filePath.Wrap()}", LoggingMode = LogMode.Hidden, LogLevel = "panic" };
string info = await RunFfprobe(s);
string[] entries = info.SplitIntoLines();
try
{
foreach (string entry in entries)
{
if (entry.Contains("nb_frames="))
return entry.GetInt();
}
}
catch { }
return -1;
}
public static async Task<int> ReadFrameCountFfmpegAsync(string filePath)
{
string args = $"{filePath.GetConcStr()} -i {filePath.Wrap()} -map 0:v:0 -c copy -f null - ";
string info = await RunFfmpeg(args, LogMode.Hidden, "panic");
try
{
string[] lines = info.SplitIntoLines();
string lastLine = lines.Last().ToLowerInvariant();
return lastLine.Substring(0, lastLine.IndexOf("fps")).GetInt();
}
catch
{
return -1;
}
}
public static async Task<VidExtraData> GetVidExtraInfo(string inputFile)
{
string ffprobeOutput = await GetVideoInfo.GetFfprobeInfoAsync(inputFile, GetVideoInfo.FfprobeMode.ShowBoth);
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=1920x1080 -vframes 1 -c:v {enc} -f null -";
string output = await RunFfmpeg(args, LogMode.Hidden);
return !output.SplitIntoLines().Where(l => !l.Lower().StartsWith("frame") && l.IsNotEmpty()).Any();
}
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);
}
}
}

View File

@@ -0,0 +1,154 @@
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.MiscUtils;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using static NmkdUtils.StringExtensions;
using static Flowframes.AvProcess;
using Padding = Flowframes.Data.Padding;
using Utils = Flowframes.Media.FfmpegUtils;
namespace Flowframes.Media
{
partial class FfmpegEncode : FfmpegCommands
{
public static async Task FramesToVideo(string framesFile, string outPath, OutputSettings settings, Fraction fps, Fraction resampleFps, float itsScale, 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.RenameExistingFileOrDir(outPath);
Directory.CreateDirectory(outPath.GetParentDir());
string[] encArgs = Utils.GetEncArgs(settings, (Interpolate.currentSettings.ScaledResolution.IsEmpty ? Interpolate.currentSettings.InputResolution : Interpolate.currentSettings.ScaledResolution), Interpolate.currentSettings.outFps.GetFloat());
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 args = "";
for (int i = 0; i < encArgs.Length; i++)
{
string pre = i == 0 ? "" : $" && ffmpeg {AvProcess.GetFfmpegDefaultArgs()}";
string post = (i == 0 && encArgs.Length > 1) ? $"-f null -" : outPath.Wrap();
args += $"{pre} {await GetFfmpegExportArgsIn(fps, itsScale)} {inArg} {encArgs[i]} {await GetFfmpegExportArgsOut(resampleFps, extraData, settings, isChunk)} {post} ";
}
await RunFfmpeg(args, framesFile.GetParentDir(), logMode, !isChunk);
IoUtils.TryDeleteIfExists(linksDir);
}
public static async Task<string> GetFfmpegExportArgsIn(Fraction fps, float itsScale)
{
var args = new List<string>();
fps = fps / new Fraction(itsScale);
args.Add($"-r {fps}");
return string.Join(" ", args);
}
public static async Task<string> GetFfmpegExportArgsOut(Fraction resampleFps, VidExtraData extraData, OutputSettings settings, bool isChunk = false)
{
var beforeArgs = new List<string>();
var filters = new List<string>();
var extraArgs = new List<string> { Config.Get(Config.Key.ffEncArgs) };
if (resampleFps.GetFloat() >= 0.1f)
filters.Add($"fps={resampleFps}");
if (Config.GetBool(Config.Key.keepColorSpace) && extraData.HasAllValues())
{
Logger.Log($"Using color data: Space {extraData.colorSpace}; Primaries {extraData.colorPrimaries}; Transfer {extraData.colorTransfer}; Range {extraData.colorRange}", true, false, "ffmpeg");
extraArgs.Add($"-colorspace {extraData.colorSpace} -color_primaries {extraData.colorPrimaries} -color_trc {extraData.colorTransfer} -color_range:v {extraData.colorRange.Wrap()}");
}
if (!string.IsNullOrWhiteSpace(extraData.displayRatio) && !extraData.displayRatio.MatchesWildcard("*N/A*"))
extraArgs.Add($"-aspect {extraData.displayRatio}");
if (!isChunk && settings.Format == Enums.Output.Format.Mp4 || settings.Format == Enums.Output.Format.Mov)
extraArgs.Add($"-movflags +faststart");
if (settings.Format == Enums.Output.Format.Gif)
{
string dither = Config.Get(Config.Key.gifDitherType).Split(' ').First();
string palettePath = Path.Combine(Paths.GetSessionDataPath(), "palette.png");
string paletteFilter = $"[1:v]paletteuse=dither={dither}";
int colors = OutputUtils.GetGifColors(ParseUtils.GetEnum<Enums.Encoding.Quality.GifColors>(settings.Quality, true, Strings.VideoQuality));
await FfmpegExtract.GeneratePalette(Interpolate.currentMediaFile.ImportPath, palettePath, colors);
if (File.Exists(palettePath))
{
beforeArgs.Add($"-i {palettePath.Wrap()}");
filters.Add(paletteFilter);
}
}
else if (settings.Encoder == Enums.Encoding.Encoder.Exr)
{
if(Interpolate.currentMediaFile.Format.Upper() != "EXR")
filters.Add($"zscale=transfer=linear,format={settings.PixelFormat.ToString().Lower()}".Wrap());
}
filters.Add(GetPadFilter());
filters = filters.Where(f => f.IsNotEmpty()).ToList();
return filters.Count > 0 ?
$"{string.Join(" ", beforeArgs)} -filter_complex [0:v]{string.Join("[vf],[vf]", filters.Where(f => !string.IsNullOrWhiteSpace(f)))}[vf] -map [vf] {string.Join(" ", extraArgs)}" :
$"{string.Join(" ", beforeArgs)} {string.Join(" ", extraArgs)}";
}
public static string GetConcatFileExt(string concatFilePath)
{
return Path.GetExtension(File.ReadAllLines(concatFilePath).FirstOrDefault().Split('\'')[1]);
}
public static async Task FramesToFrames(string framesFile, string outDir, int startNo, Fraction fps, Fraction resampleFps, Enums.Encoding.Encoder format = Enums.Encoding.Encoder.Png, int lossyQ = 1, 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 sn = $"-start_number {startNo}";
string rate = fps.ToString().Replace(",", ".");
string vf = (resampleFps.GetFloat() < 0.1f) ? "" : $"-vf fps=fps={resampleFps}";
string compression = format == Enums.Encoding.Encoder.Png ? pngCompr : $"-q:v {lossyQ}";
string codec = format == Enums.Encoding.Encoder.Webp ? "-c:v libwebp" : ""; // Specify libwebp to avoid putting all frames into single animated WEBP
string args = $"-r {rate} {inArg} {codec} {compression} {sn} {vf} -fps_mode passthrough \"{outDir}/%{Padding.interpFrames}d.{format.GetInfo().OverideExtension}\"";
await RunFfmpeg(args, framesFile.GetParentDir(), logMode, "error", true);
IoUtils.TryDeleteIfExists(linksDir);
}
public static async Task FramesToGifConcat(string framesFile, string outPath, Fraction rate, bool palette, int colors, Fraction resampleFps, float itsScale, 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);
rate = rate / new Fraction(itsScale);
string args = $"-f concat -r {rate} -i {framesFilename.Wrap()} -gifflags -offsetting {vf} {extraArgs} {outPath.Wrap()}";
await RunFfmpeg(args, framesFile.GetParentDir(), LogMode.OnlyLastLine, "error");
}
}
}

View File

@@ -0,0 +1,350 @@
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 static Flowframes.AvProcess;
using Padding = Flowframes.Data.Padding;
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.GetSessionDataPath(), "png-scndetect-concat-temp.ini");
FfmpegUtils.CreateConcatFile(inPath, concatFile, Filetypes.imagesInterpCompat.ToList());
inArg = $"-f concat -safe 0 -i {concatFile.Wrap()}";
}
string scnDetect = $"-vf \"select='gt(scene,{Config.GetFloatString(Config.Key.scnDetectValue)})'\"";
string rateArg = (rate.GetFloat() > 0) ? $"-fps_mode cfr -r {rate}" : "-fps_mode passthrough";
string args = $"{GetTrimArg(true)} {inArg} {GetImgArgs(format)} {rateArg} {scnDetect} -frame_pts 1 -s 256x144 {GetTrimArg(false)} \"{outDir}/%{Padding.inputFrames}d{format}\"";
LogMode logMode = Interpolate.currentMediaFile.FrameCount > 50 ? LogMode.OnlyLastLine : LogMode.Hidden;
await RunFfmpeg(args, logMode, inputIsFrames ? "panic" : "warning", true);
bool hiddenLog = Interpolate.currentMediaFile.FrameCount <= 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.Lower().Remove(".").Replace("jpeg", "jpg");
string pixFmt = "yuv420p";
if (Interpolate.currentMediaFile != null && Interpolate.currentMediaFile.VideoStreams.Any())
{
pixFmt = Interpolate.currentMediaFile.VideoStreams.First().PixelFormat;
}
bool inputHighBitDepth = pixFmt.Contains("p10") || pixFmt.Contains("p16");
bool outputHighBitDepth = Interpolate.currentSettings.outSettings.PixelFormat.ToString().Lower().Contains("p10");
string args = "";
if (extension == "png")
{
pixFmt = alpha ? "rgba" : "rgb24"; // PNG can't use YUV so we overwrite it with RGB
args = pngCompr;
}
else if (extension == "jpg")
{
// Fallback to YUV420P if not in list of supported formats
if (!new[] { "yuv420p", "yuv422p", "yuv444p" }.Contains(pixFmt.Replace("yuvj", "yuv")))
{
pixFmt = "yuv420p";
}
args = $"-q:v 1 -qmin 1 -color_range pc";
}
else if (extension == "tiff")
{
// Fallback to YUV420P if not in list of supported formats
if (!new[] { "rgb24", "rgb48le", "pal8", "rgba", "yuv420p", "yuv422p", "yuv440p", "yuv444p" }.Contains(pixFmt))
{
pixFmt = inputHighBitDepth && outputHighBitDepth ? "rgb48le" : "yuv420p";
}
}
else if (extension == "webp")
{
// Fallback to YUV420P if not in list of supported formats
if (!new[] { "bgra", "yuv420p", "yuva420p" }.Contains(pixFmt))
{
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 ? GetMpdecimate(true) : "";
string filters = FormatUtils.ConcatStrings(new[] { GetPadFilter(), mpStr });
string vf = filters.Length > 2 ? $"-vf {filters}" : "";
bool allowCfr = rate.GetFloat() > 0 && !deDupe && Path.GetExtension(inputFile).Lower() != ".gif"; // Forcing CFR on GIFs causes issues // TODO: Maybe never use CFR???
string rateArg = allowCfr ? $" -fps_mode cfr -r {rate}" : "-fps_mode passthrough";
string args = $"{GetTrimArg(true)} -itsscale {Interpolate.currentMediaFile.VideoStreams.First().FpsInfo.VfrRatio} -i {inputFile.Wrap()} {GetImgArgs(format, true, alpha)} {rateArg} -frame_pts 1 {vf} {sizeStr} {GetTrimArg(false)} \"{framesDir}/%{Padding.inputFrames}d{format}\""; LogMode logMode = Interpolate.currentMediaFile.FrameCount > 50 ? LogMode.OnlyLastLine : LogMode.Hidden;
await RunFfmpeg(args, logMode, 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($"Loading 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))
{
Logger.Log($"Symlink Import enabled, creating symlinks for input frames...", true);
Dictionary<string, string> moveFromToSwapped = moveFromTo.ToDictionary(x => x.Value, x => x.Key); // From/To => To/From (Link/Target)
await Symlinks.CreateSymlinksParallel(moveFromToSwapped);
}
else
{
Logger.Log($"Symlink Import disabled, copying input frames...", true);
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;
}
int sampleCount = Config.GetInt(Config.Key.imgSeqSampleCount, 10);
Image[] randomSamples = files.OrderBy(arg => Guid.NewGuid()).Take(sampleCount).Select(x => IoUtils.GetImage(x.FullName)).ToArray();
if(files.All(f => f != null))
{
bool allSameSize = randomSamples.All(i => i.Size == randomSamples.First().Size);
if (!allSameSize)
{
Logger.Log($"Sequence not compatible: Not all images have the same dimensions.", true);
return false;
}
int div = GetModulo();
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}.", true);
return false;
}
bool allSmallEnough = randomSamples.All(i => (i.Height <= maxHeight));
if (!allSmallEnough)
{
Logger.Log($"Sequence not compatible: Image dimensions above max size.", 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).", true);
// return false;
// }
}
Interpolate.currentSettings.framesExt = files.First().Extension;
Logger.Log($"Sequence compatible!", 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.GetSessionDataPath(), "import-concat-temp.ini");
FfmpegUtils.CreateConcatFile(inPath, concatFile, Filetypes.imagesInterpCompat.ToList());
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} -fps_mode passthrough -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");
}
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).ToLowerInvariant() == ".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);
}
public static async Task ExtractSingleFrame(string inputFile, string outputPath, int frameNum)
{
bool isPng = (Path.GetExtension(outputPath).ToLowerInvariant() == ".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);
}
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).ToLowerInvariant() == ".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);
}
public static async Task GeneratePalette(string inputFile, string outputPath, int colors = 256)
{
string args = $"-i {inputFile.Wrap()} -vf palettegen={colors} {outputPath.Wrap()}";
await Task.Run(() => AvProcess.RunFfmpegSync(args));
}
}
}

View File

@@ -0,0 +1,592 @@
using Flowframes.Data;
using Flowframes.Data.Streams;
using Flowframes.IO;
using Flowframes.MiscUtils;
using Flowframes.Os;
using Flowframes.Properties;
using ImageMagick;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using static Flowframes.Data.Enums.Encoding;
using static Flowframes.Media.GetVideoInfo;
using Stream = Flowframes.Data.Streams.Stream;
using static NmkdUtils.StringExtensions;
namespace Flowframes.Media
{
class FfmpegUtils
{
private readonly static FfprobeMode showStreams = FfprobeMode.ShowStreams;
private readonly static FfprobeMode showFormat = FfprobeMode.ShowFormat;
public static List<Encoder> CompatibleHwEncoders = new List<Encoder>();
public static bool NvencSupportsBFrames = false;
public static async Task<int> GetStreamCount(string path)
{
Logger.Log($"GetStreamCount({path})", true);
string output = await GetFfmpegInfoAsync(path, "Stream #0:");
if (string.IsNullOrWhiteSpace(output.Trim()))
return 0;
return output.SplitIntoLines().Where(x => x.MatchesWildcard("*Stream #0:*: *: *")).Count();
}
public static async Task<List<Stream>> GetStreams(string path, bool progressBar, int streamCount, Fraction? defaultFps, bool countFrames)
{
List<Stream> streamList = new List<Stream>();
try
{
if (defaultFps == null)
defaultFps = new Fraction(30, 1);
string output = await GetFfmpegInfoAsync(path, "Stream #0:");
string[] streams = output.SplitIntoLines().Where(x => x.MatchesWildcard("*Stream #0:*: *: *")).ToArray();
foreach (string streamStr in streams)
{
try
{
int idx = streamStr.Split(':')[1].Split('[')[0].Split('(')[0].GetInt();
bool def = await GetFfprobeInfoAsync(path, showStreams, "DISPOSITION:default", idx) == "1";
if (progressBar)
Program.mainForm.SetProgress(FormatUtils.RatioInt(idx + 1, streamCount));
if (streamStr.Contains(": Video:"))
{
string lang = await GetFfprobeInfoAsync(path, showStreams, "TAG:language", idx);
string title = await GetFfprobeInfoAsync(path, showStreams, "TAG:title", idx);
string codec = await GetFfprobeInfoAsync(path, showStreams, "codec_name", idx);
string codecLong = await GetFfprobeInfoAsync(path, showStreams, "codec_long_name", idx);
string pixFmt = (await GetFfprobeInfoAsync(path, showStreams, "pix_fmt", idx)).ToUpper();
int kbits = (await GetFfprobeInfoAsync(path, showStreams, "bit_rate", idx)).GetInt() / 1024;
Size res = await GetMediaResolutionCached.GetSizeAsync(path);
Size sar = SizeFromString(await GetFfprobeInfoAsync(path, showStreams, "sample_aspect_ratio", idx));
Size dar = SizeFromString(await GetFfprobeInfoAsync(path, showStreams, "display_aspect_ratio", idx));
int frameCount = countFrames ? await GetFrameCountCached.GetFrameCountAsync(path) : 0;
FpsInfo fps = await GetFps(path, streamStr, idx, (Fraction)defaultFps, frameCount);
VideoStream vStream = new VideoStream(lang, title, codec, codecLong, pixFmt, kbits, res, sar, dar, fps, frameCount);
vStream.Index = idx;
vStream.IsDefault = def;
Logger.Log($"Added video stream: {vStream}", true);
streamList.Add(vStream);
continue;
}
if (streamStr.Contains(": Audio:"))
{
string lang = await GetFfprobeInfoAsync(path, showStreams, "TAG:language", idx);
string title = await GetFfprobeInfoAsync(path, showStreams, "TAG:title", idx);
string codec = await GetFfprobeInfoAsync(path, showStreams, "codec_name", idx);
string profile = await GetFfprobeInfoAsync(path, showStreams, "profile", idx);
if (codec.ToLowerInvariant() == "dts" && profile != "unknown") codec = profile;
string codecLong = await GetFfprobeInfoAsync(path, showStreams, "codec_long_name", idx);
int kbits = (await GetFfprobeInfoAsync(path, showStreams, "bit_rate", idx)).GetInt() / 1024;
int sampleRate = (await GetFfprobeInfoAsync(path, showStreams, "sample_rate", idx)).GetInt();
int channels = (await GetFfprobeInfoAsync(path, showStreams, "channels", idx)).GetInt();
string layout = (await GetFfprobeInfoAsync(path, showStreams, "channel_layout", idx));
AudioStream aStream = new AudioStream(lang, title, codec, codecLong, kbits, sampleRate, channels, layout);
aStream.Index = idx;
aStream.IsDefault = def;
Logger.Log($"Added audio stream: {aStream}", true);
streamList.Add(aStream);
continue;
}
if (streamStr.Contains(": Subtitle:"))
{
string lang = await GetFfprobeInfoAsync(path, showStreams, "TAG:language", idx);
string title = await GetFfprobeInfoAsync(path, showStreams, "TAG:title", idx);
string codec = await GetFfprobeInfoAsync(path, showStreams, "codec_name", idx);
string codecLong = await GetFfprobeInfoAsync(path, showStreams, "codec_long_name", idx);
bool bitmap = await IsSubtitleBitmapBased(path, idx, codec);
SubtitleStream sStream = new SubtitleStream(lang, title, codec, codecLong, bitmap);
sStream.Index = idx;
sStream.IsDefault = def;
Logger.Log($"Added subtitle stream: {sStream}", true);
streamList.Add(sStream);
continue;
}
if (streamStr.Contains(": Data:"))
{
string codec = await GetFfprobeInfoAsync(path, showStreams, "codec_name", idx);
string codecLong = await GetFfprobeInfoAsync(path, showStreams, "codec_long_name", idx);
DataStream dStream = new DataStream(codec, codecLong);
dStream.Index = idx;
dStream.IsDefault = def;
Logger.Log($"Added data stream: {dStream}", true);
streamList.Add(dStream);
continue;
}
if (streamStr.Contains(": Attachment:"))
{
string codec = await GetFfprobeInfoAsync(path, showStreams, "codec_name", idx);
string codecLong = await GetFfprobeInfoAsync(path, showStreams, "codec_long_name", idx);
string filename = await GetFfprobeInfoAsync(path, showStreams, "TAG:filename", idx);
string mimeType = await GetFfprobeInfoAsync(path, showStreams, "TAG:mimetype", idx);
AttachmentStream aStream = new AttachmentStream(codec, codecLong, filename, mimeType);
aStream.Index = idx;
aStream.IsDefault = def;
Logger.Log($"Added attachment stream: {aStream}", true);
streamList.Add(aStream);
continue;
}
Logger.Log($"Unknown stream (not vid/aud/sub/dat/att): {streamStr}", true);
Stream stream = new Stream { Codec = "Unknown", CodecLong = "Unknown", Index = idx, IsDefault = def, Type = Stream.StreamType.Unknown };
streamList.Add(stream);
}
catch (Exception e)
{
Logger.Log($"Error scanning stream: {e.Message}\n{e.StackTrace}");
}
}
}
catch (Exception e)
{
Logger.Log($"GetStreams Exception: {e.Message}\n{e.StackTrace}", true);
}
Logger.Log($"Video Streams: {string.Join(", ", streamList.Where(x => x.Type == Stream.StreamType.Video).Select(x => string.IsNullOrWhiteSpace(x.Title) ? "No Title" : x.Title))}", true);
Logger.Log($"Audio Streams: {string.Join(", ", streamList.Where(x => x.Type == Stream.StreamType.Audio).Select(x => string.IsNullOrWhiteSpace(x.Title) ? "No Title" : x.Title))}", true);
Logger.Log($"Subtitle Streams: {string.Join(", ", streamList.Where(x => x.Type == Stream.StreamType.Subtitle).Select(x => string.IsNullOrWhiteSpace(x.Title) ? "No Title" : x.Title))}", true);
if (progressBar)
Program.mainForm.SetProgress(0);
return streamList;
}
private static async Task<FpsInfo> GetFps(string path, string streamStr, int streamIdx, Fraction defaultFps, int frameCount)
{
if (path.IsConcatFile())
return new FpsInfo(defaultFps);
if (streamStr.Contains("fps, ") && streamStr.Contains(" tbr"))
{
string fps = streamStr.Split(", ").Where(s => s.Contains(" fps")).First().Trim().Split(' ')[0];
string tbr = streamStr.Split("fps, ")[1].Split(" tbr")[0].Trim();
long durationMs = Interpolate.currentMediaFile.DurationMs;
float fpsCalc = (float)frameCount / (durationMs / 1000f);
fpsCalc = (float)Math.Round(fpsCalc, 5);
var info = new FpsInfo(new Fraction(fps.GetFloat())); // Set both true FPS and average FPS to this number for now
Logger.Log($"FPS: {fps} - TBR: {tbr} - Est. FPS: {fpsCalc.ToString("0.#####")}", true);
if (tbr != fps)
{
info.SpecifiedFps = new Fraction(tbr); // Change FPS to TBR if they mismatch
}
float fpsEstTolerance = GetFpsEstimationTolerance(durationMs);
if (Math.Abs(fps.GetFloat() - fpsCalc) > fpsEstTolerance)
{
Logger.Log($"Detected FPS {fps} is not within tolerance (+-{fpsEstTolerance}) of calculated FPS ({fpsCalc}), using estimated FPS.", true);
info.Fps = new Fraction(fpsCalc); // Change true FPS to the estimated FPS if the estimate does not match the specified FPS
}
return info;
}
return new FpsInfo(await IoUtils.GetVideoFramerate(path));
}
private static float GetFpsEstimationTolerance (long videoDurationMs)
{
if (videoDurationMs < 300) return 5.0f;
if (videoDurationMs < 1000) return 2.5f;
if (videoDurationMs < 2500) return 1.0f;
if (videoDurationMs < 5000) return 0.75f;
if (videoDurationMs < 10000) return 0.5f;
if (videoDurationMs < 20000) return 0.25f;
return 0.1f;
}
public static async Task<bool> IsSubtitleBitmapBased(string path, int streamIndex, string codec = "")
{
if (codec == "ssa" || codec == "ass" || codec == "mov_text" || codec == "srt" || codec == "subrip" || codec == "text" || codec == "webvtt")
return false;
if (codec == "dvdsub" || codec == "dvd_subtitle" || codec == "pgssub" || codec == "hdmv_pgs_subtitle" || codec.StartsWith("dvb_"))
return true;
// If codec was not listed above, manually check if it's compatible by trying to encode it:
//string ffmpegCheck = await GetFfmpegOutputAsync(path, $"-map 0:{streamIndex} -c:s srt -t 0 -f null -");
//return ffmpegCheck.Contains($"encoding currently only possible from text to text or bitmap to bitmap");
return false;
}
public static string[] GetEncArgs(OutputSettings settings, Size res, float fps, bool forceSinglePass = false) // Array contains as many entries as there are encoding passes.
{
Encoder enc = settings.Encoder;
int keyint = 10;
var args = new List<string>();
EncoderInfoVideo info = OutputUtils.GetEncoderInfoVideo(enc);
PixelFormat pixFmt = settings.PixelFormat;
if (settings.Format == Enums.Output.Format.Realtime)
pixFmt = PixelFormat.Yuv444P16Le;
if (pixFmt == (PixelFormat)(-1)) // No pixel format set in GUI
pixFmt = info.PixelFormatDefault != (PixelFormat)(-1) ? info.PixelFormatDefault : info.PixelFormats.First(); // Set default or fallback to first in list
args.Add($"-c:v {info.Name}");
if (enc == Encoder.X264 || enc == Encoder.X265 || enc == Encoder.SvtAv1 || enc == Encoder.VpxVp9 || enc == Encoder.Nvenc264 || enc == Encoder.Nvenc265 || enc == Encoder.NvencAv1)
args.Add(GetKeyIntArg(fps, keyint));
if (enc == Encoder.X264)
{
string preset = Config.Get(Config.Key.ffEncPreset).ToLowerInvariant().Remove(" "); // TODO: Replace this ugly stuff with enums
int crf = GetCrf(settings);
args.Add($"-crf {crf} -preset {preset}");
}
if (enc == Encoder.X265)
{
string preset = Config.Get(Config.Key.ffEncPreset).ToLowerInvariant().Remove(" "); // TODO: Replace this ugly stuff with enums
int crf = GetCrf(settings);
args.Add($"{(crf > 0 ? $"-crf {crf}" : "-x265-params lossless=1")} -preset {preset}");
}
if (enc == Encoder.SvtAv1)
{
int crf = GetCrf(settings);
args.Add($"-crf {crf} {GetSvtAv1Speed()} -svtav1-params enable-qm=1:enable-overlays=1:enable-tf=0:scd=0");
}
if (enc == Encoder.VpxVp9)
{
int crf = GetCrf(settings);
string qualityStr = (crf > 0) ? $"-crf {crf}" : "-lossless 1";
string t = GetTilingArgs(res, "-tile-columns ", "-tile-rows ");
if (forceSinglePass) // Force 1-pass
{
args.Add($"-b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1");
}
else
{
return new string[] {
$"{string.Join(" ", args)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 -pass 1 -an",
$"{string.Join(" ", args)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 -pass 2"
};
}
}
// Fix NVENC pixel formats
if (enc.ToString().StartsWith("Nvenc"))
{
if (pixFmt == PixelFormat.Yuv420P10Le) pixFmt = PixelFormat.P010Le;
}
if (enc == Encoder.Nvenc264)
{
int crf = GetCrf(settings);
args.Add($"-b:v 0 -preset p7 {(crf > 0 ? $"-cq {crf}" : "-tune lossless")}");
}
if (enc == Encoder.Nvenc265)
{
int crf = GetCrf(settings);
args.Add($"-b:v 0 -preset p7 {(crf > 0 ? $"-cq {crf}" : "-tune lossless")}");
}
if (enc == Encoder.NvencAv1)
{
int crf = GetCrf(settings);
args.Add($"-b:v 0 -preset p7 -cq {crf}");
}
if (enc == Encoder.Amf264)
{
int crf = GetCrf(settings);
args.Add($"-b:v 0 -rc cqp -qp_i {crf} -qp_p {crf} -quality 2");
}
if (enc == Encoder.Amf265)
{
int crf = GetCrf(settings);
args.Add($"-b:v 0 -rc cqp -qp_i {crf} -qp_p {crf} -quality 2");
}
if (enc == Encoder.Qsv264)
{
int crf = GetCrf(settings).Clamp(1, 51);
args.Add($"-preset veryslow -global_quality {crf}");
}
if (enc == Encoder.Qsv265)
{
int crf = GetCrf(settings).Clamp(1, 51);
args.Add($"-preset veryslow -global_quality {crf}");
}
if (enc == Encoder.ProResKs)
{
var profile = ParseUtils.GetEnum<Quality.ProResProfile>(settings.Quality, true, Strings.VideoQuality);
args.Add($"-profile:v {OutputUtils.ProresProfiles[profile]}");
}
if (enc == Encoder.Gif)
{
args.Add("-gifflags -offsetting");
}
if (enc == Encoder.Jpeg)
{
var qualityLevel = ParseUtils.GetEnum<Quality.JpegWebm>(settings.Quality, true, Strings.VideoQuality);
args.Add($"-q:v {OutputUtils.JpegQuality[qualityLevel]}");
}
if (enc == Encoder.Webp)
{
var qualityLevel = ParseUtils.GetEnum<Quality.JpegWebm>(settings.Quality, true, Strings.VideoQuality);
args.Add($"-q:v {OutputUtils.WebpQuality[qualityLevel]}");
}
if (enc == Encoder.Exr)
{
args.Add($"-format {settings.Quality.Lower()}");
}
if (pixFmt != (PixelFormat)(-1))
args.Add($"-pix_fmt {pixFmt.ToString().Lower()}");
return new string[] { string.Join(" ", args) };
}
private static int GetCrf(OutputSettings settings)
{
if (settings.CustomQuality.IsNotEmpty())
return settings.CustomQuality.GetInt();
else
return OutputUtils.GetCrf(ParseUtils.GetEnum<Quality.Common>(settings.Quality, true, Strings.VideoQuality), settings.Encoder);
}
public static string GetTilingArgs(Size resolution, string colArg, string rowArg)
{
int cols = 0;
if (resolution.Width >= 1920) cols = 1;
if (resolution.Width >= 3840) cols = 2;
if (resolution.Width >= 7680) cols = 3;
int rows = 0;
if (resolution.Height >= 1600) rows = 1;
if (resolution.Height >= 3200) rows = 2;
if (resolution.Height >= 6400) rows = 3;
Logger.Log($"GetTilingArgs: Video resolution is {resolution.Width}x{resolution.Height} - Using 2^{cols} columns, 2^{rows} rows (=> {Math.Pow(2, cols)}x{Math.Pow(2, rows)} = {Math.Pow(2, cols) * Math.Pow(2, rows)} Tiles)", true);
return $"{(cols > 0 ? colArg + cols : "")} {(rows > 0 ? rowArg + rows : "")}";
}
public static string GetKeyIntArg(float fps, int intervalSeconds, string arg = "-g ")
{
int keyInt = (fps * intervalSeconds).RoundToInt().Clamp(30, 600);
return $"{arg}{keyInt}";
}
static string GetVp9Speed()
{
string preset = Config.Get(Config.Key.ffEncPreset).ToLowerInvariant().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).ToLowerInvariant().Remove(" ");
string arg = "8";
if (preset == "veryslow") arg = "3";
if (preset == "slower") arg = "4";
if (preset == "slow") arg = "5";
if (preset == "medium") arg = "6";
if (preset == "fast") arg = "7";
if (preset == "faster") arg = "8";
if (preset == "veryfast") arg = "9";
return $"-preset {arg}";
}
public static bool ContainerSupportsAllAudioFormats(Enums.Output.Format outFormat, 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(outFormat, format))
return false;
}
return true;
}
public static bool ContainerSupportsAudioFormat(Enums.Output.Format outFormat, string format)
{
bool supported = false;
string alias = GetAudioExt(format);
string[] formatsMp4 = new string[] { "m4a", "mp3", "ac3", "dts" };
string[] formatsMkv = new string[] { "m4a", "mp3", "ac3", "dts", "ogg", "mp2", "wav", "wma" };
string[] formatsWebm = new string[] { "ogg" };
string[] formatsMov = new string[] { "m4a", "ac3", "dts", "wav" };
string[] formatsAvi = new string[] { "m4a", "ac3", "dts" };
switch (outFormat)
{
case Enums.Output.Format.Mp4: supported = formatsMp4.Contains(alias); break;
case Enums.Output.Format.Mkv: supported = formatsMkv.Contains(alias); break;
case Enums.Output.Format.Webm: supported = formatsWebm.Contains(alias); break;
case Enums.Output.Format.Mov: supported = formatsMov.Contains(alias); break;
case Enums.Output.Format.Avi: supported = formatsAvi.Contains(alias); break;
}
Logger.Log($"Checking if {outFormat} supports audio format '{format}' ({alias}): {supported}", true, false, "ffmpeg");
return supported;
}
public static string GetExt(OutputSettings settings, bool dot = true)
{
string ext = dot ? "." : "";
EncoderInfoVideo info = settings.Encoder.GetInfo();
if (string.IsNullOrWhiteSpace(info.OverideExtension))
ext += settings.Format.ToString().Lower();
else
ext += info.OverideExtension;
return ext;
}
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 async Task<string> GetAudioFallbackArgs(string videoPath, Enums.Output.Format outFormat, float itsScale)
{
bool opusMp4 = Config.GetBool(Config.Key.allowOpusInMp4);
int opusBr = Config.GetInt(Config.Key.opusBitrate, 128);
int aacBr = Config.GetInt(Config.Key.aacBitrate, 160);
int ac = (await GetVideoInfo.GetFfprobeInfoAsync(videoPath, GetVideoInfo.FfprobeMode.ShowStreams, "channels", 0)).GetInt();
string af = GetAudioFilters(itsScale);
if (outFormat == Enums.Output.Format.Mkv || outFormat == Enums.Output.Format.Webm || (outFormat == Enums.Output.Format.Mp4 && opusMp4))
return $"-c:a libopus -b:a {(ac > 4 ? $"{opusBr * 2}" : $"{opusBr}")}k -ac {(ac > 0 ? $"{ac}" : "2")} {af}"; // Double bitrate if 5ch or more, ignore ac if <= 0
else
return $"-c:a aac -b:a {(ac > 4 ? $"{aacBr * 2}" : $"{aacBr}")}k -aac_coder twoloop -ac {(ac > 0 ? $"{ac}" : "2")} {af}";
}
private static string GetAudioFilters(float itsScale)
{
if (itsScale == 0 || itsScale == 1)
return "";
if (itsScale > 4)
return $"-af atempo=0.5,atempo=0.5,atempo={((1f / itsScale) * 4).ToStringDot()}";
else if (itsScale > 2)
return $"-af atempo=0.5,atempo={((1f / itsScale) * 2).ToStringDot()}";
else
return $"-af atempo={(1f / itsScale).ToStringDot()}";
}
public static string GetSubCodecForContainer(string containerExt)
{
containerExt = containerExt.Remove(".");
if (containerExt == "mp4" || containerExt == "mov") return "mov_text";
if (containerExt == "webm") return "webvtt";
return "copy"; // Default: Copy 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: {containerExt.ToUpper()} exports do not include subtitles.");
return supported;
}
public static int CreateConcatFile(string inputFilesDir, string outputPath, List<string> validExtensions = null)
{
if (IoUtils.GetAmountOfFiles(inputFilesDir, false) < 1)
return 0;
Directory.CreateDirectory(outputPath.GetParentDir());
validExtensions = validExtensions ?? new List<string>();
validExtensions = validExtensions.Select(x => x.Remove(".").Lower()).ToList(); // Ignore "." in extensions
var validFiles = IoUtils.GetFilesSorted(inputFilesDir).Where(f => validExtensions.Contains(Path.GetExtension(f).Replace(".", "").Lower()));
string fileContent = string.Join(Environment.NewLine, validFiles.Select(f => $"file '{f.Replace(@"\", "/")}'"));
IoUtils.TryDeleteIfExists(outputPath);
File.WriteAllText(outputPath, fileContent);
return validFiles.Count();
}
public static Size SizeFromString(string str, char delimiter = ':')
{
try
{
if (str.IsEmpty() || str.Length < 3 || !str.Contains(delimiter))
return new Size();
string[] nums = str.Remove(" ").Trim().Split(delimiter);
return new Size(nums[0].GetInt(), nums[1].GetInt());
}
catch
{
return new Size();
}
}
}
}

View File

@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using Flowframes.Data;
using Flowframes.IO;
namespace Flowframes.Media
{
class GetFrameCountCached
{
private static Dictionary<QueryInfo, int> cache = new Dictionary<QueryInfo, int>();
public static async Task<int> GetFrameCountAsync(MediaFile mf, int retryCount = 3)
{
return await GetFrameCountAsync(mf.SourcePath, retryCount);
}
public static async Task<int> GetFrameCountAsync(string path, int retryCount = 3)
{
Logger.Log($"Getting frame count ({path})", true);
long filesize = IoUtils.GetPathSize(path);
QueryInfo hash = new QueryInfo(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); // Count frames based on image file amount
}
else
{
if (path.IsConcatFile())
{
var lines = IoUtils.ReadFileLines(path);
var filtered = lines.Where(l => l.StartsWith("file '"));
frameCount = filtered.Count(); // Count frames from concat file
}
else
frameCount = await FfmpegCommands.GetFrameCountAsync(path); // Count frames from video stream
}
if (frameCount > 0)
{
Logger.Log($"Adding hash with value {frameCount} to cache.", true);
cache.Add(hash, frameCount);
}
else
{
if (retryCount > 0)
{
Logger.Log($"Got {frameCount} frames, retrying ({retryCount} left)", true);
Clear();
frameCount = await GetFrameCountAsync(path, retryCount - 1);
}
else
{
Logger.Log($"Failed to get frames and out of retries ({frameCount} frames for {path})", true);
}
}
return frameCount;
}
private static bool CacheContains(QueryInfo hash)
{
foreach (KeyValuePair<QueryInfo, int> entry in cache)
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
return true;
return false;
}
private static int GetFromCache(QueryInfo hash)
{
foreach (KeyValuePair<QueryInfo, int> entry in cache)
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
return entry.Value;
return 0;
}
public static void Clear()
{
cache.Clear();
}
}
}

View File

@@ -0,0 +1,65 @@
using System.Collections.Generic;
using System.Drawing;
using System.Threading.Tasks;
using Flowframes.Data;
using Flowframes.IO;
namespace Flowframes.Media
{
class GetMediaResolutionCached
{
private static Dictionary<QueryInfo, Size> cache = new Dictionary<QueryInfo, Size>();
public static async Task<Size> GetSizeAsync(string path)
{
Logger.Log($"Getting media resolution ({path})", true);
long filesize = IoUtils.GetPathSize(path);
QueryInfo hash = new QueryInfo(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);
if(size.Width > 0 && size.Height > 0)
{
Logger.Log($"Adding hash with value {size} to cache.", true);
cache.Add(hash, size);
}
return size;
}
private static bool CacheContains(QueryInfo hash)
{
foreach (KeyValuePair<QueryInfo, Size> entry in cache)
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
return true;
return false;
}
private static Size GetFromCache(QueryInfo hash)
{
foreach (KeyValuePair<QueryInfo, Size> entry in cache)
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
return entry.Value;
return new Size();
}
public static void Clear()
{
cache.Clear();
}
}
}

View File

@@ -0,0 +1,140 @@
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 GetVideoInfo
{
enum InfoType { Ffmpeg, Ffprobe };
public enum FfprobeMode { ShowFormat, ShowStreams, ShowBoth };
static Dictionary<QueryInfo, string> cmdCache = new Dictionary<QueryInfo, string>();
public static async Task<string> GetFfmpegInfoAsync(string path, string lineFilter = "", bool noCache = false)
{
return await GetFfmpegOutputAsync(path, "", lineFilter, noCache);
}
public static async Task<string> GetFfmpegOutputAsync(string path, string args, string lineFilter = "", bool noCache = false)
{
return await GetFfmpegOutputAsync(path, "", args, lineFilter, noCache);
}
public static async Task<string> GetFfmpegOutputAsync(string path, string argsIn, string argsOut, string lineFilter = "", bool noCache = false)
{
Process process = OsUtils.NewProcess(true);
process.StartInfo.Arguments = $"/C cd /D {GetAvPath().Wrap()} & " +
$"ffmpeg.exe -hide_banner -y {argsIn} {path.GetConcStr()} -i {path.Wrap()} {argsOut}";
return await GetInfoAsync(path, process, lineFilter, noCache);
}
public static async Task<string> GetFfprobeInfoAsync(string path, FfprobeMode mode, string lineFilter = "", int streamIndex = -1, bool stripKeyName = true)
{
Process process = OsUtils.NewProcess(true);
string showFormat = mode == FfprobeMode.ShowBoth || mode == FfprobeMode.ShowFormat ? "-show_format" : "";
string showStreams = mode == FfprobeMode.ShowBoth || mode == FfprobeMode.ShowStreams ? "-show_streams" : "";
process.StartInfo.Arguments = $"/C cd /D {GetAvPath().Wrap()} & " +
$"ffprobe -v quiet {path.GetConcStr()} {showFormat} {showStreams} {path.Wrap()}";
string output = await GetInfoAsync(path, process, lineFilter, streamIndex, stripKeyName);
return output;
}
static async Task<string> GetInfoAsync(string path, Process process, string lineFilter, bool noCache = false) // for ffmpeg
{
string output = await GetOutputCached(path, process, noCache);
if (!string.IsNullOrWhiteSpace(lineFilter.Trim()))
output = string.Join("\n", output.SplitIntoLines().Where(x => x.Contains(lineFilter)).ToArray());
return output;
}
static async Task<string> GetInfoAsync(string path, Process process, string lineFilter, int streamIndex = -1, bool stripKeyName = true, bool noCache = false) // for ffprobe
{
string output = await GetOutputCached(path, process, noCache);
try
{
if (streamIndex >= 0)
output = output.Split("[/STREAM]")[streamIndex];
}
catch
{
Logger.Log($"output.Split(\"[/STREAM]\")[{streamIndex}] failed! Can't access index {streamIndex} because array only has {output.Split("[/STREAM]").Length} items.", true);
return "";
}
if (!string.IsNullOrWhiteSpace(lineFilter.Trim()))
{
if (stripKeyName)
{
List<string> filtered = output.SplitIntoLines().Where(x => x.ToLowerInvariant().Contains(lineFilter.ToLowerInvariant())).ToList(); // Filter
filtered = filtered.Select(x => string.Join("", x.Split('=').Skip(1))).ToList(); // Ignore everything before (and including) the first '=' sign
output = string.Join("\n", filtered);
}
else
{
output = string.Join("\n", output.SplitIntoLines().Where(x => x.ToLowerInvariant().Contains(lineFilter.ToLowerInvariant())).ToArray());
}
}
return output;
}
static async Task<string> GetOutputCached(string path, Process process, bool noCache = false)
{
long filesize = IoUtils.GetPathSize(path);
QueryInfo hash = new QueryInfo(path, filesize, process.StartInfo.Arguments);
if (!noCache && filesize > 0 && CacheContains(hash, ref cmdCache))
{
Logger.Log($"GetVideoInfo: '{process.StartInfo.FileName} {process.StartInfo.Arguments}' cached, won't re-run.", true, false, "ffmpeg");
return GetFromCache(hash, ref cmdCache);
}
Logger.Log($"GetVideoInfo: '{process.StartInfo.FileName} {process.StartInfo.Arguments}' not cached, running.", true, false, "ffmpeg");
string output = await OsUtils.GetOutputAsync(process);
cmdCache.Add(hash, output);
return output;
}
private static bool CacheContains(QueryInfo hash, ref Dictionary<QueryInfo, string> cacheDict)
{
foreach (KeyValuePair<QueryInfo, string> entry in cacheDict)
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize && entry.Key.cmd == hash.cmd)
return true;
return false;
}
private static string GetFromCache(QueryInfo hash, ref Dictionary<QueryInfo, string> cacheDict)
{
foreach (KeyValuePair<QueryInfo, string> entry in cacheDict)
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize && entry.Key.cmd == hash.cmd)
return entry.Value;
return "";
}
public static void ClearCache()
{
cmdCache.Clear();
}
private static string GetAvPath()
{
return Path.Combine(Paths.GetPkgPath(), Paths.audioVideoDir);
}
}
}

View File

@@ -0,0 +1,82 @@
dotnet add package AdamsLair.WinForms --version 1.1.18
dotnet add package AdamsLair.WinForms.PopupControl --version 1.0.1
dotnet add package CircularProgressBar --version 2.8.0.16
dotnet add package Crc32.NET --version 1.2.0
dotnet add package CyotekTabList --version 2.0.0
dotnet add package diskdetector-net --version 0.3.2
dotnet add package HTAlt.Standart --version 0.1.6
dotnet add package HTAlt.WinForms --version 0.1.6
dotnet add package Magick.NET.Core --version 4.1.0
dotnet add package Magick.NET-Q8-AnyCPU --version 7.21.1
# dotnet add package Microsoft.NETCore.Platforms --version 1.1.0
# dotnet add package Microsoft.Win32.Primitives --version 4.3.0
# dotnet add package Microsoft-WindowsAPICodePack-Core --version 1.1.4
# dotnet add package Microsoft-WindowsAPICodePack-Shell --version 1.1.4
dotnet add package NDesk.Options --version 0.2.1
dotnet add package NETStandard.Library --version 1.6.1
dotnet add package Newtonsoft.Json --version 13.0.1
dotnet add package NvAPIWrapper.Net --version 0.8.1.101
dotnet add package PagedControl --version 2.2.0
# dotnet add package System.AppContext --version 4.3.0
# dotnet add package System.Buffers --version 4.5.1
# dotnet add package System.Collections --version 4.3.0
# dotnet add package System.Collections.Concurrent --version 4.3.0
# dotnet add package System.Console --version 4.3.0
# dotnet add package System.Diagnostics.Debug --version 4.3.0
# dotnet add package System.Diagnostics.DiagnosticSource --version 4.3.0
# dotnet add package System.Diagnostics.Tools --version 4.3.0
# dotnet add package System.Diagnostics.Tracing --version 4.3.0
# dotnet add package System.Drawing.Common --version 8.0.6
# dotnet add package System.Globalization --version 4.3.0
# dotnet add package System.Globalization.Calendars --version 4.3.0
# dotnet add package System.IO --version 4.3.0
# dotnet add package System.IO.Compression --version 4.3.0
# dotnet add package System.IO.Compression.ZipFile --version 4.3.0
# dotnet add package System.IO.FileSystem --version 4.3.0
# dotnet add package System.IO.FileSystem.Primitives --version 4.3.0
# dotnet add package System.Linq --version 4.3.0
# dotnet add package System.Linq.Expressions --version 4.3.0
# dotnet add package System.Management --version 5.0.0
# dotnet add package System.Memory --version 4.5.4
# dotnet add package System.Net.Http --version 4.3.0
# dotnet add package System.Net.Primitives --version 4.3.0
# dotnet add package System.Net.Sockets --version 4.3.0
# dotnet add package System.Numerics.Vectors --version 4.5.0
# dotnet add package System.ObjectModel --version 4.3.0
# dotnet add package System.Reflection --version 4.3.0
# dotnet add package System.Reflection.Extensions --version 4.3.0
# dotnet add package System.Reflection.Primitives --version 4.3.0
# dotnet add package System.Resources.ResourceManager --version 4.3.0
# dotnet add package System.Runtime --version 4.3.0
# dotnet add package System.Runtime.CompilerServices.Unsafe --version 4.7.1
# dotnet add package System.Runtime.Extensions --version 4.3.0
# dotnet add package System.Runtime.Handles --version 4.3.0
# dotnet add package System.Runtime.InteropServices --version 4.3.0
# dotnet add package System.Runtime.InteropServices.RuntimeInformation --version 4.3.0
# dotnet add package System.Runtime.Numerics --version 4.3.0
# dotnet add package System.Security.Claims --version 4.3.0
# dotnet add package System.Security.Cryptography.Algorithms --version 4.3.0
# dotnet add package System.Security.Cryptography.Encoding --version 4.3.0
# dotnet add package System.Security.Cryptography.Primitives --version 4.3.0
# dotnet add package System.Security.Cryptography.X509Certificates --version 4.3.0
# dotnet add package System.Security.Principal.Windows --version 4.3.0
# dotnet add package System.Text.Encoding --version 4.3.0
# dotnet add package System.Text.Encoding.Extensions --version 4.3.0
# dotnet add package System.Text.RegularExpressions --version 4.3.0
# dotnet add package System.Threading --version 4.3.0
# dotnet add package System.Threading.Tasks --version 4.3.0
# dotnet add package System.Threading.Tasks.Extensions --version 4.5.3
# dotnet add package System.Threading.Timer --version 4.3.0
# dotnet add package System.Xml.ReaderWriter --version 4.3.0
# dotnet add package System.Xml.XDocument --version 4.3.0
dotnet add package TabControl --version 2.1.2
dotnet add package Tulpep.NotificationWindow --version 1.1.38
dotnet add package VulkanSharp --version 0.1.10
dotnet add package Win32Interop.Dwmapi --version 1.0.1
dotnet add package Win32Interop.Gdi32 --version 1.0.1
dotnet add package Win32Interop.Kernel32 --version 1.0.1
dotnet add package Win32Interop.User32 --version 1.0.1
dotnet add package Win32Interop.Uxtheme --version 1.0.1
dotnet add package WindowsAPICodePack-Core --version 1.1.1
dotnet add package WindowsAPICodePack-Shell --version 1.1.1
dotnet add package WinFormAnimation --version 1.6.0.4

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More