mirror of
https://github.com/n00mkrad/flowframes.git
synced 2025-12-16 08:27:44 +01:00
Full VFR handling including FPS downsampling to CFR
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
{
|
||||
public class Enums
|
||||
{
|
||||
public enum Round { Near, Up, Down }
|
||||
|
||||
public class Output
|
||||
{
|
||||
public enum Format { Mp4, Mkv, Webm, Mov, Avi, Gif, Images, Realtime };
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
namespace Flowframes.Data
|
||||
{
|
||||
public struct Fraction
|
||||
public class Fraction
|
||||
{
|
||||
public long Numerator;
|
||||
public long Denominator;
|
||||
public long Numerator = 0;
|
||||
public long Denominator = 1;
|
||||
public static Fraction Zero = new Fraction(0, 0);
|
||||
|
||||
public Fraction() { }
|
||||
|
||||
public Fraction(long numerator, long denominator)
|
||||
{
|
||||
this.Numerator = numerator;
|
||||
@@ -22,18 +24,6 @@ namespace Flowframes.Data
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -44,7 +34,9 @@ namespace Flowframes.Data
|
||||
{
|
||||
Numerator = (value * 10000f).RoundToInt();
|
||||
Denominator = 10000;
|
||||
this = GetReduced();
|
||||
var reducedFrac = GetReduced();
|
||||
Numerator = reducedFrac.Numerator;
|
||||
Denominator = reducedFrac.Denominator;
|
||||
}
|
||||
|
||||
public Fraction(string text)
|
||||
@@ -76,7 +68,9 @@ namespace Flowframes.Data
|
||||
else
|
||||
{
|
||||
// Use float constructor if not a whole number
|
||||
this = new Fraction(numFloat);
|
||||
var floatFrac = new Fraction(numFloat);
|
||||
Numerator = floatFrac.Numerator;
|
||||
Denominator = floatFrac.Denominator;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
@@ -4,12 +4,9 @@ using Flowframes.IO;
|
||||
using Flowframes.Main;
|
||||
using Flowframes.MiscUtils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Flowframes
|
||||
{
|
||||
@@ -19,10 +16,11 @@ namespace Flowframes
|
||||
public string outPath;
|
||||
public string FullOutPath { get; set; } = "";
|
||||
public AiInfo ai;
|
||||
public string inPixFmt = "yuv420p";
|
||||
public Fraction inFps;
|
||||
public Fraction inFpsDetected;
|
||||
public Fraction outFps;
|
||||
public Fraction outFpsResampled;
|
||||
public bool FpsResampling => outFpsResampled != null && outFpsResampled.Float > 0.1f && outFpsResampled.Float < outFps.Float;
|
||||
public float outItsScale;
|
||||
public float interpFactor;
|
||||
public OutputSettings outSettings;
|
||||
@@ -86,56 +84,23 @@ namespace Flowframes
|
||||
|
||||
public InterpSettings() { }
|
||||
|
||||
public InterpSettings(string inPathArg, string outPathArg, AiInfo 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;
|
||||
|
||||
outFpsResampled = new Fraction(Config.Get(Config.Key.maxFps));
|
||||
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);
|
||||
|
||||
SetPaths(inPath);
|
||||
RefreshExtensions(ai: ai);
|
||||
}
|
||||
|
||||
public void UpdatePaths (string inPathArg, string outPathArg)
|
||||
private void SetPaths (string inputPath)
|
||||
{
|
||||
inPath = inPathArg;
|
||||
outPath = outPathArg;
|
||||
inPath = inputPath;
|
||||
outPath = (Config.GetInt("outFolderLoc") == 0) ? inputPath.GetParentDir() : Config.Get("custOutDir").Trim();
|
||||
tempFolder = InterpolateUtils.GetTempFolderLoc(inPath, outPath);
|
||||
framesFolder = Path.Combine(tempFolder, Paths.framesDir);
|
||||
interpFolder = Path.Combine(tempFolder, Paths.interpDir);
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Flowframes.Data
|
||||
public string Format;
|
||||
public string Title;
|
||||
public string Language;
|
||||
public Fraction? InputRate = null;
|
||||
public Fraction InputRate;
|
||||
public long DurationMs;
|
||||
public int StreamCount;
|
||||
public int TotalKbits;
|
||||
@@ -42,6 +42,8 @@ namespace Flowframes.Data
|
||||
public bool IsVfr = false;
|
||||
public List<float> InputTimestamps = new List<float>();
|
||||
public List<float> InputTimestampDurations = new List<float>();
|
||||
public List<float> OutputTimestamps = new List<float>();
|
||||
public List<int> OutputFrameIndexes = null;
|
||||
|
||||
public int FileCount = 1;
|
||||
public int FrameCount { get { return VideoStreams.Count > 0 ? VideoStreams[0].FrameCount : 0; } }
|
||||
@@ -140,35 +142,6 @@ namespace Flowframes.Data
|
||||
TotalKbits = (await GetVideoInfo.GetFfprobeInfoAsync(path, GetVideoInfo.FfprobeMode.ShowFormat, "bit_rate")).GetInt() / 1000;
|
||||
}
|
||||
|
||||
public List<float> GetResampledTimestamps(List<float> timestamps, double factor)
|
||||
{
|
||||
int originalCount = timestamps.Count;
|
||||
int newCount = (int)Math.Round(originalCount * factor);
|
||||
List<float> resampledTimestamps = new List<float>();
|
||||
|
||||
for (int i = 0; i < newCount; i++)
|
||||
{
|
||||
double x = i / factor;
|
||||
|
||||
if (x >= originalCount - 1)
|
||||
{
|
||||
resampledTimestamps.Add(timestamps[originalCount - 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
int index = (int)Math.Floor(x);
|
||||
double fraction = x - index;
|
||||
float startTime = timestamps[index];
|
||||
float endTime = timestamps[index + 1];
|
||||
|
||||
float interpolatedTime = (float)(startTime + (endTime - startTime) * fraction);
|
||||
resampledTimestamps.Add(interpolatedTime);
|
||||
}
|
||||
}
|
||||
|
||||
return resampledTimestamps;
|
||||
}
|
||||
|
||||
public string GetName()
|
||||
{
|
||||
if (IsDirectory)
|
||||
|
||||
@@ -494,6 +494,7 @@
|
||||
<Compile Include="Media\GetMediaResolutionCached.cs" />
|
||||
<Compile Include="Media\GetVideoInfo.cs" />
|
||||
<Compile Include="Media\HwEncCheck.cs" />
|
||||
<Compile Include="Media\TimestampUtils.cs" />
|
||||
<Compile Include="MiscUtils\Benchmarker.cs" />
|
||||
<Compile Include="MiscUtils\FrameRename.cs" />
|
||||
<Compile Include="MiscUtils\ModelDownloadFormUtils.cs" />
|
||||
|
||||
@@ -153,9 +153,6 @@ namespace Flowframes.Forms
|
||||
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;
|
||||
|
||||
|
||||
@@ -321,29 +321,6 @@ namespace Flowframes.Forms.Main
|
||||
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;
|
||||
|
||||
@@ -597,9 +597,7 @@ namespace Flowframes.IO
|
||||
public static async Task<string> GetCurrentExportFilename(bool fpsLimit, bool isImgSeq = false, bool includeExt = true)
|
||||
{
|
||||
InterpSettings curr = Interpolate.currentSettings;
|
||||
string max = Config.Get(Config.Key.maxFps);
|
||||
Fraction maxFps = max.Contains("/") ? new Fraction(max) : new Fraction(max.GetFloat());
|
||||
float fps = fpsLimit ? maxFps.Float : curr.outFps.Float;
|
||||
float fps = fpsLimit ? curr.outFpsResampled.Float : curr.outFps.Float;
|
||||
|
||||
Size outRes = curr.OutputResolution; // TODO: Replace with EncodeResolution once implemented?
|
||||
string pattern = Config.Get(Config.Key.exportNamePattern);
|
||||
|
||||
@@ -264,27 +264,21 @@ namespace Flowframes.Magick
|
||||
|
||||
public static async Task CreateFramesFileVideo(string videoPath, bool loop)
|
||||
{
|
||||
if (!Directory.Exists(Interpolate.currentSettings.tempFolder))
|
||||
Directory.CreateDirectory(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(wrap: false); // 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 ffmpegOutputLines = (await Task.Run(() => OsUtils.GetProcStdOut(ffmpeg, true))).SplitIntoLines();
|
||||
|
||||
var frames = new Dictionary<int, List<int>>();
|
||||
var frameNums = new List<int>();
|
||||
int lastKeepFrameNum = 0;
|
||||
|
||||
for (int frameIdx = 0; frameIdx < ffmpegOutputLines.Count; frameIdx++)
|
||||
for (int frameIdx = 0; frameIdx < ffmpegOutputLines.Length; 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 (line.Contains(" keep pts:"))
|
||||
{
|
||||
if (!frames.ContainsKey(frameIdx) || frames[frameIdx] == null)
|
||||
{
|
||||
@@ -293,7 +287,7 @@ namespace Flowframes.Magick
|
||||
|
||||
lastKeepFrameNum = frameIdx;
|
||||
}
|
||||
else
|
||||
else if (line.Contains(" drop pts:"))
|
||||
{
|
||||
frames[lastKeepFrameNum].Add(frameIdx);
|
||||
}
|
||||
@@ -301,6 +295,8 @@ namespace Flowframes.Magick
|
||||
|
||||
var inputFrames = new List<int>(frames.Keys);
|
||||
|
||||
Logger.Log($"Dedupe: Kept {inputFrames.Count}/{ffmpegOutputLines.Length} frames", true);
|
||||
|
||||
if (loop)
|
||||
{
|
||||
inputFrames.Add(inputFrames.First());
|
||||
|
||||
@@ -19,8 +19,7 @@ namespace Flowframes.Main
|
||||
{
|
||||
class Export
|
||||
{
|
||||
private static string MaxFps => Config.Get(Config.Key.maxFps);
|
||||
private static Fraction MaxFpsFrac => new Fraction(MaxFps);
|
||||
private static Fraction MaxFpsFrac => I.currentSettings.outFpsResampled;
|
||||
|
||||
public static async Task ExportFrames(string path, string outFolder, OutputSettings exportSettings, bool stepByStep)
|
||||
{
|
||||
@@ -67,7 +66,7 @@ namespace Flowframes.Main
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log("FramesToVideo Error: " + e.Message, false);
|
||||
Logger.Log($"{nameof(ExportFrames)} 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);
|
||||
}
|
||||
}
|
||||
@@ -79,7 +78,7 @@ namespace Flowframes.Main
|
||||
bool fpsLimit = MaxFpsFrac.Float > 0f && s.outFps.Float > MaxFpsFrac.Float;
|
||||
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, extraData.Rotation);
|
||||
string extraArgsIn = await FfmpegEncode.GetFfmpegExportArgsIn(s.FpsResampling ? s.outFpsResampled : s.outFps, s.outItsScale, extraData.Rotation);
|
||||
string extraArgsOut = await FfmpegEncode.GetFfmpegExportArgsOut(fpsLimit ? MaxFpsFrac : new Fraction(), extraData, s.outSettings);
|
||||
|
||||
// For EXR, force bt709 input flags. Not sure if this really does anything, EXR
|
||||
@@ -121,8 +120,7 @@ namespace Flowframes.Main
|
||||
Enums.Encoding.Encoder desiredFormat = I.currentSettings.outSettings.Encoder;
|
||||
string availableFormat = Path.GetExtension(IoUtils.GetFilesSorted(framesPath, "*.*")[0]).Remove(".").Upper();
|
||||
|
||||
Fraction maxFps = new Fraction(MaxFps);
|
||||
bool fpsLimit = maxFps.Float > 0f && I.currentSettings.outFps.Float > maxFps.Float;
|
||||
bool fpsLimit = MaxFpsFrac.Float > 0f && I.currentSettings.outFps.Float > MaxFpsFrac.Float;
|
||||
bool encodeFullFpsSeq = !(fpsLimit && Config.GetInt(Config.Key.maxFpsMode) == 0);
|
||||
string framesFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(I.currentSettings.interpFactor));
|
||||
|
||||
@@ -141,8 +139,8 @@ namespace Flowframes.Main
|
||||
if (fpsLimit)
|
||||
{
|
||||
string outputFolderPath = Path.Combine(I.currentSettings.outPath, await IoUtils.GetCurrentExportFilename(fpsLimit: true, isImgSeq: true));
|
||||
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));
|
||||
Logger.Log($"Exporting {desiredFormat.ToString().Upper()} frames to '{Path.GetFileName(outputFolderPath)}' (Resampled to {MaxFpsFrac} FPS)...");
|
||||
await FfmpegEncode.FramesToFrames(framesFile, outputFolderPath, 1, I.currentSettings.outFps, MaxFpsFrac, desiredFormat, OutputUtils.GetImgSeqQ(I.currentSettings.outSettings));
|
||||
}
|
||||
|
||||
if (!stepByStep)
|
||||
@@ -389,24 +387,19 @@ namespace Flowframes.Main
|
||||
{
|
||||
if (I.currentSettings.dedupe)
|
||||
{
|
||||
Logger.Log($"{nameof(MuxTimestamps)}: Dedupe was used; won't mux timestamps for '{vidPath}'", hidden: true);
|
||||
Logger.Log($"{nameof(MuxTimestamps)}: Dedupe was used; won't mux timestamps.", hidden: true);
|
||||
return;
|
||||
}
|
||||
|
||||
if(I.currentMediaFile.IsVfr && I.currentMediaFile.OutputFrameIndexes != null && I.currentMediaFile.OutputFrameIndexes.Count > 0)
|
||||
{
|
||||
Logger.Log($"{nameof(MuxTimestamps)}: CFR conversion due to FPS limit was applied (picked {I.currentMediaFile.OutputFrameIndexes.Count} frames for {I.currentSettings.outFpsResampled} FPS); won't mux timestamps.", hidden: true);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Log($"{nameof(MuxTimestamps)}: Muxing timestamps for '{vidPath}'", hidden: true);
|
||||
float avgDuration = I.currentMediaFile.InputTimestampDurations.Average();
|
||||
I.currentMediaFile.InputTimestamps.Add(I.currentMediaFile.InputTimestamps.Last() + avgDuration); // Add extra frame using avg. duration, needed for duration matching or looping
|
||||
|
||||
var resampledTs = I.currentMediaFile.GetResampledTimestamps(I.currentMediaFile.InputTimestamps, I.currentSettings.interpFactor);
|
||||
var tsFileLines = new List<string>() { "# timecode format v2" };
|
||||
|
||||
for (int i = 0; i < (resampledTs.Count - 1); i++)
|
||||
{
|
||||
tsFileLines.Add((resampledTs[i] * 1000f).ToString("0.000000"));
|
||||
}
|
||||
|
||||
string tsFile = Path.Combine(Paths.GetSessionDataPath(), "ts.out.txt");
|
||||
File.WriteAllLines(tsFile, tsFileLines);
|
||||
string tsFile = Path.Combine(Paths.GetSessionDataPath(), "ts.txt");
|
||||
TimestampUtils.WriteTsFile(I.currentMediaFile.OutputTimestamps, tsFile);
|
||||
string outPath = Path.ChangeExtension(vidPath, ".tmp.mkv");
|
||||
string args = $"mkvmerge --output {outPath.Wrap()} --timestamps \"0:{tsFile}\" {vidPath.Wrap()}";
|
||||
var outputMux = NUtilsTemp.OsUtils.RunCommand($"cd /D {Path.Combine(Paths.GetPkgPath(), Paths.audioVideoDir).Wrap()} && {args}");
|
||||
@@ -414,14 +407,14 @@ namespace Flowframes.Main
|
||||
// Check if file exists and is not too small (min. 80% of input file)
|
||||
if (File.Exists(outPath) && ((double)new FileInfo(outPath).Length / (double)new FileInfo(vidPath).Length) > 0.8d)
|
||||
{
|
||||
Logger.Log($"{nameof(MuxTimestamps)}: Deleting '{vidPath}' and moving '{outPath}' to '{vidPath}'", hidden: true);
|
||||
Logger.Log($"{nameof(MuxTimestamps)}: Deleting original '{vidPath}' and moving muxed '{outPath}' to '{vidPath}'", hidden: true);
|
||||
File.Delete(vidPath);
|
||||
File.Move(outPath, vidPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log(outputMux, hidden: true);
|
||||
Logger.Log($"{nameof(MuxTimestamps)}: Timestamp muxing failed, keeping original video file", hidden: true);
|
||||
Logger.Log(outputMux, hidden: true);
|
||||
IoUtils.TryDeleteIfExists(outPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Padding = Flowframes.Data.Padding;
|
||||
using Utils = Flowframes.Main.InterpolateUtils;
|
||||
using System.Drawing.Imaging;
|
||||
|
||||
namespace Flowframes
|
||||
{
|
||||
@@ -27,7 +28,7 @@ namespace Flowframes
|
||||
public static MediaFile currentMediaFile;
|
||||
public static bool canceled = false;
|
||||
public static float InterpProgressMultiplier = 1f;
|
||||
static Stopwatch sw = new Stopwatch();
|
||||
private static Stopwatch sw = new Stopwatch();
|
||||
|
||||
public static async Task Start()
|
||||
{
|
||||
@@ -45,6 +46,11 @@ namespace Flowframes
|
||||
Program.mainForm.SetStatus("Starting...");
|
||||
sw.Restart();
|
||||
|
||||
if (currentMediaFile.IsVfr)
|
||||
{
|
||||
TimestampUtils.CalcTimestamps(currentMediaFile, currentSettings);
|
||||
}
|
||||
|
||||
if (!AutoEncodeResume.resumeNextRun && !(currentSettings.ai.Piped && !currentSettings.inputIsFrames /* && Config.GetInt(Config.Key.dedupMode) == 0) */))
|
||||
{
|
||||
await GetFrames();
|
||||
|
||||
@@ -20,17 +20,7 @@ namespace Flowframes.Main
|
||||
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 = Program.mainForm.GetCurrentSettings();
|
||||
currentSettings.RefreshAlpha();
|
||||
currentSettings.stepByStep = true;
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace Flowframes.Main
|
||||
|
||||
public static string GetTempFolderLoc(string inPath, string outPath)
|
||||
{
|
||||
string basePath = Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"), "Temp", "Flowframes");
|
||||
string basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Temp", "Flowframes");
|
||||
int tempFolderLoc = Config.GetInt(Config.Key.tempFolderLoc);
|
||||
|
||||
switch (tempFolderLoc)
|
||||
@@ -96,7 +96,7 @@ namespace Flowframes.Main
|
||||
break;
|
||||
}
|
||||
|
||||
string folderName = Path.GetFileNameWithoutExtension(inPath).StripBadChars().Remove(" ").Trunc(30, false) + ".tmp";
|
||||
string folderName = Path.GetFileNameWithoutExtension(inPath).StripBadChars().Remove(" ").Trunc(35, false) + "_tmp";
|
||||
return Path.Combine(basePath, folderName);
|
||||
}
|
||||
|
||||
@@ -131,23 +131,17 @@ namespace Flowframes.Main
|
||||
passes = false;
|
||||
}
|
||||
|
||||
string fpsLimitValue = Config.Get(Config.Key.maxFps);
|
||||
float fpsLimit = (fpsLimitValue.Contains("/") ? new Fraction(fpsLimitValue).Float : fpsLimitValue.GetFloat());
|
||||
int maxFps = s.outSettings.Encoder.GetInfo().MaxFramerate;
|
||||
float fpsLimit = s.outFpsResampled.Float;
|
||||
int maxEncoderFps = s.outSettings.Encoder.GetInfo().MaxFramerate;
|
||||
|
||||
if (passes && s.outFps.Float < 1f || (s.outFps.Float > maxFps && !(fpsLimit > 0 && fpsLimit <= maxFps)))
|
||||
if (passes && s.outFps.Float < 1f || (s.outFps.Float > maxEncoderFps && !(fpsLimit > 0 && fpsLimit <= maxEncoderFps)))
|
||||
{
|
||||
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.Float}).\nMust be 1-{maxFps}. Either lower the interpolation factor or use the \"Maximum Output Frame Rate\" option.{imgSeqNote}");
|
||||
UiUtils.ShowMessageBox($"Invalid output frame rate ({s.outFps.Float}).\nMust be 1-{maxEncoderFps}. 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.Float)
|
||||
Interpolate.InterpProgressMultiplier = s.outFps.Float / fpsLimitFloat;
|
||||
else
|
||||
Interpolate.InterpProgressMultiplier = 1f;
|
||||
I.InterpProgressMultiplier = s.FpsResampling ? s.outFps.Float / fpsLimit : 1f;
|
||||
|
||||
if (!passes)
|
||||
I.Cancel("Invalid settings detected.", true);
|
||||
|
||||
@@ -6,6 +6,7 @@ using static Flowframes.AvProcess;
|
||||
using Utils = Flowframes.Media.FfmpegUtils;
|
||||
using I = Flowframes.Interpolate;
|
||||
using Flowframes.Main;
|
||||
using System.Linq;
|
||||
|
||||
namespace Flowframes.Media
|
||||
{
|
||||
@@ -60,7 +61,7 @@ namespace Flowframes.Media
|
||||
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 (I.currentMediaFile.IsVfr)
|
||||
if (I.currentMediaFile.IsVfr && I.currentMediaFile.OutputTimestamps.Any())
|
||||
{
|
||||
Export.MuxTimestamps(tempPath);
|
||||
}
|
||||
|
||||
@@ -70,12 +70,13 @@ namespace Flowframes.Media
|
||||
{
|
||||
if (Interpolate.currentMediaFile.IsVfr && !Interpolate.currentSettings.dedupe)
|
||||
{
|
||||
Logger.Log($"Ignoring {resampleFps.Float} FPS limit as this is currently unsupported for variable framerate videos.");
|
||||
// Logger.Log($"Ignoring {resampleFps.Float} FPS limit as this is currently unsupported for variable framerate videos.");
|
||||
}
|
||||
else
|
||||
{
|
||||
filters.Add($"fps={resampleFps}");
|
||||
}
|
||||
// filters.Add($"fps={resampleFps}");
|
||||
}
|
||||
|
||||
if (Config.GetBool(Config.Key.keepColorSpace) && extraData.HasAllColorValues())
|
||||
|
||||
@@ -163,7 +163,7 @@ namespace Flowframes.Media
|
||||
}
|
||||
}
|
||||
|
||||
static bool AreImagesCompatible(string inpath, int maxHeight)
|
||||
private static bool AreImagesCompatible(string inpath, int maxHeight)
|
||||
{
|
||||
NmkdStopwatch sw = new NmkdStopwatch();
|
||||
string[] validExtensions = Filetypes.imagesInterpCompat; // = new string[] { ".jpg", ".jpeg", ".png" };
|
||||
@@ -204,17 +204,6 @@ namespace Flowframes.Media
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger.Log($"---> TODO: Modulo check for images? Or just check later and apply padding before interpolation if needed...", hidden: true);
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Flowframes.Media
|
||||
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, MediaFile mediaFile = null)
|
||||
public static async Task<List<Stream>> GetStreams(string path, bool progressBar, int streamCount, Fraction defaultFps, bool countFrames, MediaFile mediaFile = null)
|
||||
{
|
||||
List<Stream> streamList = new List<Stream>();
|
||||
|
||||
|
||||
104
CodeLegacy/Media/TimestampUtils.cs
Normal file
104
CodeLegacy/Media/TimestampUtils.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using Flowframes.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Flowframes.Media
|
||||
{
|
||||
public class TimestampUtils
|
||||
{
|
||||
public static void CalcTimestamps(MediaFile media, InterpSettings settings)
|
||||
{
|
||||
float avgDuration = media.InputTimestampDurations.Average();
|
||||
media.InputTimestamps.Add(media.InputTimestamps.Last() + avgDuration); // Add extra frame using avg. duration, needed for duration matching or looping
|
||||
media.OutputTimestamps = StretchTimestamps(media.InputTimestamps, settings.interpFactor);
|
||||
|
||||
if (settings.FpsResampling)
|
||||
{
|
||||
List<float[]> timestampsWithInputIdx = ConvertToConstantFrameRate(media.OutputTimestamps, settings.outFpsResampled.Float);
|
||||
media.OutputTimestamps = timestampsWithInputIdx.Select(x => x[0]).ToList();
|
||||
media.OutputFrameIndexes = timestampsWithInputIdx.Select(x => (int)x[1]).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public static string WriteTsFile(List<float> timestamps, string path)
|
||||
{
|
||||
var lines = new List<string>() { "# timecode format v2" };
|
||||
|
||||
foreach (var ts in timestamps)
|
||||
{
|
||||
lines.Add((ts * 1000f).ToString("0.000000"));
|
||||
}
|
||||
|
||||
File.WriteAllLines(path, lines);
|
||||
return path;
|
||||
}
|
||||
|
||||
public static List<float> StretchTimestamps(List<float> timestamps, double factor)
|
||||
{
|
||||
int originalCount = timestamps.Count;
|
||||
int newCount = (int)Math.Round(originalCount * factor);
|
||||
List<float> resampledTimestamps = new List<float>();
|
||||
|
||||
for (int i = 0; i < newCount; i++)
|
||||
{
|
||||
double x = i / factor;
|
||||
|
||||
if (x >= originalCount - 1)
|
||||
{
|
||||
resampledTimestamps.Add(timestamps[originalCount - 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
int index = (int)Math.Floor(x);
|
||||
double fraction = x - index;
|
||||
float startTime = timestamps[index];
|
||||
float endTime = timestamps[index + 1];
|
||||
|
||||
float interpolatedTime = (float)(startTime + (endTime - startTime) * fraction);
|
||||
resampledTimestamps.Add(interpolatedTime);
|
||||
}
|
||||
}
|
||||
|
||||
return resampledTimestamps;
|
||||
}
|
||||
|
||||
public static List<float[]> ConvertToConstantFrameRate(List<float> inputTimestamps, float targetFrameRate, Enums.Round roundMethod = Enums.Round.Near)
|
||||
{
|
||||
List<float[]> outputTimestamps = new List<float[]>(); // Resulting list of timestamps
|
||||
float targetFrameInterval = 1.0f / targetFrameRate; // Interval for the target frame rate
|
||||
float currentTargetTime = 0.0f; // Start time for the target frame rate
|
||||
int index = 0; // Index for iterating through the input timestamps
|
||||
|
||||
Console.WriteLine("--> Converting to constant frame rate...");
|
||||
|
||||
while (currentTargetTime <= inputTimestamps.Last()) // Use ^1 to get the last element
|
||||
{
|
||||
switch (roundMethod)
|
||||
{
|
||||
case Enums.Round.Near: // Find the closest timestamp to the current target time
|
||||
while (index < inputTimestamps.Count - 1 && Math.Abs(inputTimestamps[index + 1] - currentTargetTime) < Math.Abs(inputTimestamps[index] - currentTargetTime)) index++;
|
||||
break;
|
||||
case Enums.Round.Down: // Find the closest timestamp that is <= the current target time
|
||||
while (index < inputTimestamps.Count - 1 && inputTimestamps[index + 1] <= currentTargetTime) index++;
|
||||
break;
|
||||
case Enums.Round.Up: // Find the closest timestamp that is >= the current target time
|
||||
while (index < inputTimestamps.Count - 1 && inputTimestamps[index] < currentTargetTime) index++;
|
||||
break;
|
||||
}
|
||||
|
||||
// Debug message to show which frame index was picked
|
||||
Console.WriteLine($"--> Frame {(outputTimestamps.Count + 1).ToString().PadLeft(3)} | Target Time: {(currentTargetTime * 1000f):F5} | Picked Input Index: {index} | Input TS: {(inputTimestamps[index] * 1000f):F3}");
|
||||
|
||||
// Add the closest timestamp to the output list, along with the index of the input timestamp
|
||||
outputTimestamps.Add(new float[] { inputTimestamps[index], index });
|
||||
|
||||
// Move to the next frame time in the target frame rate
|
||||
currentTargetTime += targetFrameInterval;
|
||||
}
|
||||
|
||||
return outputTimestamps;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,6 +172,15 @@ namespace Flowframes.Os
|
||||
l.Add(Debugger.IsAttached ? $"clip = core.text.FrameNum(clip, alignment=9, scale={txtScale}) # Output frame counter" : "");
|
||||
l.Add(Debugger.IsAttached ? $"clip = core.text.Text(clip, f\"Frames: {{srcFrames}}/{{preInterpFrames}} -> {{len(clip)}} [{factor.GetString()}x]\", alignment=8, scale={txtScale})" : "");
|
||||
l.Add(Debugger.IsAttached ? $"clip = core.text.Text(clip, f\"targetMatchDuration: {targetFrameCountMatchDuration} - targetTrue: {targetFrameCountTrue} - endDupeCount: {endDupeCount}\", alignment=2, scale={txtScale})" : "");
|
||||
|
||||
if(Interpolate.currentMediaFile.IsVfr && Interpolate.currentMediaFile.OutputFrameIndexes != null && Interpolate.currentMediaFile.OutputFrameIndexes.Count > 0 && s.InterpSettings.outFpsResampled.Float > 0.1f)
|
||||
{
|
||||
l.Add($"# Frames picked to resample VFR video to {s.InterpSettings.outFpsResampled.Float} FPS");
|
||||
l.Add($"frameIndexes = [{string.Join(", ", Interpolate.currentMediaFile.OutputFrameIndexes)}]");
|
||||
l.Add($"clip = core.std.Splice([clip[i] for i in frameIndexes if i < len(clip)])");
|
||||
l.Add($"clip = core.std.AssumeFPS(clip, fpsnum={s.InterpSettings.outFpsResampled.Numerator}, fpsden={s.InterpSettings.outFpsResampled.Denominator})");
|
||||
}
|
||||
|
||||
l.Add($"clip.set_output()"); // Set output
|
||||
l.Add("");
|
||||
|
||||
|
||||
@@ -61,5 +61,10 @@ namespace Flowframes.Ui
|
||||
control.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Invoke(this Control control, MethodInvoker action)
|
||||
{
|
||||
control.Invoke(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ namespace Flowframes.Ui
|
||||
if (I.canceled) return;
|
||||
interpolatedInputFramesCount = ((frames / I.currentSettings.interpFactor).RoundToInt() - 1);
|
||||
//ResumeUtils.Save();
|
||||
target = (target / Interpolate.InterpProgressMultiplier).RoundToInt();
|
||||
target = (target / I.InterpProgressMultiplier).RoundToInt();
|
||||
frames = frames.Clamp(0, target);
|
||||
|
||||
if (_framesAtTime == null)
|
||||
|
||||
Reference in New Issue
Block a user