mirror of
https://github.com/n00mkrad/flowframes.git
synced 2025-12-16 08:27:44 +01:00
Split into legacy (Framework 4.8) and .NET 8 projects, WIP .NET 8 fixes
This commit is contained in:
107
Flowframes/Data/AI.cs
Normal file
107
Flowframes/Data/AI.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Flowframes/Data/AudioTrack.cs
Normal file
18
Flowframes/Data/AudioTrack.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace Flowframes.Data
|
||||
{
|
||||
class AudioTrack
|
||||
{
|
||||
public int streamIndex;
|
||||
public string metadata;
|
||||
public string codec;
|
||||
|
||||
public AudioTrack(int streamNum, string metaStr, string codecStr)
|
||||
{
|
||||
streamIndex = streamNum;
|
||||
metadata = metaStr.Trim().Replace("_", ".").Replace(" ", ".");
|
||||
codec = codecStr.Trim().Replace("_", ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Flowframes/Data/EncoderInfo.cs
Normal file
14
Flowframes/Data/EncoderInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Flowframes/Data/EncoderInfoVideo.cs
Normal file
31
Flowframes/Data/EncoderInfoVideo.cs
Normal 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
33
Flowframes/Data/Enums.cs
Normal 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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Flowframes/Data/ExportSettings.cs
Normal file
17
Flowframes/Data/ExportSettings.cs
Normal 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; } = "";
|
||||
}
|
||||
}
|
||||
8
Flowframes/Data/Filetypes.cs
Normal file
8
Flowframes/Data/Filetypes.cs
Normal 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" };
|
||||
}
|
||||
}
|
||||
24
Flowframes/Data/FpsInfo.cs
Normal file
24
Flowframes/Data/FpsInfo.cs
Normal 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
264
Flowframes/Data/Fraction.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
108
Flowframes/Data/Implementations.cs
Normal file
108
Flowframes/Data/Implementations.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
251
Flowframes/Data/InterpSettings.cs
Normal file
251
Flowframes/Data/InterpSettings.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
176
Flowframes/Data/MediaFile.cs
Normal file
176
Flowframes/Data/MediaFile.cs
Normal 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)})";
|
||||
}
|
||||
}
|
||||
}
|
||||
77
Flowframes/Data/ModelCollection.cs
Normal file
77
Flowframes/Data/ModelCollection.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Flowframes/Data/Padding.cs
Normal file
15
Flowframes/Data/Padding.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flowframes.Data
|
||||
{
|
||||
class Padding
|
||||
{
|
||||
public const int inputFrames = 9;
|
||||
public const int inputFramesRenamed = 8;
|
||||
public const int interpFrames = 8;
|
||||
}
|
||||
}
|
||||
103
Flowframes/Data/Paths.cs
Normal file
103
Flowframes/Data/Paths.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Flowframes/Data/QueryInfo.cs
Normal file
16
Flowframes/Data/QueryInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Flowframes/Data/ResumeState.cs
Normal file
56
Flowframes/Data/ResumeState.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flowframes.Data
|
||||
{
|
||||
public struct ResumeState
|
||||
{
|
||||
public bool autoEncode;
|
||||
public int interpolatedInputFrames;
|
||||
|
||||
public ResumeState (bool autoEncArg, int lastInterpInFrameArg)
|
||||
{
|
||||
autoEncode = autoEncArg;
|
||||
interpolatedInputFrames = lastInterpInFrameArg;
|
||||
}
|
||||
|
||||
public ResumeState(string serializedData)
|
||||
{
|
||||
autoEncode = false;
|
||||
interpolatedInputFrames = 0;
|
||||
|
||||
Dictionary<string, string> entries = new Dictionary<string, string>();
|
||||
|
||||
foreach (string line in serializedData.SplitIntoLines())
|
||||
{
|
||||
if (line.Length < 3) continue;
|
||||
string[] keyValuePair = line.Split('|');
|
||||
entries.Add(keyValuePair[0], keyValuePair[1]);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, string> entry in entries)
|
||||
{
|
||||
switch (entry.Key)
|
||||
{
|
||||
case "AUTOENC": autoEncode = bool.Parse(entry.Value); break;
|
||||
case "INTERPOLATEDINPUTFRAMES": interpolatedInputFrames = entry.Value.GetInt(); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
string s = $"AUTOENC|{autoEncode}\n";
|
||||
|
||||
if (!autoEncode)
|
||||
{
|
||||
s += $"INTERPOLATEDINPUTFRAMES|{interpolatedInputFrames}";
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
Flowframes/Data/Servers.cs
Normal file
77
Flowframes/Data/Servers.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Flowframes/Data/Streams/AttachmentStream.cs
Normal file
22
Flowframes/Data/Streams/AttachmentStream.cs
Normal 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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Flowframes/Data/Streams/AudioStream.cs
Normal file
29
Flowframes/Data/Streams/AudioStream.cs
Normal 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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Flowframes/Data/Streams/DataStream.cs
Normal file
17
Flowframes/Data/Streams/DataStream.cs
Normal 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()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Flowframes/Data/Streams/Stream.cs
Normal file
19
Flowframes/Data/Streams/Stream.cs
Normal 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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Flowframes/Data/Streams/SubtitleStream.cs
Normal file
26
Flowframes/Data/Streams/SubtitleStream.cs
Normal 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()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Flowframes/Data/Streams/VideoStream.cs
Normal file
37
Flowframes/Data/Streams/VideoStream.cs
Normal 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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
94
Flowframes/Data/Strings.cs
Normal file
94
Flowframes/Data/Strings.cs
Normal 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" },
|
||||
};
|
||||
}
|
||||
}
|
||||
20
Flowframes/Data/SubtitleTrack.cs
Normal file
20
Flowframes/Data/SubtitleTrack.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace Flowframes.Data
|
||||
{
|
||||
class SubtitleTrack
|
||||
{
|
||||
public int streamIndex;
|
||||
public string lang;
|
||||
//public string langFriendly;
|
||||
public string encoding;
|
||||
|
||||
public SubtitleTrack(int streamNum, string metaStr, string encodingStr)
|
||||
{
|
||||
streamIndex = streamNum;
|
||||
lang = metaStr.Trim().Replace("_", ".").Replace(" ", ".");
|
||||
//langFriendly = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(metaStr.ToLowerInvariant().Trim().Replace("_", ".").Replace(" ", "."));
|
||||
encoding = encodingStr.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
123
Flowframes/Data/VidExtraData.cs
Normal file
123
Flowframes/Data/VidExtraData.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Flowframes/Data/VideoColorData.cs
Normal file
54
Flowframes/Data/VideoColorData.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
409
Flowframes/Extensions/ExtensionMethods.cs
Normal file
409
Flowframes/Extensions/ExtensionMethods.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Flowframes/Extensions/ProcessExtensions.cs
Normal file
61
Flowframes/Extensions/ProcessExtensions.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Win32Interop;
|
||||
|
||||
namespace Flowframes.Extensions
|
||||
{
|
||||
public static class ProcessExtensions
|
||||
{
|
||||
[Flags]
|
||||
public enum ThreadAccess : int
|
||||
{
|
||||
TERMINATE = (0x0001),
|
||||
SUSPEND_RESUME = (0x0002),
|
||||
GET_CONTEXT = (0x0008),
|
||||
SET_CONTEXT = (0x0010),
|
||||
SET_INFORMATION = (0x0020),
|
||||
QUERY_INFORMATION = (0x0040),
|
||||
SET_THREAD_TOKEN = (0x0080),
|
||||
IMPERSONATE = (0x0100),
|
||||
DIRECT_IMPERSONATION = (0x0200)
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
|
||||
[DllImport("kernel32.dll")]
|
||||
static extern uint SuspendThread(IntPtr hThread);
|
||||
[DllImport("kernel32.dll")]
|
||||
static extern int ResumeThread(IntPtr hThread);
|
||||
|
||||
public static void Suspend(this Process process)
|
||||
{
|
||||
foreach (ProcessThread thread in process.Threads)
|
||||
{
|
||||
var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
|
||||
|
||||
if (pOpenThread == IntPtr.Zero)
|
||||
break;
|
||||
|
||||
SuspendThread(pOpenThread);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Resume(this Process process)
|
||||
{
|
||||
foreach (ProcessThread thread in process.Threads)
|
||||
{
|
||||
var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
|
||||
|
||||
if (pOpenThread == IntPtr.Zero)
|
||||
break;
|
||||
|
||||
ResumeThread(pOpenThread);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Flowframes/Flowframes.csproj
Normal file
48
Flowframes/Flowframes.csproj
Normal 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
31
Flowframes/Flowframes.sln
Normal 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
266
Flowframes/Forms/BatchForm.Designer.cs
generated
Normal file
@@ -0,0 +1,266 @@
|
||||
namespace Flowframes.Forms
|
||||
{
|
||||
partial class BatchForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(BatchForm));
|
||||
this.titleLabel = new System.Windows.Forms.Label();
|
||||
this.stopBtn = new System.Windows.Forms.Button();
|
||||
this.runBtn = new System.Windows.Forms.Button();
|
||||
this.addToQueue = new System.Windows.Forms.Button();
|
||||
this.forceStopBtn = new System.Windows.Forms.Button();
|
||||
this.clearBtn = new System.Windows.Forms.Button();
|
||||
this.taskList = new System.Windows.Forms.ListBox();
|
||||
this.clearSelectedBtn = new System.Windows.Forms.Button();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.panel1 = new System.Windows.Forms.Panel();
|
||||
this.moveDownBtn = new System.Windows.Forms.Button();
|
||||
this.moveUpBtn = new System.Windows.Forms.Button();
|
||||
this.panel1.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// titleLabel
|
||||
//
|
||||
this.titleLabel.AutoSize = true;
|
||||
this.titleLabel.Font = new System.Drawing.Font("Yu Gothic UI", 21.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.titleLabel.ForeColor = System.Drawing.Color.White;
|
||||
this.titleLabel.Location = new System.Drawing.Point(12, 9);
|
||||
this.titleLabel.Margin = new System.Windows.Forms.Padding(3, 0, 3, 10);
|
||||
this.titleLabel.Name = "titleLabel";
|
||||
this.titleLabel.Size = new System.Drawing.Size(323, 40);
|
||||
this.titleLabel.TabIndex = 1;
|
||||
this.titleLabel.Text = "Batch Processing Queue";
|
||||
//
|
||||
// stopBtn
|
||||
//
|
||||
this.stopBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.stopBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
|
||||
this.stopBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.stopBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.stopBtn.Location = new System.Drawing.Point(682, 351);
|
||||
this.stopBtn.Name = "stopBtn";
|
||||
this.stopBtn.Size = new System.Drawing.Size(250, 40);
|
||||
this.stopBtn.TabIndex = 35;
|
||||
this.stopBtn.Text = "Stop After Current Task";
|
||||
this.stopBtn.UseVisualStyleBackColor = false;
|
||||
this.stopBtn.Visible = false;
|
||||
this.stopBtn.Click += new System.EventHandler(this.stopBtn_Click);
|
||||
//
|
||||
// runBtn
|
||||
//
|
||||
this.runBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.runBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
|
||||
this.runBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.runBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.runBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.runBtn.Location = new System.Drawing.Point(682, 443);
|
||||
this.runBtn.Name = "runBtn";
|
||||
this.runBtn.Size = new System.Drawing.Size(250, 42);
|
||||
this.runBtn.TabIndex = 36;
|
||||
this.runBtn.Text = "Start";
|
||||
this.runBtn.UseVisualStyleBackColor = false;
|
||||
this.runBtn.Click += new System.EventHandler(this.runBtn_Click);
|
||||
//
|
||||
// addToQueue
|
||||
//
|
||||
this.addToQueue.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.addToQueue.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
|
||||
this.addToQueue.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.addToQueue.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.addToQueue.ForeColor = System.Drawing.Color.White;
|
||||
this.addToQueue.Location = new System.Drawing.Point(682, 65);
|
||||
this.addToQueue.Name = "addToQueue";
|
||||
this.addToQueue.Size = new System.Drawing.Size(250, 40);
|
||||
this.addToQueue.TabIndex = 39;
|
||||
this.addToQueue.Text = "Add Current Configuration To Queue";
|
||||
this.addToQueue.UseVisualStyleBackColor = false;
|
||||
this.addToQueue.Click += new System.EventHandler(this.addToQueue_Click);
|
||||
//
|
||||
// forceStopBtn
|
||||
//
|
||||
this.forceStopBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.forceStopBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
|
||||
this.forceStopBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.forceStopBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.forceStopBtn.Location = new System.Drawing.Point(682, 397);
|
||||
this.forceStopBtn.Name = "forceStopBtn";
|
||||
this.forceStopBtn.Size = new System.Drawing.Size(250, 40);
|
||||
this.forceStopBtn.TabIndex = 40;
|
||||
this.forceStopBtn.Text = "Force Stop Now";
|
||||
this.forceStopBtn.UseVisualStyleBackColor = false;
|
||||
this.forceStopBtn.Visible = false;
|
||||
this.forceStopBtn.Click += new System.EventHandler(this.forceStopBtn_Click);
|
||||
//
|
||||
// clearBtn
|
||||
//
|
||||
this.clearBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.clearBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
|
||||
this.clearBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.clearBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.clearBtn.Location = new System.Drawing.Point(682, 157);
|
||||
this.clearBtn.Name = "clearBtn";
|
||||
this.clearBtn.Size = new System.Drawing.Size(250, 40);
|
||||
this.clearBtn.TabIndex = 41;
|
||||
this.clearBtn.Text = "Clear All Queue Entries";
|
||||
this.clearBtn.UseVisualStyleBackColor = false;
|
||||
this.clearBtn.Click += new System.EventHandler(this.clearBtn_Click);
|
||||
//
|
||||
// taskList
|
||||
//
|
||||
this.taskList.AllowDrop = true;
|
||||
this.taskList.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
|
||||
this.taskList.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.taskList.ForeColor = System.Drawing.Color.White;
|
||||
this.taskList.FormattingEnabled = true;
|
||||
this.taskList.ItemHeight = 16;
|
||||
this.taskList.Location = new System.Drawing.Point(12, 65);
|
||||
this.taskList.Name = "taskList";
|
||||
this.taskList.Size = new System.Drawing.Size(664, 420);
|
||||
this.taskList.TabIndex = 43;
|
||||
this.taskList.SelectedIndexChanged += new System.EventHandler(this.taskList_SelectedIndexChanged);
|
||||
this.taskList.DragDrop += new System.Windows.Forms.DragEventHandler(this.taskList_DragDrop);
|
||||
this.taskList.DragEnter += new System.Windows.Forms.DragEventHandler(this.taskList_DragEnter);
|
||||
//
|
||||
// clearSelectedBtn
|
||||
//
|
||||
this.clearSelectedBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.clearSelectedBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
|
||||
this.clearSelectedBtn.Enabled = false;
|
||||
this.clearSelectedBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.clearSelectedBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.clearSelectedBtn.Location = new System.Drawing.Point(682, 111);
|
||||
this.clearSelectedBtn.Name = "clearSelectedBtn";
|
||||
this.clearSelectedBtn.Size = new System.Drawing.Size(250, 40);
|
||||
this.clearSelectedBtn.TabIndex = 44;
|
||||
this.clearSelectedBtn.Text = "Clear Selected Queue Entry";
|
||||
this.clearSelectedBtn.UseVisualStyleBackColor = false;
|
||||
this.clearSelectedBtn.Click += new System.EventHandler(this.clearSelectedBtn_Click);
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.ForeColor = System.Drawing.Color.White;
|
||||
this.label1.Location = new System.Drawing.Point(6, 6);
|
||||
this.label1.Margin = new System.Windows.Forms.Padding(6);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(238, 111);
|
||||
this.label1.TabIndex = 45;
|
||||
this.label1.Text = "Tip:\r\nYou can also drag and drop multiple videos into the list.\r\nThey will be add" +
|
||||
"ed to the queue using the interpolation settings set in the GUI.";
|
||||
//
|
||||
// panel1
|
||||
//
|
||||
this.panel1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(40)))), ((int)(((byte)(40)))));
|
||||
this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||
this.panel1.Controls.Add(this.label1);
|
||||
this.panel1.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.panel1.Location = new System.Drawing.Point(682, 203);
|
||||
this.panel1.Name = "panel1";
|
||||
this.panel1.Size = new System.Drawing.Size(250, 142);
|
||||
this.panel1.TabIndex = 46;
|
||||
//
|
||||
// moveDownBtn
|
||||
//
|
||||
this.moveDownBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.moveDownBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
|
||||
this.moveDownBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.moveDownBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.moveDownBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.moveDownBtn.Location = new System.Drawing.Point(636, 445);
|
||||
this.moveDownBtn.Margin = new System.Windows.Forms.Padding(0);
|
||||
this.moveDownBtn.Name = "moveDownBtn";
|
||||
this.moveDownBtn.Size = new System.Drawing.Size(35, 35);
|
||||
this.moveDownBtn.TabIndex = 47;
|
||||
this.moveDownBtn.Text = "↓";
|
||||
this.moveDownBtn.UseVisualStyleBackColor = false;
|
||||
this.moveDownBtn.Visible = false;
|
||||
this.moveDownBtn.Click += new System.EventHandler(this.moveDownBtn_Click);
|
||||
//
|
||||
// moveUpBtn
|
||||
//
|
||||
this.moveUpBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.moveUpBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
|
||||
this.moveUpBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.moveUpBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.moveUpBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.moveUpBtn.Location = new System.Drawing.Point(597, 445);
|
||||
this.moveUpBtn.Margin = new System.Windows.Forms.Padding(0);
|
||||
this.moveUpBtn.Name = "moveUpBtn";
|
||||
this.moveUpBtn.Size = new System.Drawing.Size(35, 35);
|
||||
this.moveUpBtn.TabIndex = 48;
|
||||
this.moveUpBtn.Text = "↑";
|
||||
this.moveUpBtn.UseVisualStyleBackColor = false;
|
||||
this.moveUpBtn.Visible = false;
|
||||
this.moveUpBtn.Click += new System.EventHandler(this.moveUpBtn_Click);
|
||||
//
|
||||
// BatchForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(32)))), ((int)(((byte)(32)))));
|
||||
this.ClientSize = new System.Drawing.Size(944, 501);
|
||||
this.Controls.Add(this.moveUpBtn);
|
||||
this.Controls.Add(this.moveDownBtn);
|
||||
this.Controls.Add(this.panel1);
|
||||
this.Controls.Add(this.clearSelectedBtn);
|
||||
this.Controls.Add(this.taskList);
|
||||
this.Controls.Add(this.clearBtn);
|
||||
this.Controls.Add(this.forceStopBtn);
|
||||
this.Controls.Add(this.addToQueue);
|
||||
this.Controls.Add(this.runBtn);
|
||||
this.Controls.Add(this.stopBtn);
|
||||
this.Controls.Add(this.titleLabel);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.MaximizeBox = false;
|
||||
this.Name = "BatchForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "Batch Processing";
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.BatchForm_FormClosing);
|
||||
this.Load += new System.EventHandler(this.BatchForm_Load);
|
||||
this.panel1.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label titleLabel;
|
||||
private System.Windows.Forms.Button stopBtn;
|
||||
private System.Windows.Forms.Button runBtn;
|
||||
private System.Windows.Forms.Button addToQueue;
|
||||
private System.Windows.Forms.Button forceStopBtn;
|
||||
private System.Windows.Forms.Button clearBtn;
|
||||
private System.Windows.Forms.ListBox taskList;
|
||||
private System.Windows.Forms.Button clearSelectedBtn;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.Panel panel1;
|
||||
private System.Windows.Forms.Button moveDownBtn;
|
||||
private System.Windows.Forms.Button moveUpBtn;
|
||||
}
|
||||
}
|
||||
185
Flowframes/Forms/BatchForm.cs
Normal file
185
Flowframes/Forms/BatchForm.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
3185
Flowframes/Forms/BatchForm.resx
Normal file
3185
Flowframes/Forms/BatchForm.resx
Normal file
File diff suppressed because it is too large
Load Diff
70
Flowframes/Forms/BigPreviewForm.Designer.cs
generated
Normal file
70
Flowframes/Forms/BigPreviewForm.Designer.cs
generated
Normal file
@@ -0,0 +1,70 @@
|
||||
namespace Flowframes.Forms
|
||||
{
|
||||
partial class BigPreviewForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(BigPreviewForm));
|
||||
this.picBox = new System.Windows.Forms.PictureBox();
|
||||
((System.ComponentModel.ISupportInitialize)(this.picBox)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// picBox
|
||||
//
|
||||
this.picBox.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.picBox.Image = global::Flowframes.Properties.Resources.baseline_image_white_48dp_4x_25pcAlpha;
|
||||
this.picBox.Location = new System.Drawing.Point(0, 0);
|
||||
this.picBox.Margin = new System.Windows.Forms.Padding(0);
|
||||
this.picBox.Name = "picBox";
|
||||
this.picBox.Size = new System.Drawing.Size(704, 601);
|
||||
this.picBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
|
||||
this.picBox.TabIndex = 0;
|
||||
this.picBox.TabStop = false;
|
||||
//
|
||||
// BigPreviewForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(32)))), ((int)(((byte)(32)))));
|
||||
this.ClientSize = new System.Drawing.Size(704, 601);
|
||||
this.Controls.Add(this.picBox);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.Name = "BigPreviewForm";
|
||||
this.Text = "Resizable Preview";
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.BigPreviewForm_FormClosing);
|
||||
this.Load += new System.EventHandler(this.BigPreviewForm_Load);
|
||||
((System.ComponentModel.ISupportInitialize)(this.picBox)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.PictureBox picBox;
|
||||
}
|
||||
}
|
||||
37
Flowframes/Forms/BigPreviewForm.cs
Normal file
37
Flowframes/Forms/BigPreviewForm.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Flowframes.Main;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Flowframes.Ui;
|
||||
|
||||
namespace Flowframes.Forms
|
||||
{
|
||||
public partial class BigPreviewForm : Form
|
||||
{
|
||||
public BigPreviewForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void BigPreviewForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void SetImage (Image img)
|
||||
{
|
||||
picBox.Image = img;
|
||||
}
|
||||
|
||||
private void BigPreviewForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
InterpolationProgress.bigPreviewForm = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
3185
Flowframes/Forms/BigPreviewForm.resx
Normal file
3185
Flowframes/Forms/BigPreviewForm.resx
Normal file
File diff suppressed because it is too large
Load Diff
64
Flowframes/Forms/CustomForm.cs
Normal file
64
Flowframes/Forms/CustomForm.cs
Normal 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
356
Flowframes/Forms/DebugForm.Designer.cs
generated
Normal file
@@ -0,0 +1,356 @@
|
||||
|
||||
namespace Flowframes.Forms
|
||||
{
|
||||
partial class DebugForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DebugForm));
|
||||
this.titleLabel = new System.Windows.Forms.Label();
|
||||
this.tabPage2 = new System.Windows.Forms.TabPage();
|
||||
this.panel2 = new System.Windows.Forms.Panel();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.panel1 = new System.Windows.Forms.Panel();
|
||||
this.configDataGrid = new System.Windows.Forms.DataGridView();
|
||||
this.tabPage1 = new System.Windows.Forms.TabPage();
|
||||
this.copyTextClipboardBtn = new HTAlt.WinForms.HTButton();
|
||||
this.monospaceBtn = new HTAlt.WinForms.HTButton();
|
||||
this.refreshBtn = new HTAlt.WinForms.HTButton();
|
||||
this.textWrapBtn = new HTAlt.WinForms.HTButton();
|
||||
this.clearLogsBtn = new HTAlt.WinForms.HTButton();
|
||||
this.openLogFolderBtn = new HTAlt.WinForms.HTButton();
|
||||
this.logFilesDropdown = new System.Windows.Forms.ComboBox();
|
||||
this.logBox = new System.Windows.Forms.TextBox();
|
||||
this.htTabControl1 = new HTAlt.WinForms.HTTabControl();
|
||||
this.toolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.tabPage2.SuspendLayout();
|
||||
this.panel2.SuspendLayout();
|
||||
this.panel1.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.configDataGrid)).BeginInit();
|
||||
this.tabPage1.SuspendLayout();
|
||||
this.htTabControl1.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// titleLabel
|
||||
//
|
||||
this.titleLabel.AutoSize = true;
|
||||
this.titleLabel.Font = new System.Drawing.Font("Yu Gothic UI", 21.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.titleLabel.ForeColor = System.Drawing.Color.White;
|
||||
this.titleLabel.Location = new System.Drawing.Point(12, 9);
|
||||
this.titleLabel.Margin = new System.Windows.Forms.Padding(3, 0, 3, 10);
|
||||
this.titleLabel.Name = "titleLabel";
|
||||
this.titleLabel.Size = new System.Drawing.Size(175, 40);
|
||||
this.titleLabel.TabIndex = 2;
|
||||
this.titleLabel.Text = "Debug Tools";
|
||||
//
|
||||
// tabPage2
|
||||
//
|
||||
this.tabPage2.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
|
||||
this.tabPage2.Controls.Add(this.panel2);
|
||||
this.tabPage2.Controls.Add(this.panel1);
|
||||
this.tabPage2.Location = new System.Drawing.Point(4, 27);
|
||||
this.tabPage2.Name = "tabPage2";
|
||||
this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabPage2.Size = new System.Drawing.Size(912, 396);
|
||||
this.tabPage2.TabIndex = 1;
|
||||
this.tabPage2.Text = "Config Editor";
|
||||
this.tabPage2.Enter += new System.EventHandler(this.tabPage2_Enter);
|
||||
//
|
||||
// panel2
|
||||
//
|
||||
this.panel2.Controls.Add(this.label1);
|
||||
this.panel2.Location = new System.Drawing.Point(6, 6);
|
||||
this.panel2.Name = "panel2";
|
||||
this.panel2.Size = new System.Drawing.Size(900, 68);
|
||||
this.panel2.TabIndex = 5;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.ForeColor = System.Drawing.SystemColors.Control;
|
||||
this.label1.Location = new System.Drawing.Point(3, 3);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(415, 60);
|
||||
this.label1.TabIndex = 0;
|
||||
this.label1.Text = resources.GetString("label1.Text");
|
||||
//
|
||||
// panel1
|
||||
//
|
||||
this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.panel1.Controls.Add(this.configDataGrid);
|
||||
this.panel1.Location = new System.Drawing.Point(6, 80);
|
||||
this.panel1.Name = "panel1";
|
||||
this.panel1.Size = new System.Drawing.Size(900, 310);
|
||||
this.panel1.TabIndex = 4;
|
||||
//
|
||||
// configDataGrid
|
||||
//
|
||||
this.configDataGrid.AllowUserToResizeColumns = false;
|
||||
this.configDataGrid.AllowUserToResizeRows = false;
|
||||
this.configDataGrid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.configDataGrid.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells;
|
||||
this.configDataGrid.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.AllCells;
|
||||
this.configDataGrid.ClipboardCopyMode = System.Windows.Forms.DataGridViewClipboardCopyMode.EnableWithoutHeaderText;
|
||||
this.configDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.configDataGrid.Location = new System.Drawing.Point(0, 0);
|
||||
this.configDataGrid.MultiSelect = false;
|
||||
this.configDataGrid.Name = "configDataGrid";
|
||||
this.configDataGrid.Size = new System.Drawing.Size(900, 310);
|
||||
this.configDataGrid.TabIndex = 0;
|
||||
this.configDataGrid.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.configDataGrid_CellValueChanged);
|
||||
this.configDataGrid.RowsAdded += new System.Windows.Forms.DataGridViewRowsAddedEventHandler(this.configDataGrid_RowsAdded);
|
||||
this.configDataGrid.RowsRemoved += new System.Windows.Forms.DataGridViewRowsRemovedEventHandler(this.configDataGrid_RowsRemoved);
|
||||
//
|
||||
// tabPage1
|
||||
//
|
||||
this.tabPage1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
|
||||
this.tabPage1.Controls.Add(this.copyTextClipboardBtn);
|
||||
this.tabPage1.Controls.Add(this.monospaceBtn);
|
||||
this.tabPage1.Controls.Add(this.refreshBtn);
|
||||
this.tabPage1.Controls.Add(this.textWrapBtn);
|
||||
this.tabPage1.Controls.Add(this.clearLogsBtn);
|
||||
this.tabPage1.Controls.Add(this.openLogFolderBtn);
|
||||
this.tabPage1.Controls.Add(this.logFilesDropdown);
|
||||
this.tabPage1.Controls.Add(this.logBox);
|
||||
this.tabPage1.Location = new System.Drawing.Point(4, 27);
|
||||
this.tabPage1.Name = "tabPage1";
|
||||
this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabPage1.Size = new System.Drawing.Size(912, 396);
|
||||
this.tabPage1.TabIndex = 0;
|
||||
this.tabPage1.Text = "Log Viewer";
|
||||
//
|
||||
// copyTextClipboardBtn
|
||||
//
|
||||
this.copyTextClipboardBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.copyTextClipboardBtn.FlatAppearance.BorderSize = 0;
|
||||
this.copyTextClipboardBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.copyTextClipboardBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.copyTextClipboardBtn.Location = new System.Drawing.Point(600, 6);
|
||||
this.copyTextClipboardBtn.Name = "copyTextClipboardBtn";
|
||||
this.copyTextClipboardBtn.Size = new System.Drawing.Size(150, 23);
|
||||
this.copyTextClipboardBtn.TabIndex = 80;
|
||||
this.copyTextClipboardBtn.Text = "Copy Text To Clipboard";
|
||||
this.copyTextClipboardBtn.UseVisualStyleBackColor = false;
|
||||
this.copyTextClipboardBtn.Click += new System.EventHandler(this.copyTextClipboardBtn_Click);
|
||||
//
|
||||
// monospaceBtn
|
||||
//
|
||||
this.monospaceBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.monospaceBtn.ButtonImage = global::Flowframes.Properties.Resources.baseline_format_size_white_48dp;
|
||||
this.monospaceBtn.DrawImage = true;
|
||||
this.monospaceBtn.FlatAppearance.BorderSize = 0;
|
||||
this.monospaceBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.monospaceBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.monospaceBtn.ImageSizeMode = HTAlt.WinForms.HTButton.ButtonImageSizeMode.Zoom;
|
||||
this.monospaceBtn.Location = new System.Drawing.Point(241, 6);
|
||||
this.monospaceBtn.Name = "monospaceBtn";
|
||||
this.monospaceBtn.Size = new System.Drawing.Size(23, 23);
|
||||
this.monospaceBtn.TabIndex = 79;
|
||||
this.toolTip.SetToolTip(this.monospaceBtn, "Toggle Monospace Font");
|
||||
this.monospaceBtn.UseVisualStyleBackColor = false;
|
||||
this.monospaceBtn.Click += new System.EventHandler(this.monospaceBtn_Click);
|
||||
//
|
||||
// refreshBtn
|
||||
//
|
||||
this.refreshBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.refreshBtn.ButtonImage = global::Flowframes.Properties.Resources.baseline_refresh_white_48dp;
|
||||
this.refreshBtn.DrawImage = true;
|
||||
this.refreshBtn.FlatAppearance.BorderSize = 0;
|
||||
this.refreshBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.refreshBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.refreshBtn.ImageSizeMode = HTAlt.WinForms.HTButton.ButtonImageSizeMode.Zoom;
|
||||
this.refreshBtn.Location = new System.Drawing.Point(212, 6);
|
||||
this.refreshBtn.Name = "refreshBtn";
|
||||
this.refreshBtn.Size = new System.Drawing.Size(23, 23);
|
||||
this.refreshBtn.TabIndex = 78;
|
||||
this.toolTip.SetToolTip(this.refreshBtn, "Refresh");
|
||||
this.refreshBtn.UseVisualStyleBackColor = false;
|
||||
this.refreshBtn.Click += new System.EventHandler(this.refreshBtn_Click);
|
||||
//
|
||||
// textWrapBtn
|
||||
//
|
||||
this.textWrapBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.textWrapBtn.ButtonImage = global::Flowframes.Properties.Resources.baseline_wrap_text_white_48dp;
|
||||
this.textWrapBtn.DrawImage = true;
|
||||
this.textWrapBtn.FlatAppearance.BorderSize = 0;
|
||||
this.textWrapBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.textWrapBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.textWrapBtn.ImageSizeMode = HTAlt.WinForms.HTButton.ButtonImageSizeMode.Zoom;
|
||||
this.textWrapBtn.Location = new System.Drawing.Point(270, 5);
|
||||
this.textWrapBtn.Name = "textWrapBtn";
|
||||
this.textWrapBtn.Size = new System.Drawing.Size(23, 23);
|
||||
this.textWrapBtn.TabIndex = 77;
|
||||
this.toolTip.SetToolTip(this.textWrapBtn, "Toggle Text Wrap");
|
||||
this.textWrapBtn.UseVisualStyleBackColor = false;
|
||||
this.textWrapBtn.Click += new System.EventHandler(this.textWrapBtn_Click);
|
||||
//
|
||||
// clearLogsBtn
|
||||
//
|
||||
this.clearLogsBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.clearLogsBtn.FlatAppearance.BorderSize = 0;
|
||||
this.clearLogsBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.clearLogsBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.clearLogsBtn.Location = new System.Drawing.Point(756, 6);
|
||||
this.clearLogsBtn.Name = "clearLogsBtn";
|
||||
this.clearLogsBtn.Size = new System.Drawing.Size(150, 23);
|
||||
this.clearLogsBtn.TabIndex = 76;
|
||||
this.clearLogsBtn.Text = "Clear Logs";
|
||||
this.clearLogsBtn.UseVisualStyleBackColor = false;
|
||||
this.clearLogsBtn.Click += new System.EventHandler(this.clearLogsBtn_Click);
|
||||
//
|
||||
// openLogFolderBtn
|
||||
//
|
||||
this.openLogFolderBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.openLogFolderBtn.FlatAppearance.BorderSize = 0;
|
||||
this.openLogFolderBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.openLogFolderBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.openLogFolderBtn.Location = new System.Drawing.Point(444, 6);
|
||||
this.openLogFolderBtn.Name = "openLogFolderBtn";
|
||||
this.openLogFolderBtn.Size = new System.Drawing.Size(150, 23);
|
||||
this.openLogFolderBtn.TabIndex = 75;
|
||||
this.openLogFolderBtn.Text = "Open Log Folder";
|
||||
this.openLogFolderBtn.UseVisualStyleBackColor = false;
|
||||
this.openLogFolderBtn.Click += new System.EventHandler(this.openLogFolderBtn_Click);
|
||||
//
|
||||
// logFilesDropdown
|
||||
//
|
||||
this.logFilesDropdown.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.logFilesDropdown.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.logFilesDropdown.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.logFilesDropdown.ForeColor = System.Drawing.Color.White;
|
||||
this.logFilesDropdown.FormattingEnabled = true;
|
||||
this.logFilesDropdown.Location = new System.Drawing.Point(6, 6);
|
||||
this.logFilesDropdown.Name = "logFilesDropdown";
|
||||
this.logFilesDropdown.Size = new System.Drawing.Size(200, 23);
|
||||
this.logFilesDropdown.TabIndex = 73;
|
||||
this.logFilesDropdown.SelectedIndexChanged += new System.EventHandler(this.logFilesDropdown_SelectedIndexChanged);
|
||||
//
|
||||
// logBox
|
||||
//
|
||||
this.logBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.logBox.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.logBox.Cursor = System.Windows.Forms.Cursors.Arrow;
|
||||
this.logBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.logBox.ForeColor = System.Drawing.Color.White;
|
||||
this.logBox.Location = new System.Drawing.Point(6, 35);
|
||||
this.logBox.MinimumSize = new System.Drawing.Size(4, 21);
|
||||
this.logBox.Multiline = true;
|
||||
this.logBox.Name = "logBox";
|
||||
this.logBox.ReadOnly = true;
|
||||
this.logBox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||
this.logBox.Size = new System.Drawing.Size(900, 355);
|
||||
this.logBox.TabIndex = 6;
|
||||
this.logBox.TabStop = false;
|
||||
this.logBox.WordWrap = false;
|
||||
//
|
||||
// htTabControl1
|
||||
//
|
||||
this.htTabControl1.AllowDrop = true;
|
||||
this.htTabControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.htTabControl1.BackgroundColor = System.Drawing.Color.FromArgb(((int)(((byte)(45)))), ((int)(((byte)(45)))), ((int)(((byte)(48)))));
|
||||
this.htTabControl1.BorderTabLineColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(122)))), ((int)(((byte)(204)))));
|
||||
this.htTabControl1.Controls.Add(this.tabPage1);
|
||||
this.htTabControl1.Controls.Add(this.tabPage2);
|
||||
this.htTabControl1.DisableClose = true;
|
||||
this.htTabControl1.DisableDragging = true;
|
||||
this.htTabControl1.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
this.htTabControl1.HoverTabButtonColor = System.Drawing.Color.FromArgb(((int)(((byte)(82)))), ((int)(((byte)(176)))), ((int)(((byte)(239)))));
|
||||
this.htTabControl1.HoverTabColor = System.Drawing.Color.FromArgb(((int)(((byte)(28)))), ((int)(((byte)(151)))), ((int)(((byte)(234)))));
|
||||
this.htTabControl1.HoverUnselectedTabButtonColor = System.Drawing.Color.FromArgb(((int)(((byte)(85)))), ((int)(((byte)(85)))), ((int)(((byte)(85)))));
|
||||
this.htTabControl1.Location = new System.Drawing.Point(12, 62);
|
||||
this.htTabControl1.Name = "htTabControl1";
|
||||
this.htTabControl1.Padding = new System.Drawing.Point(14, 4);
|
||||
this.htTabControl1.SelectedIndex = 0;
|
||||
this.htTabControl1.SelectedTabButtonColor = System.Drawing.Color.FromArgb(((int)(((byte)(28)))), ((int)(((byte)(151)))), ((int)(((byte)(234)))));
|
||||
this.htTabControl1.SelectedTabColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(122)))), ((int)(((byte)(204)))));
|
||||
this.htTabControl1.Size = new System.Drawing.Size(920, 427);
|
||||
this.htTabControl1.TabIndex = 3;
|
||||
this.htTabControl1.TextColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(255)))), ((int)(((byte)(255)))));
|
||||
this.htTabControl1.UnderBorderTabLineColor = System.Drawing.Color.FromArgb(((int)(((byte)(67)))), ((int)(((byte)(67)))), ((int)(((byte)(70)))));
|
||||
this.htTabControl1.UnselectedBorderTabLineColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70)))));
|
||||
this.htTabControl1.UnselectedTabColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70)))));
|
||||
this.htTabControl1.UpDownBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(63)))), ((int)(((byte)(70)))));
|
||||
this.htTabControl1.UpDownTextColor = System.Drawing.Color.FromArgb(((int)(((byte)(109)))), ((int)(((byte)(109)))), ((int)(((byte)(112)))));
|
||||
//
|
||||
// DebugForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(32)))), ((int)(((byte)(32)))));
|
||||
this.ClientSize = new System.Drawing.Size(944, 501);
|
||||
this.Controls.Add(this.htTabControl1);
|
||||
this.Controls.Add(this.titleLabel);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.Name = "DebugForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "Debug Tools";
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.DebugForm_FormClosing);
|
||||
this.Shown += new System.EventHandler(this.DebugForm_Shown);
|
||||
this.tabPage2.ResumeLayout(false);
|
||||
this.panel2.ResumeLayout(false);
|
||||
this.panel2.PerformLayout();
|
||||
this.panel1.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)(this.configDataGrid)).EndInit();
|
||||
this.tabPage1.ResumeLayout(false);
|
||||
this.tabPage1.PerformLayout();
|
||||
this.htTabControl1.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label titleLabel;
|
||||
private System.Windows.Forms.TabPage tabPage2;
|
||||
private System.Windows.Forms.TabPage tabPage1;
|
||||
private HTAlt.WinForms.HTTabControl htTabControl1;
|
||||
private System.Windows.Forms.DataGridView configDataGrid;
|
||||
private System.Windows.Forms.Panel panel2;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.Panel panel1;
|
||||
private System.Windows.Forms.TextBox logBox;
|
||||
private System.Windows.Forms.ComboBox logFilesDropdown;
|
||||
private HTAlt.WinForms.HTButton clearLogsBtn;
|
||||
private HTAlt.WinForms.HTButton openLogFolderBtn;
|
||||
private HTAlt.WinForms.HTButton textWrapBtn;
|
||||
private HTAlt.WinForms.HTButton refreshBtn;
|
||||
private System.Windows.Forms.ToolTip toolTip;
|
||||
private HTAlt.WinForms.HTButton monospaceBtn;
|
||||
private HTAlt.WinForms.HTButton copyTextClipboardBtn;
|
||||
}
|
||||
}
|
||||
109
Flowframes/Forms/DebugForm.cs
Normal file
109
Flowframes/Forms/DebugForm.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
3197
Flowframes/Forms/DebugForm.resx
Normal file
3197
Flowframes/Forms/DebugForm.resx
Normal file
File diff suppressed because it is too large
Load Diff
1572
Flowframes/Forms/Main/Form1.Designer.cs
generated
Normal file
1572
Flowframes/Forms/Main/Form1.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
74
Flowframes/Forms/Main/Form1.Properties.cs
Normal file
74
Flowframes/Forms/Main/Form1.Properties.cs
Normal 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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
860
Flowframes/Forms/Main/Form1.cs
Normal file
860
Flowframes/Forms/Main/Form1.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
3211
Flowframes/Forms/Main/Form1.resx
Normal file
3211
Flowframes/Forms/Main/Form1.resx
Normal file
File diff suppressed because it is too large
Load Diff
122
Flowframes/Forms/MessageForm.Designer.cs
generated
Normal file
122
Flowframes/Forms/MessageForm.Designer.cs
generated
Normal 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;
|
||||
}
|
||||
}
|
||||
116
Flowframes/Forms/MessageForm.cs
Normal file
116
Flowframes/Forms/MessageForm.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
120
Flowframes/Forms/MessageForm.resx
Normal file
120
Flowframes/Forms/MessageForm.resx
Normal 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>
|
||||
279
Flowframes/Forms/ModelDownloadForm.Designer.cs
generated
Normal file
279
Flowframes/Forms/ModelDownloadForm.Designer.cs
generated
Normal 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;
|
||||
}
|
||||
}
|
||||
85
Flowframes/Forms/ModelDownloadForm.cs
Normal file
85
Flowframes/Forms/ModelDownloadForm.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
3193
Flowframes/Forms/ModelDownloadForm.resx
Normal file
3193
Flowframes/Forms/ModelDownloadForm.resx
Normal file
File diff suppressed because it is too large
Load Diff
39
Flowframes/Forms/PromptForm.cs
Normal file
39
Flowframes/Forms/PromptForm.cs
Normal 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
102
Flowframes/Forms/PromptForm.designer.cs
generated
Normal 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;
|
||||
}
|
||||
}
|
||||
3070
Flowframes/Forms/PromptForm.resx
Normal file
3070
Flowframes/Forms/PromptForm.resx
Normal file
File diff suppressed because it is too large
Load Diff
1979
Flowframes/Forms/SettingsForm.Designer.cs
generated
Normal file
1979
Flowframes/Forms/SettingsForm.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
254
Flowframes/Forms/SettingsForm.cs
Normal file
254
Flowframes/Forms/SettingsForm.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
3208
Flowframes/Forms/SettingsForm.resx
Normal file
3208
Flowframes/Forms/SettingsForm.resx
Normal file
File diff suppressed because it is too large
Load Diff
118
Flowframes/Forms/TimeoutForm.Designer.cs
generated
Normal file
118
Flowframes/Forms/TimeoutForm.Designer.cs
generated
Normal file
@@ -0,0 +1,118 @@
|
||||
|
||||
namespace Flowframes.Forms
|
||||
{
|
||||
partial class TimeoutForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TimeoutForm));
|
||||
this.mainLabel = new System.Windows.Forms.Label();
|
||||
this.countdownLabel = new System.Windows.Forms.Label();
|
||||
this.cancelActionBtn = new HTAlt.WinForms.HTButton();
|
||||
this.skipCountdownBtn = new HTAlt.WinForms.HTButton();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// mainLabel
|
||||
//
|
||||
this.mainLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.mainLabel.ForeColor = System.Drawing.Color.White;
|
||||
this.mainLabel.Location = new System.Drawing.Point(12, 9);
|
||||
this.mainLabel.Name = "mainLabel";
|
||||
this.mainLabel.Size = new System.Drawing.Size(320, 23);
|
||||
this.mainLabel.TabIndex = 0;
|
||||
this.mainLabel.Text = "Waiting before running action...";
|
||||
this.mainLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// countdownLabel
|
||||
//
|
||||
this.countdownLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.countdownLabel.ForeColor = System.Drawing.Color.White;
|
||||
this.countdownLabel.Location = new System.Drawing.Point(12, 42);
|
||||
this.countdownLabel.Margin = new System.Windows.Forms.Padding(3, 10, 3, 0);
|
||||
this.countdownLabel.Name = "countdownLabel";
|
||||
this.countdownLabel.Size = new System.Drawing.Size(320, 23);
|
||||
this.countdownLabel.TabIndex = 1;
|
||||
this.countdownLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// cancelActionBtn
|
||||
//
|
||||
this.cancelActionBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.cancelActionBtn.FlatAppearance.BorderSize = 0;
|
||||
this.cancelActionBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.cancelActionBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.cancelActionBtn.Location = new System.Drawing.Point(12, 126);
|
||||
this.cancelActionBtn.Name = "cancelActionBtn";
|
||||
this.cancelActionBtn.Size = new System.Drawing.Size(320, 23);
|
||||
this.cancelActionBtn.TabIndex = 39;
|
||||
this.cancelActionBtn.Text = "Cancel Action";
|
||||
this.cancelActionBtn.UseVisualStyleBackColor = false;
|
||||
this.cancelActionBtn.Click += new System.EventHandler(this.cancelActionBtn_Click);
|
||||
//
|
||||
// skipCountdownBtn
|
||||
//
|
||||
this.skipCountdownBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.skipCountdownBtn.FlatAppearance.BorderSize = 0;
|
||||
this.skipCountdownBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.skipCountdownBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.skipCountdownBtn.Location = new System.Drawing.Point(12, 94);
|
||||
this.skipCountdownBtn.Margin = new System.Windows.Forms.Padding(3, 6, 3, 6);
|
||||
this.skipCountdownBtn.Name = "skipCountdownBtn";
|
||||
this.skipCountdownBtn.Size = new System.Drawing.Size(320, 23);
|
||||
this.skipCountdownBtn.TabIndex = 40;
|
||||
this.skipCountdownBtn.Text = "Run Action Now";
|
||||
this.skipCountdownBtn.UseVisualStyleBackColor = false;
|
||||
this.skipCountdownBtn.Click += new System.EventHandler(this.skipCountdownBtn_Click);
|
||||
//
|
||||
// TimeoutForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(32)))), ((int)(((byte)(32)))));
|
||||
this.ClientSize = new System.Drawing.Size(344, 161);
|
||||
this.Controls.Add(this.skipCountdownBtn);
|
||||
this.Controls.Add(this.cancelActionBtn);
|
||||
this.Controls.Add(this.countdownLabel);
|
||||
this.Controls.Add(this.mainLabel);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.Name = "TimeoutForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "Timeout";
|
||||
this.Load += new System.EventHandler(this.TimeoutForm_Load);
|
||||
this.Shown += new System.EventHandler(this.TimeoutForm_Shown);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label mainLabel;
|
||||
private System.Windows.Forms.Label countdownLabel;
|
||||
private HTAlt.WinForms.HTButton cancelActionBtn;
|
||||
private HTAlt.WinForms.HTButton skipCountdownBtn;
|
||||
}
|
||||
}
|
||||
77
Flowframes/Forms/TimeoutForm.cs
Normal file
77
Flowframes/Forms/TimeoutForm.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Flowframes.Forms
|
||||
{
|
||||
public partial class TimeoutForm : Form
|
||||
{
|
||||
string actionName = "";
|
||||
int waitSeconds;
|
||||
|
||||
public delegate void ActionCallback();
|
||||
public static ActionCallback actionCallback;
|
||||
|
||||
bool cancelCountdown = false;
|
||||
|
||||
public TimeoutForm(string action, ActionCallback callback, int waitSecs = 20, string windowTitle = "Timeout")
|
||||
{
|
||||
actionName = action;
|
||||
Text = windowTitle;
|
||||
actionCallback = callback;
|
||||
waitSeconds = waitSecs;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void TimeoutForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void TimeoutForm_Shown(object sender, EventArgs e)
|
||||
{
|
||||
mainLabel.Text = $"Waiting before running action \"{actionName}\"";
|
||||
WaitAndRun();
|
||||
}
|
||||
|
||||
async Task WaitAndRun ()
|
||||
{
|
||||
Show();
|
||||
WindowState = FormWindowState.Normal;
|
||||
Activate();
|
||||
|
||||
for (int i = waitSeconds; i > 0; i--)
|
||||
{
|
||||
countdownLabel.Text = $"{i}s";
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
if (cancelCountdown)
|
||||
return;
|
||||
|
||||
actionCallback();
|
||||
Close();
|
||||
}
|
||||
|
||||
private void skipCountdownBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
cancelCountdown = true;
|
||||
actionCallback();
|
||||
Close();
|
||||
}
|
||||
|
||||
private void cancelActionBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
cancelCountdown = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
3185
Flowframes/Forms/TimeoutForm.resx
Normal file
3185
Flowframes/Forms/TimeoutForm.resx
Normal file
File diff suppressed because it is too large
Load Diff
208
Flowframes/Forms/UpdaterForm.Designer.cs
generated
Normal file
208
Flowframes/Forms/UpdaterForm.Designer.cs
generated
Normal file
@@ -0,0 +1,208 @@
|
||||
namespace Flowframes.Forms
|
||||
{
|
||||
partial class UpdaterForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(UpdaterForm));
|
||||
this.titleLabel = new System.Windows.Forms.Label();
|
||||
this.label13 = new System.Windows.Forms.Label();
|
||||
this.updatePatreonBtn = new System.Windows.Forms.Button();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
this.installedLabel = new System.Windows.Forms.Label();
|
||||
this.latestLabel = new System.Windows.Forms.Label();
|
||||
this.statusLabel = new System.Windows.Forms.Label();
|
||||
this.downloadingLabel = new System.Windows.Forms.Label();
|
||||
this.updateFreeBtn = new System.Windows.Forms.Button();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// titleLabel
|
||||
//
|
||||
this.titleLabel.AutoSize = true;
|
||||
this.titleLabel.Font = new System.Drawing.Font("Yu Gothic UI", 21.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.titleLabel.ForeColor = System.Drawing.Color.White;
|
||||
this.titleLabel.Location = new System.Drawing.Point(12, 9);
|
||||
this.titleLabel.Margin = new System.Windows.Forms.Padding(3, 0, 3, 10);
|
||||
this.titleLabel.Name = "titleLabel";
|
||||
this.titleLabel.Size = new System.Drawing.Size(121, 40);
|
||||
this.titleLabel.TabIndex = 2;
|
||||
this.titleLabel.Text = "Updater";
|
||||
//
|
||||
// label13
|
||||
//
|
||||
this.label13.AutoSize = true;
|
||||
this.label13.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.label13.ForeColor = System.Drawing.Color.White;
|
||||
this.label13.Location = new System.Drawing.Point(17, 67);
|
||||
this.label13.Margin = new System.Windows.Forms.Padding(8, 8, 3, 0);
|
||||
this.label13.Name = "label13";
|
||||
this.label13.Size = new System.Drawing.Size(110, 16);
|
||||
this.label13.TabIndex = 35;
|
||||
this.label13.Text = "Installed Version:";
|
||||
//
|
||||
// updatePatreonBtn
|
||||
//
|
||||
this.updatePatreonBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.updatePatreonBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
|
||||
this.updatePatreonBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.updatePatreonBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.updatePatreonBtn.Location = new System.Drawing.Point(12, 229);
|
||||
this.updatePatreonBtn.Name = "updatePatreonBtn";
|
||||
this.updatePatreonBtn.Size = new System.Drawing.Size(203, 40);
|
||||
this.updatePatreonBtn.TabIndex = 36;
|
||||
this.updatePatreonBtn.Text = "Download Patreon Version";
|
||||
this.updatePatreonBtn.UseVisualStyleBackColor = true;
|
||||
this.updatePatreonBtn.Click += new System.EventHandler(this.updatePatreonBtn_Click);
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.label1.ForeColor = System.Drawing.Color.White;
|
||||
this.label1.Location = new System.Drawing.Point(16, 93);
|
||||
this.label1.Margin = new System.Windows.Forms.Padding(8, 10, 3, 0);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(96, 16);
|
||||
this.label1.TabIndex = 37;
|
||||
this.label1.Text = "Latest Version:";
|
||||
//
|
||||
// label2
|
||||
//
|
||||
this.label2.AutoSize = true;
|
||||
this.label2.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.label2.ForeColor = System.Drawing.Color.White;
|
||||
this.label2.Location = new System.Drawing.Point(16, 119);
|
||||
this.label2.Margin = new System.Windows.Forms.Padding(8, 10, 3, 0);
|
||||
this.label2.Name = "label2";
|
||||
this.label2.Size = new System.Drawing.Size(48, 16);
|
||||
this.label2.TabIndex = 38;
|
||||
this.label2.Text = "Status:";
|
||||
//
|
||||
// installedLabel
|
||||
//
|
||||
this.installedLabel.AutoSize = true;
|
||||
this.installedLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.installedLabel.ForeColor = System.Drawing.Color.White;
|
||||
this.installedLabel.Location = new System.Drawing.Point(170, 67);
|
||||
this.installedLabel.Margin = new System.Windows.Forms.Padding(8, 8, 3, 0);
|
||||
this.installedLabel.Name = "installedLabel";
|
||||
this.installedLabel.Size = new System.Drawing.Size(76, 16);
|
||||
this.installedLabel.TabIndex = 39;
|
||||
this.installedLabel.Text = "Loading...";
|
||||
//
|
||||
// latestLabel
|
||||
//
|
||||
this.latestLabel.AutoSize = true;
|
||||
this.latestLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.latestLabel.ForeColor = System.Drawing.Color.White;
|
||||
this.latestLabel.Location = new System.Drawing.Point(170, 93);
|
||||
this.latestLabel.Margin = new System.Windows.Forms.Padding(8, 8, 3, 0);
|
||||
this.latestLabel.Name = "latestLabel";
|
||||
this.latestLabel.Size = new System.Drawing.Size(76, 16);
|
||||
this.latestLabel.TabIndex = 40;
|
||||
this.latestLabel.Text = "Loading...";
|
||||
//
|
||||
// statusLabel
|
||||
//
|
||||
this.statusLabel.AutoSize = true;
|
||||
this.statusLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.statusLabel.ForeColor = System.Drawing.Color.White;
|
||||
this.statusLabel.Location = new System.Drawing.Point(170, 119);
|
||||
this.statusLabel.Margin = new System.Windows.Forms.Padding(8, 8, 3, 0);
|
||||
this.statusLabel.Name = "statusLabel";
|
||||
this.statusLabel.Size = new System.Drawing.Size(76, 16);
|
||||
this.statusLabel.TabIndex = 41;
|
||||
this.statusLabel.Text = "Loading...";
|
||||
//
|
||||
// downloadingLabel
|
||||
//
|
||||
this.downloadingLabel.AutoSize = true;
|
||||
this.downloadingLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.downloadingLabel.ForeColor = System.Drawing.Color.White;
|
||||
this.downloadingLabel.Location = new System.Drawing.Point(226, 241);
|
||||
this.downloadingLabel.Margin = new System.Windows.Forms.Padding(8, 10, 3, 0);
|
||||
this.downloadingLabel.Name = "downloadingLabel";
|
||||
this.downloadingLabel.Size = new System.Drawing.Size(0, 16);
|
||||
this.downloadingLabel.TabIndex = 42;
|
||||
//
|
||||
// updateFreeBtn
|
||||
//
|
||||
this.updateFreeBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.updateFreeBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
|
||||
this.updateFreeBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.updateFreeBtn.ForeColor = System.Drawing.Color.White;
|
||||
this.updateFreeBtn.Location = new System.Drawing.Point(221, 229);
|
||||
this.updateFreeBtn.Name = "updateFreeBtn";
|
||||
this.updateFreeBtn.Size = new System.Drawing.Size(203, 40);
|
||||
this.updateFreeBtn.TabIndex = 43;
|
||||
this.updateFreeBtn.Text = "Download Free Version";
|
||||
this.updateFreeBtn.UseVisualStyleBackColor = true;
|
||||
this.updateFreeBtn.Click += new System.EventHandler(this.updateFreeBtn_Click);
|
||||
//
|
||||
// UpdaterForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(32)))), ((int)(((byte)(32)))), ((int)(((byte)(32)))));
|
||||
this.ClientSize = new System.Drawing.Size(624, 281);
|
||||
this.Controls.Add(this.updateFreeBtn);
|
||||
this.Controls.Add(this.downloadingLabel);
|
||||
this.Controls.Add(this.statusLabel);
|
||||
this.Controls.Add(this.latestLabel);
|
||||
this.Controls.Add(this.installedLabel);
|
||||
this.Controls.Add(this.label2);
|
||||
this.Controls.Add(this.label1);
|
||||
this.Controls.Add(this.updatePatreonBtn);
|
||||
this.Controls.Add(this.label13);
|
||||
this.Controls.Add(this.titleLabel);
|
||||
this.ForeColor = System.Drawing.Color.White;
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.Name = "UpdaterForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "Updater";
|
||||
this.Load += new System.EventHandler(this.UpdaterForm_Load);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label titleLabel;
|
||||
private System.Windows.Forms.Label label13;
|
||||
private System.Windows.Forms.Button updatePatreonBtn;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.Label label2;
|
||||
private System.Windows.Forms.Label installedLabel;
|
||||
private System.Windows.Forms.Label latestLabel;
|
||||
private System.Windows.Forms.Label statusLabel;
|
||||
private System.Windows.Forms.Label downloadingLabel;
|
||||
private System.Windows.Forms.Button updateFreeBtn;
|
||||
}
|
||||
}
|
||||
82
Flowframes/Forms/UpdaterForm.cs
Normal file
82
Flowframes/Forms/UpdaterForm.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using Flowframes.Data;
|
||||
using Flowframes.Os;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Flowframes.Forms
|
||||
{
|
||||
public partial class UpdaterForm : Form
|
||||
{
|
||||
Version installed;
|
||||
Version latestPat;
|
||||
Version latestFree;
|
||||
|
||||
public UpdaterForm()
|
||||
{
|
||||
AutoScaleMode = AutoScaleMode.None;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void UpdaterForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
installed = Updater.GetInstalledVer();
|
||||
latestPat = Updater.GetLatestVer(true);
|
||||
latestFree = Updater.GetLatestVer(false);
|
||||
|
||||
installedLabel.Text = installed.ToString();
|
||||
await Task.Delay(100);
|
||||
latestLabel.Text = $"{latestPat} (Patreon/Beta) - {latestFree} (Free/Stable)";
|
||||
|
||||
if (Updater.CompareVersions(installed, latestFree) == Updater.VersionCompareResult.Equal)
|
||||
{
|
||||
statusLabel.Text = "Latest Free Version Is Installed.";
|
||||
|
||||
if (Updater.CompareVersions(installed, latestPat) == Updater.VersionCompareResult.Newer)
|
||||
statusLabel.Text += "\nBeta Update Available On Patreon.";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (Updater.CompareVersions(installed, latestPat) == Updater.VersionCompareResult.Equal)
|
||||
{
|
||||
statusLabel.Text = "Latest Patreon/Beta Version Is Installed.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (Updater.CompareVersions(installed, latestPat) == Updater.VersionCompareResult.Newer)
|
||||
{
|
||||
statusLabel.Text = "Update available on Patreon!";
|
||||
|
||||
if (Updater.CompareVersions(installed, latestFree) == Updater.VersionCompareResult.Newer)
|
||||
statusLabel.Text = $"Beta Updates Available On Patreon and Itch.io.";
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
float lastProg = -1f;
|
||||
public void SetProgLabel (float prog, string str)
|
||||
{
|
||||
if (prog == lastProg) return;
|
||||
lastProg = prog;
|
||||
downloadingLabel.Text = str;
|
||||
}
|
||||
|
||||
private void updatePatreonBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
string link = Updater.GetLatestVerLink(true);
|
||||
if(!string.IsNullOrWhiteSpace(link))
|
||||
Process.Start(link);
|
||||
}
|
||||
|
||||
private void updateFreeBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
string link = Updater.GetLatestVerLink(false);
|
||||
if (!string.IsNullOrWhiteSpace(link))
|
||||
Process.Start(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
2445
Flowframes/Forms/UpdaterForm.resx
Normal file
2445
Flowframes/Forms/UpdaterForm.resx
Normal file
File diff suppressed because it is too large
Load Diff
15
Flowframes/IO/CfgStrings.cs
Normal file
15
Flowframes/IO/CfgStrings.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flowframes.IO
|
||||
{
|
||||
class CfgStrings
|
||||
{
|
||||
// public static string dedupMode = "dedupMode";
|
||||
// public static string dedupThresh = "dedupThresh";
|
||||
// public static string keepFrames = "keepFrames";
|
||||
}
|
||||
}
|
||||
369
Flowframes/IO/Config.cs
Normal file
369
Flowframes/IO/Config.cs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
76
Flowframes/IO/ConfigParser.cs
Normal file
76
Flowframes/IO/ConfigParser.cs
Normal 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
1046
Flowframes/IO/IoUtils.cs
Normal file
File diff suppressed because it is too large
Load Diff
193
Flowframes/IO/Logger.cs
Normal file
193
Flowframes/IO/Logger.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
287
Flowframes/IO/ModelDownloader.cs
Normal file
287
Flowframes/IO/ModelDownloader.cs
Normal 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
96
Flowframes/IO/Symlinks.cs
Normal 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
166
Flowframes/Magick/Blend.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using Flowframes.Data;
|
||||
using Flowframes.MiscUtils;
|
||||
using ImageMagick;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Flowframes.IO;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
145
Flowframes/Magick/Converter.cs
Normal file
145
Flowframes/Magick/Converter.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using Flowframes;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Ui;
|
||||
using ImageMagick;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flowframes.Magick
|
||||
{
|
||||
|
||||
class Converter
|
||||
{
|
||||
public static async Task Convert (string dir, MagickFormat format, int quality, string ext = "", bool print = true, bool setProgress = true)
|
||||
{
|
||||
var files = IoUtils.GetFilesSorted(dir);
|
||||
if(print) Logger.Log($"Converting {files.Length} files in {dir}");
|
||||
int counter = 0;
|
||||
foreach (string file in files)
|
||||
{
|
||||
if (print) Logger.Log("Converting " + Path.GetFileName(file) + " to " + format.ToString().StripNumbers().ToUpper(), false, true);
|
||||
MagickImage img = new MagickImage(file);
|
||||
img.Format = format;
|
||||
img.Quality = quality;
|
||||
string outpath = file;
|
||||
if (!string.IsNullOrWhiteSpace(ext)) outpath = Path.ChangeExtension(outpath, ext);
|
||||
img.Write(outpath);
|
||||
counter++;
|
||||
if(setProgress)
|
||||
Program.mainForm.SetProgress((int)Math.Round(((float)counter / files.Length) * 100f));
|
||||
await Task.Delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task MakeBinary (string inputDir, string outputDir, bool print = true, bool setProgress = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var files = IoUtils.GetFilesSorted(inputDir);
|
||||
if (print) Logger.Log($"Processing alpha channel...");
|
||||
Directory.CreateDirectory(outputDir);
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Restart();
|
||||
int counter = 0;
|
||||
foreach (string file in files)
|
||||
{
|
||||
MagickImage img = new MagickImage(file);
|
||||
img.Format = MagickFormat.Png24;
|
||||
img.Quality = 10;
|
||||
img.Threshold(new Percentage(75));
|
||||
|
||||
string outPath = Path.Combine(outputDir, Path.GetFileName(file));
|
||||
img.Write(outPath);
|
||||
counter++;
|
||||
if (sw.ElapsedMilliseconds > 250)
|
||||
{
|
||||
if (setProgress)
|
||||
Program.mainForm.SetProgress((int)Math.Round(((float)counter / files.Length) * 100f));
|
||||
await Task.Delay(1);
|
||||
sw.Restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log("MakeBinary Error: " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ExtractAlpha (string inputDir, string outputDir, bool print = true, bool setProgress = true, bool removeInputAlpha = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var files = IoUtils.GetFilesSorted(inputDir);
|
||||
if (print) Logger.Log($"Extracting alpha channel from images...");
|
||||
Directory.CreateDirectory(outputDir);
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Restart();
|
||||
int counter = 0;
|
||||
foreach (string file in files)
|
||||
{
|
||||
MagickImage alphaImg = new MagickImage(file);
|
||||
|
||||
if (removeInputAlpha)
|
||||
{
|
||||
MagickImage rgbImg = alphaImg;
|
||||
rgbImg.Format = MagickFormat.Png24;
|
||||
rgbImg.Quality = 10;
|
||||
MagickImage bg = new MagickImage(MagickColors.Black, rgbImg.Width, rgbImg.Height);
|
||||
bg.Composite(rgbImg, CompositeOperator.Over);
|
||||
rgbImg = bg;
|
||||
rgbImg.Write(file);
|
||||
}
|
||||
|
||||
alphaImg.Format = MagickFormat.Png24;
|
||||
alphaImg.Quality = 10;
|
||||
|
||||
alphaImg.FloodFill(MagickColors.None, 0, 0); // Fill the image with a transparent background
|
||||
alphaImg.InverseOpaque(MagickColors.None, MagickColors.White); // Change all the pixels that are not transparent to white.
|
||||
alphaImg.ColorAlpha(MagickColors.Black); // Change the transparent pixels to black.
|
||||
|
||||
string outPath = Path.Combine(outputDir, Path.GetFileName(file));
|
||||
alphaImg.Write(outPath);
|
||||
counter++;
|
||||
if (sw.ElapsedMilliseconds > 250)
|
||||
{
|
||||
if (setProgress)
|
||||
Program.mainForm.SetProgress((int)Math.Round(((float)counter / files.Length) * 100f));
|
||||
await Task.Delay(1);
|
||||
sw.Restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log("ExtractAlpha Error: " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Preprocess (string dir, bool setProgress = true)
|
||||
{
|
||||
var files = IoUtils.GetFilesSorted(dir);
|
||||
Logger.Log($"Preprocessing {files} files in {dir}");
|
||||
int counter = 0;
|
||||
foreach (string file in files)
|
||||
{
|
||||
//Logger.Log("Converting " + Path.GetFileName(file) + " to " + format, false, true);
|
||||
MagickImage img = new MagickImage(file);
|
||||
//img.Format = MagickFormat.Bmp;
|
||||
//img.Write(file);
|
||||
//img = new MagickImage(file);
|
||||
img.Format = MagickFormat.Png24;
|
||||
img.Quality = 10;
|
||||
counter++;
|
||||
if (setProgress)
|
||||
Program.mainForm.SetProgress((int)Math.Round(((float)counter / files.Length) * 100f));
|
||||
await Task.Delay(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
313
Flowframes/Magick/Dedupe.cs
Normal file
313
Flowframes/Magick/Dedupe.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
164
Flowframes/Magick/MagickExtensions.cs
Normal file
164
Flowframes/Magick/MagickExtensions.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using Flowframes.MiscUtils;
|
||||
using ImageMagick;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Flowframes.Magick
|
||||
{
|
||||
public enum BitmapDensity
|
||||
{
|
||||
/// <summary>
|
||||
/// Ignore the density of the image when creating the bitmap.
|
||||
/// </summary>
|
||||
Ignore,
|
||||
|
||||
/// <summary>
|
||||
/// Use the density of the image when creating the bitmap.
|
||||
/// </summary>
|
||||
Use,
|
||||
}
|
||||
|
||||
public static class MagickExtensions
|
||||
{
|
||||
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive.")]
|
||||
public static Bitmap ToBitmap(this MagickImage magickImg, BitmapDensity density)
|
||||
{
|
||||
string mapping = "BGR";
|
||||
var format = PixelFormat.Format24bppRgb;
|
||||
|
||||
var image = magickImg;
|
||||
|
||||
try
|
||||
{
|
||||
if (image.ColorSpace != ColorSpace.sRGB)
|
||||
{
|
||||
image = (MagickImage)magickImg.Clone();
|
||||
image.ColorSpace = ColorSpace.sRGB;
|
||||
}
|
||||
|
||||
if (image.HasAlpha)
|
||||
{
|
||||
mapping = "BGRA";
|
||||
format = PixelFormat.Format32bppArgb;
|
||||
}
|
||||
|
||||
using (var pixels = image.GetPixelsUnsafe())
|
||||
{
|
||||
var bitmap = new Bitmap(image.Width, image.Height, format);
|
||||
var data = bitmap.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadWrite, format);
|
||||
var destination = data.Scan0;
|
||||
|
||||
for (int y = 0; y < image.Height; y++)
|
||||
{
|
||||
byte[] bytes = pixels.ToByteArray(0, y, image.Width, 1, mapping);
|
||||
Marshal.Copy(bytes, 0, destination, bytes.Length);
|
||||
|
||||
destination = new IntPtr(destination.ToInt64() + data.Stride);
|
||||
}
|
||||
|
||||
bitmap.UnlockBits(data);
|
||||
SetBitmapDensity(magickImg, bitmap, density);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!ReferenceEquals(image, magickImg))
|
||||
image.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static Bitmap ToBitmap(this MagickImage imageMagick) => ToBitmap(imageMagick, BitmapDensity.Ignore);
|
||||
|
||||
public static Bitmap ToBitmap(this MagickImage imageMagick, ImageFormat imageFormat) => ToBitmap(imageMagick, imageFormat, BitmapDensity.Ignore);
|
||||
|
||||
public static Bitmap ToBitmap(this MagickImage imageMagick, ImageFormat imageFormat, BitmapDensity bitmapDensity)
|
||||
{
|
||||
imageMagick.Format = InternalMagickFormatInfo.GetFormat(imageFormat);
|
||||
|
||||
MemoryStream memStream = new MemoryStream();
|
||||
imageMagick.Write(memStream);
|
||||
memStream.Position = 0;
|
||||
|
||||
/* Do not dispose the memStream, the bitmap owns it. */
|
||||
var bitmap = new Bitmap(memStream);
|
||||
|
||||
SetBitmapDensity(imageMagick, bitmap, bitmapDensity);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public static void FromBitmap(this MagickImage imageMagick, Bitmap bitmap)
|
||||
{
|
||||
using (MemoryStream memStream = new MemoryStream())
|
||||
{
|
||||
if (IsSupportedImageFormat(bitmap.RawFormat))
|
||||
bitmap.Save(memStream, bitmap.RawFormat);
|
||||
else
|
||||
bitmap.Save(memStream, ImageFormat.Bmp);
|
||||
|
||||
memStream.Position = 0;
|
||||
imageMagick.Read(memStream);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsSupportedImageFormat(ImageFormat format)
|
||||
{
|
||||
return
|
||||
format.Guid.Equals(ImageFormat.Bmp.Guid) ||
|
||||
format.Guid.Equals(ImageFormat.Gif.Guid) ||
|
||||
format.Guid.Equals(ImageFormat.Icon.Guid) ||
|
||||
format.Guid.Equals(ImageFormat.Jpeg.Guid) ||
|
||||
format.Guid.Equals(ImageFormat.Png.Guid) ||
|
||||
format.Guid.Equals(ImageFormat.Tiff.Guid);
|
||||
}
|
||||
|
||||
private static void SetBitmapDensity(MagickImage imageMagick, Bitmap bitmap, BitmapDensity bitmapDensity)
|
||||
{
|
||||
if (bitmapDensity == BitmapDensity.Use)
|
||||
{
|
||||
var dpi = GetDpi(imageMagick, bitmapDensity);
|
||||
bitmap.SetResolution((float)dpi.X, (float)dpi.Y);
|
||||
}
|
||||
}
|
||||
|
||||
private static Density GetDpi(MagickImage imageMagick, BitmapDensity bitmapDensity)
|
||||
{
|
||||
if (bitmapDensity == BitmapDensity.Ignore || (imageMagick.Density.Units == DensityUnit.Undefined && imageMagick.Density.X == 0 && imageMagick.Density.Y == 0))
|
||||
return new Density(96);
|
||||
|
||||
return imageMagick.Density.ChangeUnits(DensityUnit.PixelsPerInch);
|
||||
}
|
||||
}
|
||||
|
||||
public class InternalMagickFormatInfo
|
||||
{
|
||||
internal static MagickFormat GetFormat(ImageFormat format)
|
||||
{
|
||||
if (format == ImageFormat.Bmp || format == ImageFormat.MemoryBmp)
|
||||
return MagickFormat.Bmp;
|
||||
else if (format == ImageFormat.Gif)
|
||||
return MagickFormat.Gif;
|
||||
else if (format == ImageFormat.Icon)
|
||||
return MagickFormat.Icon;
|
||||
else if (format == ImageFormat.Jpeg)
|
||||
return MagickFormat.Jpeg;
|
||||
else if (format == ImageFormat.Png)
|
||||
return MagickFormat.Png;
|
||||
else if (format == ImageFormat.Tiff)
|
||||
return MagickFormat.Tiff;
|
||||
else
|
||||
throw new NotSupportedException("Unsupported image format: " + format.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
94
Flowframes/Magick/SceneDetect.cs
Normal file
94
Flowframes/Magick/SceneDetect.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using Flowframes.IO;
|
||||
using ImageMagick;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flowframes.Magick
|
||||
{
|
||||
class SceneDetect
|
||||
{
|
||||
public static async Task RunSceneDetection (string path)
|
||||
{
|
||||
string outFolder = path + "-analyzed";
|
||||
Directory.CreateDirectory(outFolder);
|
||||
string ext = "png";
|
||||
FileInfo[] frames = IoUtils.GetFileInfosSorted(path, false, "*." + ext);
|
||||
|
||||
for (int i = 1; i < frames.Length; i++)
|
||||
{
|
||||
FileInfo frame = frames[i];
|
||||
FileInfo lastFrame = frames[i - 1];
|
||||
Task.Run(() => ProcessFrame(frame, lastFrame, outFolder));
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<string, MagickImage> imageCache = new Dictionary<string, MagickImage>();
|
||||
static MagickImage GetImage(string path, bool allowCaching = true)
|
||||
{
|
||||
if (!allowCaching)
|
||||
return new MagickImage(path);
|
||||
|
||||
if (imageCache.Count >= 30)
|
||||
ClearCache();
|
||||
|
||||
if (!imageCache.ContainsKey(path))
|
||||
imageCache.Add(path, new MagickImage(path));
|
||||
|
||||
return imageCache[path];
|
||||
}
|
||||
|
||||
public static void ClearCache()
|
||||
{
|
||||
imageCache.Clear();
|
||||
}
|
||||
|
||||
static async Task ProcessFrame (FileInfo frame, FileInfo lastFrame, string outFolder)
|
||||
{
|
||||
MagickImage prevFrame = GetImage(lastFrame.FullName, false);
|
||||
MagickImage currFrame = GetImage(frame.FullName, false);
|
||||
|
||||
Size originalSize = new Size(currFrame.Width, currFrame.Height);
|
||||
int downscaleHeight = 144;
|
||||
prevFrame.Scale(currFrame.Width / (currFrame.Height / downscaleHeight), downscaleHeight);
|
||||
currFrame.Scale(currFrame.Width / (currFrame.Height / downscaleHeight), downscaleHeight);
|
||||
|
||||
double errNormalizedCrossCorrelation = currFrame.Compare(prevFrame, ErrorMetric.NormalizedCrossCorrelation);
|
||||
double errRootMeanSquared = currFrame.Compare(prevFrame, ErrorMetric.RootMeanSquared);
|
||||
|
||||
string str = $"\nMetrics of {frame.Name.Split('.')[0]} against {lastFrame.Name.Split('.')[0]}:\n";
|
||||
str += $"NormalizedCrossCorrelation: {errNormalizedCrossCorrelation.ToString("0.000")}\n";
|
||||
str += $"RootMeanSquared: {errRootMeanSquared.ToString("0.000")}\n";
|
||||
str += "\n\n";
|
||||
|
||||
bool nccTrigger = errNormalizedCrossCorrelation < 0.45f;
|
||||
bool rMeanSqrTrigger = errRootMeanSquared > 0.18f;
|
||||
bool rmsNccTrigger = errRootMeanSquared > 0.18f && errNormalizedCrossCorrelation < 0.6f;
|
||||
bool nccRmsTrigger = errNormalizedCrossCorrelation < 0.45f && errRootMeanSquared > 0.11f;
|
||||
|
||||
if (rmsNccTrigger) str += "\n\nRMS -> NCC DOUBLE SCENE CHANGE TRIGGER!";
|
||||
if (nccRmsTrigger) str += "\n\nNCC -> RMS DOUBLE SCENE CHANGE TRIGGER!";
|
||||
|
||||
currFrame.Scale(originalSize.Width / 2, originalSize.Height / 2);
|
||||
|
||||
new Drawables()
|
||||
.FontPointSize(12)
|
||||
.Font("Consolas", FontStyleType.Normal, FontWeight.Bold, FontStretch.Normal)
|
||||
.FillColor(MagickColors.Red)
|
||||
.TextAlignment(TextAlignment.Left)
|
||||
.Text(1, 10, str)
|
||||
.Draw(currFrame);
|
||||
|
||||
currFrame.Write(Path.Combine(outFolder, frame.Name));
|
||||
|
||||
prevFrame.Dispose();
|
||||
currFrame.Dispose();
|
||||
|
||||
await Task.Delay(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
78
Flowframes/Main/AiModels.cs
Normal file
78
Flowframes/Main/AiModels.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
246
Flowframes/Main/AutoEncode.cs
Normal file
246
Flowframes/Main/AutoEncode.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
145
Flowframes/Main/AutoEncodeResume.cs
Normal file
145
Flowframes/Main/AutoEncodeResume.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
137
Flowframes/Main/BatchProcessing.cs
Normal file
137
Flowframes/Main/BatchProcessing.cs
Normal 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
397
Flowframes/Main/Export.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
413
Flowframes/Main/FrameOrder.cs
Normal file
413
Flowframes/Main/FrameOrder.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
319
Flowframes/Main/Interpolate.cs
Normal file
319
Flowframes/Main/Interpolate.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
144
Flowframes/Main/InterpolateSteps.cs
Normal file
144
Flowframes/Main/InterpolateSteps.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
384
Flowframes/Main/InterpolateUtils.cs
Normal file
384
Flowframes/Main/InterpolateUtils.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
121
Flowframes/Main/ResumeUtils.cs
Normal file
121
Flowframes/Main/ResumeUtils.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Flowframes.MiscUtils;
|
||||
using Flowframes.Ui;
|
||||
using I = Flowframes.Interpolate;
|
||||
|
||||
namespace Flowframes.Main
|
||||
{
|
||||
|
||||
class ResumeUtils
|
||||
{
|
||||
// public static bool resumeNextRun;
|
||||
|
||||
public static float timeBetweenSaves = 10;
|
||||
public static int minFrames = 100;
|
||||
public static int safetyDelayFrames = 50;
|
||||
public static string resumeFilename = "resume.ini";
|
||||
public static string interpSettingsFilename = "interpSettings.ini";
|
||||
public static string filenameMapFilename = "frameFilenames.ini";
|
||||
|
||||
public static Stopwatch timeSinceLastSave = new Stopwatch();
|
||||
|
||||
public static void Save ()
|
||||
{
|
||||
if (timeSinceLastSave.IsRunning && timeSinceLastSave.ElapsedMilliseconds < (timeBetweenSaves * 1000f).RoundToInt()) return;
|
||||
int frames = (int)Math.Round((float)InterpolationProgress.interpolatedInputFramesCount / I.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
134
Flowframes/Media/AvOutputHandler.cs
Normal file
134
Flowframes/Media/AvOutputHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
217
Flowframes/Media/AvProcess.cs
Normal file
217
Flowframes/Media/AvProcess.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Flowframes/Media/FfmpegAlpha.cs
Normal file
53
Flowframes/Media/FfmpegAlpha.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
92
Flowframes/Media/FfmpegAudioAndMetadata.cs
Normal file
92
Flowframes/Media/FfmpegAudioAndMetadata.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
312
Flowframes/Media/FfmpegCommands.cs
Normal file
312
Flowframes/Media/FfmpegCommands.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
154
Flowframes/Media/FfmpegEncode.cs
Normal file
154
Flowframes/Media/FfmpegEncode.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
350
Flowframes/Media/FfmpegExtract.cs
Normal file
350
Flowframes/Media/FfmpegExtract.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
592
Flowframes/Media/FfmpegUtils.cs
Normal file
592
Flowframes/Media/FfmpegUtils.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
100
Flowframes/Media/GetFrameCountCached.cs
Normal file
100
Flowframes/Media/GetFrameCountCached.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Flowframes/Media/GetMediaResolutionCached.cs
Normal file
65
Flowframes/Media/GetMediaResolutionCached.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
140
Flowframes/Media/GetVideoInfo.cs
Normal file
140
Flowframes/Media/GetVideoInfo.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Flowframes/MigratePkgs.ps1
Normal file
82
Flowframes/MigratePkgs.ps1
Normal 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
|
||||
68
Flowframes/MiscUtils/BackgroundTaskManager.cs
Normal file
68
Flowframes/MiscUtils/BackgroundTaskManager.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flowframes.MiscUtils
|
||||
{
|
||||
class BackgroundTaskManager
|
||||
{
|
||||
public static ulong currentId = 0;
|
||||
public static List<RunningTask> runningTasks = new List<RunningTask>();
|
||||
|
||||
public class RunningTask
|
||||
{
|
||||
public NmkdStopwatch timer;
|
||||
public string name;
|
||||
public ulong id;
|
||||
public int timeoutSeconds;
|
||||
|
||||
public RunningTask (string name, ulong id, int timeoutSeconds)
|
||||
{
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this.timeoutSeconds = timeoutSeconds;
|
||||
timer = new NmkdStopwatch();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsBusy ()
|
||||
{
|
||||
Logger.Log($"[BgTaskMgr] BackgroundTaskManager is busy - {runningTasks.Count} tasks running.", true);
|
||||
return runningTasks.Count > 0;
|
||||
}
|
||||
|
||||
public static void ClearExpired ()
|
||||
{
|
||||
foreach(RunningTask task in runningTasks)
|
||||
{
|
||||
if(task.timer.Sw.ElapsedMilliseconds > task.timeoutSeconds * 1000)
|
||||
{
|
||||
Logger.Log($"[BgTaskMgr] Task with ID {task.id} timed out, has been running for {task.timer}!", 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Flowframes/MiscUtils/Benchmarker.cs
Normal file
24
Flowframes/MiscUtils/Benchmarker.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace Flowframes.MiscUtils
|
||||
{
|
||||
class Benchmarker
|
||||
{
|
||||
// Benchmark a method with return type (via Delegate/Func)
|
||||
public static object BenchmarkMethod(string methodName, Delegate method, params object[] args)
|
||||
{
|
||||
NmkdStopwatch sw = new NmkdStopwatch();
|
||||
var returnVal = method.DynamicInvoke(args);
|
||||
Logger.Log($"Ran {methodName} in {sw}", 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
Reference in New Issue
Block a user