mirror of
https://github.com/n00mkrad/flowframes.git
synced 2026-09-01 19:51:28 +02:00
Switched to fractional framerates, fixed VFR inputs
This commit is contained in:
227
Code/Data/Fraction.cs
Normal file
227
Code/Data/Fraction.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
using System;
|
||||
|
||||
namespace Flowframes.Data
|
||||
{
|
||||
public struct Fraction
|
||||
{
|
||||
public int Numerator;
|
||||
public int Denominator;
|
||||
public static Fraction Zero = new Fraction(0, 0);
|
||||
|
||||
public Fraction(int numerator, int denominator)
|
||||
{
|
||||
this.Numerator = numerator;
|
||||
this.Denominator = denominator;
|
||||
|
||||
//If denominator negative...
|
||||
if (this.Denominator < 0)
|
||||
{
|
||||
//...move the negative up to the numerator
|
||||
this.Numerator = -this.Numerator;
|
||||
this.Denominator = -this.Denominator;
|
||||
}
|
||||
}
|
||||
|
||||
public Fraction(int numerator, Fraction denominator)
|
||||
{
|
||||
//divide the numerator by the denominator fraction
|
||||
this = new Fraction(numerator, 1) / denominator;
|
||||
}
|
||||
|
||||
public Fraction(Fraction numerator, int denominator)
|
||||
{
|
||||
//multiply the numerator fraction by 1 over the denominator
|
||||
this = numerator * new Fraction(1, denominator);
|
||||
}
|
||||
|
||||
public Fraction(Fraction fraction)
|
||||
{
|
||||
Numerator = fraction.Numerator;
|
||||
Denominator = fraction.Denominator;
|
||||
}
|
||||
|
||||
public Fraction(float value)
|
||||
{
|
||||
int[] frac = value.ToFraction();
|
||||
Numerator = frac[0];
|
||||
Denominator = frac[1];
|
||||
}
|
||||
|
||||
public Fraction(string text)
|
||||
{
|
||||
string[] numbers = text.Split('/');
|
||||
Numerator = numbers[0].GetInt();
|
||||
Denominator = numbers[1].GetInt();
|
||||
}
|
||||
|
||||
private static int getGCD(int a, int b)
|
||||
{
|
||||
//Drop negative signs
|
||||
a = Math.Abs(a);
|
||||
b = Math.Abs(b);
|
||||
|
||||
//Return the greatest common denominator between two integers
|
||||
while (a != 0 && b != 0)
|
||||
{
|
||||
if (a > b)
|
||||
a %= b;
|
||||
else
|
||||
b %= a;
|
||||
}
|
||||
|
||||
if (a == 0)
|
||||
return b;
|
||||
else
|
||||
return a;
|
||||
}
|
||||
|
||||
private static int getLCD(int a, int b)
|
||||
{
|
||||
//Return the Least Common Denominator between two integers
|
||||
return (a * b) / getGCD(a, b);
|
||||
}
|
||||
|
||||
|
||||
public Fraction ToDenominator(int targetDenominator)
|
||||
{
|
||||
//Multiply the fraction by a factor to make the denominator
|
||||
//match the target denominator
|
||||
Fraction modifiedFraction = this;
|
||||
|
||||
//Cannot reduce to smaller denominators
|
||||
if (targetDenominator < this.Denominator)
|
||||
return modifiedFraction;
|
||||
|
||||
//The target denominator must be a factor of the current denominator
|
||||
if (targetDenominator % this.Denominator != 0)
|
||||
return modifiedFraction;
|
||||
|
||||
if (this.Denominator != targetDenominator)
|
||||
{
|
||||
int factor = targetDenominator / this.Denominator;
|
||||
modifiedFraction.Denominator = targetDenominator;
|
||||
modifiedFraction.Numerator *= factor;
|
||||
}
|
||||
|
||||
return modifiedFraction;
|
||||
}
|
||||
|
||||
public Fraction GetReduced()
|
||||
{
|
||||
//Reduce the fraction to lowest terms
|
||||
Fraction modifiedFraction = this;
|
||||
|
||||
//While the numerator and denominator share a greatest common denominator,
|
||||
//keep dividing both by it
|
||||
int gcd = 0;
|
||||
while (Math.Abs(gcd = getGCD(modifiedFraction.Numerator, modifiedFraction.Denominator)) != 1)
|
||||
{
|
||||
modifiedFraction.Numerator /= gcd;
|
||||
modifiedFraction.Denominator /= gcd;
|
||||
}
|
||||
|
||||
//Make sure only a single negative sign is on the numerator
|
||||
if (modifiedFraction.Denominator < 0)
|
||||
{
|
||||
modifiedFraction.Numerator = -this.Numerator;
|
||||
modifiedFraction.Denominator = -this.Denominator;
|
||||
}
|
||||
|
||||
return modifiedFraction;
|
||||
}
|
||||
|
||||
public Fraction GetReciprocal()
|
||||
{
|
||||
//Flip the numerator and the denominator
|
||||
return new Fraction(this.Denominator, this.Numerator);
|
||||
}
|
||||
|
||||
|
||||
public static Fraction operator +(Fraction fraction1, Fraction fraction2)
|
||||
{
|
||||
//Check if either fraction is zero
|
||||
if (fraction1.Denominator == 0)
|
||||
return fraction2;
|
||||
else if (fraction2.Denominator == 0)
|
||||
return fraction1;
|
||||
|
||||
//Get Least Common Denominator
|
||||
int lcd = getLCD(fraction1.Denominator, fraction2.Denominator);
|
||||
|
||||
//Transform the fractions
|
||||
fraction1 = fraction1.ToDenominator(lcd);
|
||||
fraction2 = fraction2.ToDenominator(lcd);
|
||||
|
||||
//Return sum
|
||||
return new Fraction(fraction1.Numerator + fraction2.Numerator, lcd).GetReduced();
|
||||
}
|
||||
|
||||
public static Fraction operator -(Fraction fraction1, Fraction fraction2)
|
||||
{
|
||||
//Get Least Common Denominator
|
||||
int lcd = getLCD(fraction1.Denominator, fraction2.Denominator);
|
||||
|
||||
//Transform the fractions
|
||||
fraction1 = fraction1.ToDenominator(lcd);
|
||||
fraction2 = fraction2.ToDenominator(lcd);
|
||||
|
||||
//Return difference
|
||||
return new Fraction(fraction1.Numerator - fraction2.Numerator, lcd).GetReduced();
|
||||
}
|
||||
|
||||
public static Fraction operator *(Fraction fract, int multi)
|
||||
{
|
||||
int numerator = fract.Numerator * multi;
|
||||
int denomenator = fract.Denominator;
|
||||
|
||||
return new Fraction(numerator, denomenator).GetReduced();
|
||||
}
|
||||
|
||||
public static Fraction operator *(Fraction fract, float multi)
|
||||
{
|
||||
int numerator = (fract.Numerator * multi).RoundToInt();
|
||||
int denomenator = fract.Denominator;
|
||||
|
||||
return new Fraction(numerator, denomenator).GetReduced();
|
||||
}
|
||||
|
||||
public static Fraction operator *(Fraction fraction1, Fraction fraction2)
|
||||
{
|
||||
int numerator = fraction1.Numerator * fraction2.Numerator;
|
||||
int denomenator = fraction1.Denominator * fraction2.Denominator;
|
||||
|
||||
return new Fraction(numerator, denomenator).GetReduced();
|
||||
}
|
||||
|
||||
public static Fraction operator /(Fraction fraction1, Fraction fraction2)
|
||||
{
|
||||
return new Fraction(fraction1 * fraction2.GetReciprocal()).GetReduced();
|
||||
}
|
||||
|
||||
|
||||
public double ToDouble()
|
||||
{
|
||||
return (double)this.Numerator / this.Denominator;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Numerator + "/" + Denominator;
|
||||
}
|
||||
|
||||
public float GetFloat()
|
||||
{
|
||||
return (float)Numerator / (float)Denominator;
|
||||
}
|
||||
|
||||
public long GetLong()
|
||||
{
|
||||
return (long)Numerator / (long)Denominator;
|
||||
}
|
||||
|
||||
public string GetString()
|
||||
{
|
||||
return ((float)Numerator / Denominator).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ namespace Flowframes
|
||||
public string inPath;
|
||||
public string outPath;
|
||||
public AI ai;
|
||||
public float inFps;
|
||||
public float outFps;
|
||||
public Fraction inFps;
|
||||
public Fraction outFps;
|
||||
public float interpFactor;
|
||||
public Interpolate.OutMode outMode;
|
||||
public string model;
|
||||
@@ -34,7 +34,7 @@ namespace Flowframes
|
||||
public bool alpha;
|
||||
public bool stepByStep;
|
||||
|
||||
public InterpSettings(string inPathArg, string outPathArg, AI aiArg, float inFpsArg, int interpFactorArg, Interpolate.OutMode outModeArg, string modelArg)
|
||||
public InterpSettings(string inPathArg, string outPathArg, AI aiArg, Fraction inFpsArg, int interpFactorArg, Interpolate.OutMode outModeArg, string modelArg)
|
||||
{
|
||||
inPath = inPathArg;
|
||||
outPath = outPathArg;
|
||||
@@ -73,9 +73,9 @@ namespace Flowframes
|
||||
inPath = "";
|
||||
outPath = "";
|
||||
ai = Networks.networks[0];
|
||||
inFps = 0;
|
||||
inFps = new Fraction();
|
||||
interpFactor = 0;
|
||||
outFps = 0;
|
||||
outFps = new Fraction();
|
||||
outMode = Interpolate.OutMode.VidMp4;
|
||||
model = "";
|
||||
alpha = false;
|
||||
@@ -99,8 +99,8 @@ namespace Flowframes
|
||||
case "INPATH": inPath = entry.Value; break;
|
||||
case "OUTPATH": outPath = entry.Value; break;
|
||||
case "AI": ai = Networks.GetAi(entry.Value); break;
|
||||
case "INFPS": inFps = float.Parse(entry.Value); break;
|
||||
case "OUTFPS": outFps = float.Parse(entry.Value); break;
|
||||
case "INFPS": inFps = new Fraction(entry.Value); break;
|
||||
case "OUTFPS": outFps = new Fraction(entry.Value); break;
|
||||
case "INTERPFACTOR": interpFactor = float.Parse(entry.Value); break;
|
||||
case "OUTMODE": outMode = (Interpolate.OutMode)Enum.Parse(typeof(Interpolate.OutMode), entry.Value); break;
|
||||
case "MODEL": model = entry.Value; break;
|
||||
@@ -192,8 +192,8 @@ namespace Flowframes
|
||||
string s = $"INPATH|{inPath}\n";
|
||||
s += $"OUTPATH|{outPath}\n";
|
||||
s += $"AI|{ai.aiName}\n";
|
||||
s += $"INFPS|{inFps.ToStringDot()}\n";
|
||||
s += $"OUTFPS|{outFps.ToStringDot()}\n";
|
||||
s += $"INFPS|{inFps}\n";
|
||||
s += $"OUTFPS|{outFps}\n";
|
||||
s += $"INTERPFACTOR|{interpFactor}\n";
|
||||
s += $"OUTMODE|{outMode}\n";
|
||||
s += $"MODEL|{model}\n";
|
||||
|
||||
@@ -185,5 +185,57 @@ namespace Flowframes
|
||||
{
|
||||
return str.Split(new string[] { trimStr }, StringSplitOptions.None);
|
||||
}
|
||||
|
||||
public static int[] ToFraction(this float value, double accuracy = 0.02)
|
||||
{
|
||||
int sign = Math.Sign(value);
|
||||
|
||||
if (sign == -1)
|
||||
value = Math.Abs(value);
|
||||
|
||||
// Accuracy is the maximum relative error; convert to absolute maxError
|
||||
double maxError = sign == 0 ? accuracy : value * accuracy;
|
||||
|
||||
int n = (int)Math.Floor(value);
|
||||
value -= n;
|
||||
|
||||
if (value < maxError)
|
||||
return new int[] { sign * n, 1 };
|
||||
|
||||
if (1 - maxError < value)
|
||||
return new int[] { sign * (n + 1), 1 };
|
||||
|
||||
// The lower fraction is 0/1
|
||||
int lower_n = 0;
|
||||
int lower_d = 1;
|
||||
|
||||
// The upper fraction is 1/1
|
||||
int upper_n = 1;
|
||||
int upper_d = 1;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// The middle fraction is (lower_n + upper_n) / (lower_d + upper_d)
|
||||
int middle_n = lower_n + upper_n;
|
||||
int middle_d = lower_d + upper_d;
|
||||
|
||||
if (middle_d * (value + maxError) < middle_n)
|
||||
{
|
||||
// real + error < middle : middle is our new upper
|
||||
upper_n = middle_n;
|
||||
upper_d = middle_d;
|
||||
}
|
||||
else if (middle_n < (value - maxError) * middle_d)
|
||||
{
|
||||
// middle < real - error : middle is our new lower
|
||||
lower_n = middle_n;
|
||||
lower_d = middle_d;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new int[] {(n * middle_d + middle_n) * sign, middle_d};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,6 +321,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="Data\AI.cs" />
|
||||
<Compile Include="Data\AudioTrack.cs" />
|
||||
<Compile Include="Data\Fraction.cs" />
|
||||
<Compile Include="Data\InterpSettings.cs" />
|
||||
<Compile Include="Data\Networks.cs" />
|
||||
<Compile Include="Data\Padding.cs" />
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace Flowframes
|
||||
public InterpSettings GetCurrentSettings()
|
||||
{
|
||||
SetTab("interpolate");
|
||||
return new InterpSettings(inputTbox.Text.Trim(), outputTbox.Text.Trim(), GetAi(), fpsInTbox.GetFloat(), interpFactorCombox.GetInt(), GetOutMode(), GetModel());
|
||||
return new InterpSettings(inputTbox.Text.Trim(), outputTbox.Text.Trim(), GetAi(), currInFps, interpFactorCombox.GetInt(), GetOutMode(), GetModel());
|
||||
}
|
||||
|
||||
public void LoadBatchEntry(InterpSettings entry)
|
||||
@@ -160,16 +160,16 @@ namespace Flowframes
|
||||
}
|
||||
|
||||
public Size currInRes;
|
||||
public float currInFps;
|
||||
public Fraction currInFps;
|
||||
public int currInFrames;
|
||||
public long currInDuration;
|
||||
public long currInDurationCut;
|
||||
|
||||
public void UpdateInputInfo ()
|
||||
{
|
||||
string str = $"Resolution: {(!currInRes.IsEmpty ? $"{currInRes.Width}x{currInRes.Height}" : "Unknown")} - ";
|
||||
str += $"Framerate: {(currInFps > 0f ? $"{currInFps.ToStringDot()} FPS" : "Unknown")} - ";
|
||||
str += $"Frame Count: {(currInFrames > 0 ? $"{currInFrames}" : "Unknown")} - ";
|
||||
string str = $"Size: {(!currInRes.IsEmpty ? $"{currInRes.Width}x{currInRes.Height}" : "Unknown")} - ";
|
||||
str += $"Rate: {(currInFps.GetFloat() > 0f ? $"{currInFps} ({currInFps.GetFloat()})" : "Unknown")} - ";
|
||||
str += $"Frames: {(currInFrames > 0 ? $"{currInFrames}" : "Unknown")} - ";
|
||||
str += $"Duration: {(currInDuration > 0 ? FormatUtils.MsToTimestamp(currInDuration) : "Unknown")}";
|
||||
inputInfo.Text = str;
|
||||
}
|
||||
@@ -177,7 +177,7 @@ namespace Flowframes
|
||||
public void ResetInputInfo ()
|
||||
{
|
||||
currInRes = new Size();
|
||||
currInFps = 0;
|
||||
currInFps = new Fraction();
|
||||
currInFrames = 0;
|
||||
currInDuration = 0;
|
||||
currInDurationCut = 0;
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Flowframes.Data;
|
||||
|
||||
namespace Flowframes.Forms
|
||||
{
|
||||
@@ -129,7 +130,7 @@ namespace Flowframes.Forms
|
||||
|
||||
InterpSettings current = Program.mainForm.GetCurrentSettings();
|
||||
current.UpdatePaths(path, path.GetParentDir());
|
||||
current.inFps = await GetFramerate(path);
|
||||
current.inFps = (await GetFramerate(path));
|
||||
current.outFps = current.inFps * current.interpFactor;
|
||||
Program.batchQueue.Enqueue(current);
|
||||
RefreshGui();
|
||||
@@ -137,11 +138,11 @@ namespace Flowframes.Forms
|
||||
}
|
||||
}
|
||||
|
||||
async Task<float> GetFramerate (string path)
|
||||
async Task<Fraction> GetFramerate (string path)
|
||||
{
|
||||
float fps = Interpolate.current.inFps;
|
||||
float fpsFromFile = await IOUtils.GetFpsFolderOrVideo(path);
|
||||
if (fpsFromFile > 0)
|
||||
Fraction fps = Interpolate.current.inFps;
|
||||
Fraction fpsFromFile = await IOUtils.GetFpsFolderOrVideo(path);
|
||||
if (fpsFromFile.GetFloat() > 0)
|
||||
return fpsFromFile;
|
||||
|
||||
return fps;
|
||||
|
||||
@@ -293,9 +293,9 @@ namespace Flowframes.IO
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<float> GetVideoFramerate (string path)
|
||||
public static async Task<Fraction> GetVideoFramerate (string path)
|
||||
{
|
||||
float fps = 0;
|
||||
Fraction fps = new Fraction();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -310,17 +310,18 @@ namespace Flowframes.IO
|
||||
return fps;
|
||||
}
|
||||
|
||||
public static float GetVideoFramerateForDir(string path)
|
||||
public static Fraction GetVideoFramerateForDir(string path)
|
||||
{
|
||||
float fps = 0;
|
||||
Fraction fps = new Fraction();
|
||||
|
||||
try
|
||||
{
|
||||
string parentDir = path.GetParentDir();
|
||||
string fpsFile = Path.Combine(parentDir, "fps.ini");
|
||||
fps = float.Parse(ReadLines(fpsFile)[0]);
|
||||
fps = new Fraction(float.Parse(ReadLines(fpsFile)[0]));
|
||||
Logger.Log($"Got {fps} FPS from file: " + fpsFile);
|
||||
|
||||
float guiFps = Program.mainForm.GetCurrentSettings().inFps;
|
||||
Fraction guiFps = Program.mainForm.GetCurrentSettings().inFps;
|
||||
|
||||
DialogResult dialogResult = MessageBox.Show("A frame rate file has been found in the parent directory.\n\n" +
|
||||
$"Click \"Yes\" to use frame rate from the file ({fps}) or \"No\" to use current FPS set in GUI ({guiFps})", "Load Frame Rate From fps.ini?", MessageBoxButtons.YesNo);
|
||||
@@ -399,7 +400,7 @@ namespace Flowframes.IO
|
||||
public static string GetCurrentExportFilename(bool fpsLimit, bool withExt)
|
||||
{
|
||||
InterpSettings curr = Interpolate.current;
|
||||
float fps = fpsLimit ? Config.GetFloat("maxFps") : curr.outFps;
|
||||
float fps = fpsLimit ? Config.GetFloat("maxFps") : curr.outFps.GetFloat();
|
||||
|
||||
string pattern = Config.Get("exportNamePattern");
|
||||
string inName = Interpolate.current.inputIsFrames ? Path.GetFileName(curr.inPath) : Path.GetFileNameWithoutExtension(curr.inPath);
|
||||
@@ -453,22 +454,22 @@ namespace Flowframes.IO
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<float> GetFpsFolderOrVideo(string path)
|
||||
public static async Task<Fraction> GetFpsFolderOrVideo(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsPathDirectory(path))
|
||||
{
|
||||
float dirFps = GetVideoFramerateForDir(path);
|
||||
Fraction dirFps = GetVideoFramerateForDir(path);
|
||||
|
||||
if (dirFps > 0)
|
||||
if (dirFps.GetFloat() > 0)
|
||||
return dirFps;
|
||||
}
|
||||
else
|
||||
{
|
||||
float vidFps = await GetVideoFramerate(path);
|
||||
Fraction vidFps = await GetVideoFramerate(path);
|
||||
|
||||
if (vidFps > 0)
|
||||
if (vidFps.GetFloat() > 0)
|
||||
return vidFps;
|
||||
}
|
||||
}
|
||||
@@ -477,7 +478,7 @@ namespace Flowframes.IO
|
||||
Logger.Log("GetFpsFolderOrVideo() Error: " + e.Message);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return new Fraction();
|
||||
}
|
||||
|
||||
public enum ErrorMode { HiddenLog, VisibleLog, Messagebox }
|
||||
|
||||
@@ -207,7 +207,7 @@ namespace Flowframes.Magick
|
||||
|
||||
public static async Task CreateDupesFile (string framesPath, int lastFrameNum)
|
||||
{
|
||||
string infoFile = Path.Combine(framesPath.GetParentDir(), $"dupes.ini");
|
||||
string infoFile = Path.Combine(framesPath.GetParentDir(), "dupes.ini");
|
||||
string fileContent = "";
|
||||
|
||||
FileInfo[] frameFiles = IOUtils.GetFileInfosSorted(framesPath, false, "*.png");
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Windows.Forms;
|
||||
using Padding = Flowframes.Data.Padding;
|
||||
using I = Flowframes.Interpolate;
|
||||
using System.Diagnostics;
|
||||
using Flowframes.Data;
|
||||
using Flowframes.Media;
|
||||
|
||||
namespace Flowframes.Main
|
||||
@@ -51,7 +52,7 @@ namespace Flowframes.Main
|
||||
try
|
||||
{
|
||||
float maxFps = Config.GetFloat("maxFps");
|
||||
bool fpsLimit = maxFps != 0 && I.current.outFps > maxFps;
|
||||
bool fpsLimit = maxFps != 0 && I.current.outFps.GetFloat() > maxFps;
|
||||
|
||||
bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt("maxFpsMode") == 0;
|
||||
|
||||
@@ -102,12 +103,12 @@ namespace Flowframes.Main
|
||||
}
|
||||
}
|
||||
|
||||
static async Task Encode(I.OutMode mode, string framesPath, string outPath, float fps, float resampleFps = -1)
|
||||
static async Task Encode(I.OutMode mode, string framesPath, string outPath, Fraction fps, float resampleFps = -1)
|
||||
{
|
||||
string currentOutFile = outPath;
|
||||
string vfrFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(I.current.interpFactor));
|
||||
string framesFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(I.current.interpFactor));
|
||||
|
||||
if (!File.Exists(vfrFile))
|
||||
if (!File.Exists(framesFile))
|
||||
{
|
||||
bool sbs = Config.GetInt("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?" : "")}");
|
||||
@@ -116,11 +117,11 @@ namespace Flowframes.Main
|
||||
|
||||
if (mode == I.OutMode.VidGif)
|
||||
{
|
||||
await FfmpegEncode.FramesToGifConcat(vfrFile, outPath, fps, true, Config.GetInt("gifColors"), resampleFps);
|
||||
await FfmpegEncode.FramesToGifConcat(framesFile, outPath, fps, true, Config.GetInt("gifColors"), resampleFps);
|
||||
}
|
||||
else
|
||||
{
|
||||
await FfmpegEncode.FramesToVideoConcat(vfrFile, outPath, mode, fps, resampleFps);
|
||||
await FfmpegEncode.FramesToVideoConcat(framesFile, outPath, mode, fps, resampleFps);
|
||||
await MuxOutputVideo(I.current.inPath, outPath);
|
||||
await Loop(currentOutFile, GetLoopTimes());
|
||||
}
|
||||
@@ -179,7 +180,7 @@ namespace Flowframes.Main
|
||||
await Blend.BlendSceneChanges(framesFileChunk, false);
|
||||
|
||||
float maxFps = Config.GetFloat("maxFps");
|
||||
bool fpsLimit = maxFps != 0 && I.current.outFps > maxFps;
|
||||
bool fpsLimit = maxFps != 0 && I.current.outFps.GetFloat() > maxFps;
|
||||
|
||||
bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt("maxFpsMode") == 0;
|
||||
|
||||
@@ -206,9 +207,9 @@ namespace Flowframes.Main
|
||||
{
|
||||
int times = -1;
|
||||
int minLength = Config.GetInt("minOutVidLength");
|
||||
int minFrameCount = (minLength * I.current.outFps).RoundToInt();
|
||||
int minFrameCount = (minLength * I.current.outFps.GetFloat()).RoundToInt();
|
||||
int outFrames = (I.currentInputFrameCount * I.current.interpFactor).RoundToInt();
|
||||
if (outFrames / I.current.outFps < minLength)
|
||||
if (outFrames / I.current.outFps.GetFloat() < minLength)
|
||||
times = (int)Math.Ceiling((double)minFrameCount / (double)outFrames);
|
||||
times--; // Not counting the 1st play (0 loops)
|
||||
if (times <= 0) return -1; // Never try to loop 0 times, idk what would happen, probably nothing
|
||||
|
||||
@@ -72,9 +72,9 @@ namespace Flowframes.Main
|
||||
frameFiles = new DirectoryInfo(framesPath).GetFiles($"*.png");
|
||||
frameFilesWithoutLast = frameFiles;
|
||||
Array.Resize(ref frameFilesWithoutLast, frameFilesWithoutLast.Length - 1);
|
||||
string vfrFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(interpFactor));
|
||||
string framesFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(interpFactor));
|
||||
string fileContent = "";
|
||||
string dupesFile = Path.Combine(framesPath.GetParentDir(), $"dupes.ini");
|
||||
string dupesFile = Path.Combine(framesPath.GetParentDir(), "dupes.ini");
|
||||
LoadDupesFile(dupesFile);
|
||||
|
||||
string scnFramesPath = Path.Combine(framesPath.GetParentDir(), Paths.scenesDir);
|
||||
@@ -109,7 +109,7 @@ namespace Flowframes.Main
|
||||
if (loop)
|
||||
fileContent = fileContent.Remove(fileContent.LastIndexOf("\n"));
|
||||
|
||||
File.WriteAllText(vfrFile, fileContent);
|
||||
File.WriteAllText(framesFile, fileContent);
|
||||
|
||||
if (notFirstRun) return; // Skip all steps that only need to be done once
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ namespace Flowframes
|
||||
if(!Config.GetBool("enableLoop"))
|
||||
await Utils.CopyLastFrame(currentInputFrameCount);
|
||||
|
||||
if (Config.GetInt("dedupMode") > 0)
|
||||
// if (Config.GetInt("dedupMode") > 0)
|
||||
await Dedupe.CreateDupesFile(current.framesFolder, currentInputFrameCount);
|
||||
|
||||
if (canceled) return;
|
||||
|
||||
@@ -295,7 +295,7 @@ namespace Flowframes.Main
|
||||
return Path.Combine(basePath, Path.GetFileNameWithoutExtension(inPath).StripBadChars().Remove(" ").Trunc(30, false) + "-temp");
|
||||
}
|
||||
|
||||
public static bool InputIsValid(string inDir, string outDir, float fpsOut, float factor, Interpolate.OutMode outMode)
|
||||
public static bool InputIsValid(string inDir, string outDir, Fraction fpsOut, float factor, Interpolate.OutMode outMode)
|
||||
{
|
||||
bool passes = true;
|
||||
|
||||
@@ -316,12 +316,12 @@ namespace Flowframes.Main
|
||||
ShowMessage("Interpolation factor is not valid!");
|
||||
passes = false;
|
||||
}
|
||||
if (passes && outMode == I.OutMode.VidGif && fpsOut > 50 && !(Config.GetFloat("maxFps") != 0 && Config.GetFloat("maxFps") <= 50))
|
||||
if (passes && outMode == I.OutMode.VidGif && fpsOut.GetFloat() > 50 && !(Config.GetFloat("maxFps") != 0 && Config.GetFloat("maxFps") <= 50))
|
||||
{
|
||||
ShowMessage("Invalid output frame rate!\nGIF does not properly support frame rates above 50 FPS.\nPlease use MP4, WEBM or another video format.");
|
||||
passes = false;
|
||||
}
|
||||
if (passes && fpsOut < 1 || fpsOut > 1000)
|
||||
if (passes && fpsOut.GetFloat() < 1f || fpsOut.GetFloat() > 1000f)
|
||||
{
|
||||
ShowMessage("Invalid output frame rate - Must be 1-1000.");
|
||||
passes = false;
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualBasic.Logging;
|
||||
using static Flowframes.AvProcess;
|
||||
using Utils = Flowframes.Media.FFmpegUtils;
|
||||
|
||||
@@ -39,7 +40,7 @@ namespace Flowframes
|
||||
Logger.Log($"Merging videos...", false, Logger.GetLastLine().Contains("frame"));
|
||||
string loopStr = (looptimes > 0) ? $"-stream_loop {looptimes}" : "";
|
||||
string vfrFilename = Path.GetFileName(concatFile);
|
||||
string args = $" {loopStr} -vsync 1 -f concat -i {vfrFilename} -c copy -movflags +faststart {outPath.Wrap()}";
|
||||
string args = $" {loopStr} -vsync 1 -f concat -i {vfrFilename} -c copy -movflags +faststart -fflags +genpts {outPath.Wrap()}";
|
||||
await RunFfmpeg(args, concatFile.GetParentDir(), LogMode.Hidden, TaskType.Merge);
|
||||
}
|
||||
|
||||
@@ -75,15 +76,31 @@ namespace Flowframes
|
||||
return FormatUtils.TimestampToMs(output);
|
||||
}
|
||||
|
||||
public static async Task<float> GetFramerate(string inputFile)
|
||||
public static async Task<Fraction> GetFramerate(string inputFile)
|
||||
{
|
||||
Logger.Log($"GetFramerate('{inputFile}')", true, false, "ffmpeg");
|
||||
|
||||
try
|
||||
{
|
||||
string args = $" -i {inputFile.Wrap()}";
|
||||
string output = await GetFfmpegOutputAsync(args);
|
||||
string[] entries = output.Split(',');
|
||||
try
|
||||
{
|
||||
string ffprobeArgs =
|
||||
$"-v panic -select_streams v:0 -show_entries stream=r_frame_rate {inputFile.Wrap()}";
|
||||
string ffprobeOutput = GetFfprobeOutput(ffprobeArgs);
|
||||
string fpsStr = ffprobeOutput.SplitIntoLines().Where(x => x.Contains("r_frame_rate")).First();
|
||||
string[] numbers = fpsStr.Split('=')[1].Split('/');
|
||||
Logger.Log($"Accurate FPS: {numbers[0]}/{numbers[1]} = {((float)numbers[0].GetInt() / numbers[1].GetInt())}", true, false, "ffmpeg");
|
||||
return new Fraction(numbers[0].GetInt(), numbers[1].GetInt());
|
||||
}
|
||||
catch (Exception ffprobeEx)
|
||||
{
|
||||
Logger.Log("GetFramerate ffprobe Error: " + ffprobeEx.Message, true, false);
|
||||
}
|
||||
|
||||
|
||||
string ffmpegArgs = $" -i {inputFile.Wrap()}";
|
||||
string ffmpegOutput = await GetFfmpegOutputAsync(ffmpegArgs);
|
||||
string[] entries = ffmpegOutput.Split(',');
|
||||
|
||||
foreach (string entry in entries)
|
||||
{
|
||||
@@ -92,16 +109,16 @@ namespace Flowframes
|
||||
string num = entry.Replace(" fps", "").Trim().Replace(",", ".");
|
||||
float value;
|
||||
float.TryParse(num, NumberStyles.Any, CultureInfo.InvariantCulture, out value);
|
||||
return value;
|
||||
return new Fraction(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
catch(Exception ffmpegEx)
|
||||
{
|
||||
Logger.Log("GetFramerate Error: " + e.Message, true, false);
|
||||
Logger.Log("GetFramerate ffmpeg Error: " + ffmpegEx.Message, true, false);
|
||||
}
|
||||
|
||||
return 0f;
|
||||
return new Fraction(0, 1);
|
||||
}
|
||||
|
||||
public static Size GetSize(string inputFile)
|
||||
|
||||
@@ -16,12 +16,12 @@ namespace Flowframes.Media
|
||||
{
|
||||
partial class FfmpegEncode : FfmpegCommands
|
||||
{
|
||||
public static async Task FramesToVideoConcat(string framesFile, string outPath, Interpolate.OutMode outMode, float fps, LogMode logMode = LogMode.OnlyLastLine, bool isChunk = false)
|
||||
public static async Task FramesToVideoConcat(string framesFile, string outPath, Interpolate.OutMode outMode, Fraction fps, LogMode logMode = LogMode.OnlyLastLine, bool isChunk = false)
|
||||
{
|
||||
await FramesToVideoConcat(framesFile, outPath, outMode, fps, 0, logMode, isChunk);
|
||||
}
|
||||
|
||||
public static async Task FramesToVideoConcat(string framesFile, string outPath, Interpolate.OutMode outMode, float fps, float resampleFps, LogMode logMode = LogMode.OnlyLastLine, bool isChunk = false)
|
||||
public static async Task FramesToVideoConcat(string framesFile, string outPath, Interpolate.OutMode outMode, Fraction fps, float resampleFps, LogMode logMode = LogMode.OnlyLastLine, bool isChunk = false)
|
||||
{
|
||||
if (logMode != LogMode.Hidden)
|
||||
Logger.Log((resampleFps <= 0) ? $"Encoding video..." : $"Encoding video resampled to {resampleFps.ToString().Replace(",", ".")} FPS...");
|
||||
@@ -36,7 +36,7 @@ namespace Flowframes.Media
|
||||
await RunFfmpeg(args, framesFile.GetParentDir(), logMode, "error", TaskType.Encode, !isChunk);
|
||||
}
|
||||
|
||||
public static async Task FramesToGifConcat(string framesFile, string outPath, float fps, bool palette, int colors = 64, float resampleFps = -1, LogMode logMode = LogMode.OnlyLastLine)
|
||||
public static async Task FramesToGifConcat(string framesFile, string outPath, Fraction rate, bool palette, int colors = 64, float resampleFps = -1, LogMode logMode = LogMode.OnlyLastLine)
|
||||
{
|
||||
if (logMode != LogMode.Hidden)
|
||||
Logger.Log((resampleFps <= 0) ? $"Encoding GIF..." : $"Encoding GIF resampled to {resampleFps.ToString().Replace(",", ".")} FPS...");
|
||||
@@ -44,7 +44,6 @@ namespace Flowframes.Media
|
||||
string paletteFilter = palette ? $"-vf \"split[s0][s1];[s0]palettegen={colors}[p];[s1][p]paletteuse=dither=floyd_steinberg\"" : "";
|
||||
string fpsFilter = (resampleFps <= 0) ? "" : $"fps=fps={resampleFps.ToStringDot()}";
|
||||
string vf = FormatUtils.ConcatStrings(new string[] { paletteFilter, fpsFilter });
|
||||
string rate = fps.ToStringDot();
|
||||
string args = $"-f concat -r {rate} -i {vfrFilename.Wrap()} -f gif {vf} {outPath.Wrap()}";
|
||||
await RunFfmpeg(args, framesFile.GetParentDir(), LogMode.OnlyLastLine, "error", TaskType.Encode);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Flowframes.Media
|
||||
{
|
||||
partial class FfmpegExtract : FfmpegCommands
|
||||
{
|
||||
public static async Task ExtractSceneChanges(string inPath, string outDir, float rate, bool inputIsFrames = false)
|
||||
public static async Task ExtractSceneChanges(string inPath, string outDir, Fraction rate, bool inputIsFrames = false)
|
||||
{
|
||||
Logger.Log("Extracting scene changes...");
|
||||
Directory.CreateDirectory(outDir);
|
||||
@@ -31,8 +31,8 @@ namespace Flowframes.Media
|
||||
}
|
||||
|
||||
string scnDetect = $"-vf \"select='gt(scene,{Config.GetFloatString("scnDetectValue")})'\"";
|
||||
string rateArg = (rate > 0) ? $"-r {rate.ToStringDot()}" : "";
|
||||
string args = $"{rateArg} -vsync 0 {GetTrimArg(true)} {inArg} {compr} {scnDetect} -frame_pts true -s 256x144 {GetTrimArg(false)} \"{outDir}/%{Padding.inputFrames}d.png\"";
|
||||
string rateArg = (rate.GetFloat() > 0) ? $"-r {rate}" : "";
|
||||
string args = $"{rateArg} -vsync 0 {GetTrimArg(true)} {inArg} {compr} {scnDetect} -frame_pts 1 -s 256x144 {GetTrimArg(false)} \"{outDir}/%{Padding.inputFrames}d.png\"";
|
||||
|
||||
LogMode logMode = Interpolate.currentInputFrameCount > 50 ? LogMode.OnlyLastLine : LogMode.Hidden;
|
||||
await RunFfmpeg(args, logMode, inputIsFrames ? "panic" : "warning", TaskType.ExtractFrames, true);
|
||||
@@ -42,18 +42,17 @@ namespace Flowframes.Media
|
||||
Logger.Log($"Detected {amount} scene {(amount == 1 ? "change" : "changes")}.".Replace(" 0 ", " no "), false, !hiddenLog);
|
||||
}
|
||||
|
||||
public static async Task VideoToFrames(string inputFile, string framesDir, bool alpha, float rate, bool deDupe, bool delSrc, Size size)
|
||||
public static async Task VideoToFrames(string inputFile, string framesDir, bool alpha, Fraction rate, bool deDupe, bool delSrc, Size size)
|
||||
{
|
||||
Logger.Log("Extracting video frames from input video...");
|
||||
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
|
||||
IOUtils.CreateDir(framesDir);
|
||||
string timecodeStr = /* timecodes ? $"-copyts -r {FrameOrder.timebase} -frame_pts true" : */ "-frame_pts true";
|
||||
string mpStr = deDupe ? ((Config.GetInt("mpdecimateMode") == 0) ? mpDecDef : mpDecAggr) : "";
|
||||
string filters = FormatUtils.ConcatStrings(new string[] { GetPadFilter(), mpStr });
|
||||
string vf = filters.Length > 2 ? $"-vf {filters}" : "";
|
||||
string rateArg = (rate > 0) ? $" -r {rate.ToStringDot()}" : "";
|
||||
string rateArg = (rate.GetFloat() > 0) ? $" -r {rate}" : "";
|
||||
string pixFmt = alpha ? "-pix_fmt rgba" : "-pix_fmt rgb24"; // Use RGBA for GIF for alpha support
|
||||
string args = $"{rateArg} {GetTrimArg(true)} -i {inputFile.Wrap()} {compr} -vsync 0 {pixFmt} {timecodeStr} {vf} {sizeStr} {GetTrimArg(false)} \"{framesDir}/%{Padding.inputFrames}d.png\"";
|
||||
string args = $"{GetTrimArg(true)} -i {inputFile.Wrap()} {compr} -vsync 0 {pixFmt} {rateArg} -frame_pts 1 {vf} {sizeStr} {GetTrimArg(false)} \"{framesDir}/%{Padding.inputFrames}d.png\"";
|
||||
LogMode logMode = Interpolate.currentInputFrameCount > 50 ? LogMode.OnlyLastLine : LogMode.Hidden;
|
||||
await RunFfmpeg(args, logMode, TaskType.ExtractFrames, true);
|
||||
int amount = IOUtils.GetAmountOfFiles(framesDir, false, "*.png");
|
||||
|
||||
@@ -84,8 +84,13 @@ namespace Flowframes
|
||||
Program.mainForm.SetProgress(100);
|
||||
InterpolateUtils.UpdateInterpProgress(IOUtils.GetAmountOfFiles(Interpolate.current.interpFolder, false, "*.png"), InterpolateUtils.targetFrames);
|
||||
string logStr = $"Done running {aiName} - Interpolation took {FormatUtils.Time(processTime.Elapsed)}. Peak Output FPS: {InterpolateUtils.peakFpsOut.ToString("0.00")}";
|
||||
|
||||
if (Interpolate.currentlyUsingAutoEnc && AutoEncode.HasWorkToDo())
|
||||
{
|
||||
logStr += " - Waiting for encoding to finish...";
|
||||
Program.mainForm.SetStatus("Creating output video from frames...");
|
||||
}
|
||||
|
||||
Logger.Log(logStr);
|
||||
processTime.Stop();
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Flowframes.Data;
|
||||
|
||||
namespace Flowframes.UI
|
||||
{
|
||||
@@ -38,14 +39,14 @@ namespace Flowframes.UI
|
||||
Program.mainForm.currInDurationCut = Program.mainForm.currInDuration;
|
||||
int frameCount = await InterpolateUtils.GetInputFrameCountAsync(path);
|
||||
string fpsStr = "Not Found";
|
||||
float fps = await IOUtils.GetFpsFolderOrVideo(path);
|
||||
fpsInTbox.Text = fps.ToString();
|
||||
Fraction fps = (await IOUtils.GetFpsFolderOrVideo(path));
|
||||
fpsInTbox.Text = fps.GetString();
|
||||
|
||||
if (fps > 0)
|
||||
fpsStr = fps.ToString();
|
||||
if (fps.GetFloat() > 0)
|
||||
fpsStr = $"{fps} (~{fps.GetFloat()})";
|
||||
|
||||
Logger.Log($"Video FPS: {fpsStr} - Total Number Of Frames: {frameCount}", false, true);
|
||||
Program.mainForm.GetInputFpsTextbox().ReadOnly = (fps > 0 && !Config.GetBool("allowCustomInputRate", false));
|
||||
Program.mainForm.GetInputFpsTextbox().ReadOnly = (fps.GetFloat() > 0 && !Config.GetBool("allowCustomInputRate", false));
|
||||
Program.mainForm.currInFps = fps;
|
||||
Program.mainForm.currInFrames = frameCount;
|
||||
Program.mainForm.UpdateInputInfo();
|
||||
|
||||
Reference in New Issue
Block a user