Switched to fractional framerates, fixed VFR inputs

This commit is contained in:
N00MKRAD
2021-04-02 14:36:08 +02:00
parent 60ee6c2102
commit b3ab84646b
17 changed files with 379 additions and 75 deletions

227
Code/Data/Fraction.cs Normal file
View 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();
}
}
}

View File

@@ -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";

View File

@@ -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};
}
}
}
}
}

View File

@@ -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" />

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 }

View File

@@ -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");

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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");

View File

@@ -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();

View File

@@ -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();