Better CFR-based encoding, fixed dupe frame when using loop

This commit is contained in:
N00MKRAD
2021-01-02 16:20:21 +01:00
parent f258cdde95
commit c38a615a15
7 changed files with 165 additions and 13 deletions

View File

@@ -92,17 +92,17 @@ namespace Flowframes
DeleteSource(inputFile);
}
public static async Task FramesToVideoVfr(string framesFile, string outPath, Interpolate.OutMode outMode, float fps, AvProcess.LogMode logMode = AvProcess.LogMode.OnlyLastLine, bool isChunk = false)
public static async Task FramesToVideoConcat(string framesFile, string outPath, Interpolate.OutMode outMode, float fps, AvProcess.LogMode logMode = AvProcess.LogMode.OnlyLastLine, bool isChunk = false)
{
if (logMode != AvProcess.LogMode.Hidden)
Logger.Log($"Encoding video...");
string encArgs = Utils.GetEncArgs(Utils.GetCodec(outMode)) + " -pix_fmt yuv420p ";
if (!isChunk) encArgs += $"-movflags +faststart";
string vfrFilename = Path.GetFileName(framesFile);
string vsync = (Interpolate.current.interpFactor == 2) ? "-vsync 1" : "-vsync 2";
//string vsync = (Interpolate.current.interpFactor == 2) ? "-vsync 1" : "-vsync 2";
string rate = fps.ToString().Replace(",", ".");
string extraArgs = Config.Get("ffEncArgs");
string args = $"{vsync} -f concat -i {vfrFilename} -r {rate} {encArgs} {extraArgs} -threads {Config.GetInt("ffEncThreads")} {outPath.Wrap()}";
string args = $"-loglevel error -vsync 0 -f concat -r {rate} -i {vfrFilename} {encArgs} {extraArgs} -threads {Config.GetInt("ffEncThreads")} {outPath.Wrap()}";
//string args = $"-vsync 0 -f concat -i {vfrFilename} {encArgs} {extraArgs} -threads {Config.GetInt("ffEncThreads")} {outPath.Wrap()}";
await AvProcess.RunFfmpeg(args, framesFile.GetParentDir(), logMode);
}
@@ -183,7 +183,7 @@ namespace Flowframes
public static async Task Encode(string inputFile, string vcodec, string acodec, int crf, int audioKbps = 0, bool delSrc = false)
{
string outPath = Path.ChangeExtension(inputFile, null) + "-convert.mp4";
string args = $" -i {inputFile.Wrap()} -c:v {vcodec} -crf {crf} -pix_fmt yuv420p -c:a {acodec} -b:a {audioKbps}k {outPath.Wrap()}";
string args = $" -i {inputFile.Wrap()} -c:v {vcodec} -crf {crf} -pix_fmt yuv420p -c:a {acodec} -b:a {audioKbps}k -vf {divisionFilter} {outPath.Wrap()}";
if (string.IsNullOrWhiteSpace(acodec))
args = args.Replace("-c:a", "-an");
if (audioKbps < 0)

View File

@@ -155,5 +155,15 @@ namespace Flowframes
return str.Remove(place, stringToReplace.Length).Insert(place, replaceWith);
}
public static string[] SplitBy (this string str, string splitBy)
{
return str.Split(new string[] { splitBy }, StringSplitOptions.None);
}
public static string RemoveComments (this string str)
{
return str.Split('#')[0].SplitBy("//")[0];
}
}
}

View File

@@ -81,6 +81,9 @@ namespace Flowframes.Magick
bool hasReachedEnd = false;
string infoFile = Path.Combine(path.GetParentDir(), $"dupes.ini");
string fileContent = "";
for (int i = 0; i < framePaths.Length; i++) // Loop through frames
{
if (hasReachedEnd)
@@ -133,16 +136,17 @@ namespace Flowframes.Magick
}
else
{
fileContent += $"{Path.GetFileNameWithoutExtension(framePaths[i].Name)}:{currentDupeCount}\n";
statsFramesKept++;
currentOutFrame++;
currentDupeCount = 0;
break;
}
if (sw.ElapsedMilliseconds >= 1000 || (i+1) == framePaths.Length)
if (sw.ElapsedMilliseconds >= 1000 || (i+1) == framePaths.Length) // Print every 1s (or when done)
{
sw.Restart();
Logger.Log($"[FrameDedup] Difference from {Path.GetFileName(frame1)} to {Path.GetFileName(frame2)}: {diff.ToString("0.00")}% - {delStr}. Total: {framePaths.Length - statsFramesDeleted} kept / {statsFramesDeleted} deleted.", false, true);
Logger.Log($"[FrameDedup] Difference from {Path.GetFileName(frame1)} to {Path.GetFileName(frame2)}: {diff.ToString("0.00")}% - {delStr}.", false, true);
Program.mainForm.SetProgress((int)Math.Round(((float)i / framePaths.Length) * 100f));
if (imageCache.Count > bufferSize || (imageCache.Count > 50 && OSUtils.GetFreeRamMb() < 2500))
ClearCache();
@@ -169,6 +173,8 @@ namespace Flowframes.Magick
}
}
File.WriteAllText(infoFile, fileContent);
foreach (string frame in framesToDelete)
IOUtils.TryDeleteIfExists(frame);
@@ -176,10 +182,15 @@ namespace Flowframes.Magick
if (Interpolate.canceled) return;
int framesLeft = IOUtils.GetAmountOfFiles(path, false, $"*.png");
int framesDeleted = framePaths.Length - framesLeft;
float percentDeleted = ((float)framesDeleted / framePaths.Length) * 100f;
string keptPercent = $"{(100f - percentDeleted).ToString("0.0")}%";
if (skipped)
Logger.Log($"[FrameDedup] First {skipAfterNoDupesFrames} frames did not have any duplicates - Skipping the rest!", false, true);
else
Logger.Log($"[FrameDedup]{testStr} Done. Kept {statsFramesKept} frames, deleted {statsFramesDeleted} frames.", false, true);
Logger.Log($"[FrameDedup]{testStr} Done. Kept {framesLeft} ({keptPercent}) frames, deleted {framesDeleted} frames.", false, true);
if (statsFramesKept <= 0)
Interpolate.Cancel("No frames were left after de-duplication!\n\nTry decreasing the de-duplication threshold.");

View File

@@ -112,7 +112,7 @@ namespace Flowframes.Main
bool h265 = Config.GetInt("mp4Enc") == 1;
int crf = h265 ? Config.GetInt("h265Crf") : Config.GetInt("h264Crf");
await FFmpegCommands.FramesToVideoVfr(vfrFile, outPath, mode, fps);
await FFmpegCommands.FramesToVideoConcat(vfrFile, outPath, mode, fps);
await MergeAudio(i.current.inPath, outPath);
if (changeFps > 0)
@@ -185,7 +185,7 @@ namespace Flowframes.Main
string vfrFile = Path.Combine(i.current.tempFolder, $"vfr-chunk-{firstFrameNum}-{firstFrameNum + framesAmount}.ini");
File.WriteAllLines(vfrFile, IOUtils.ReadLines(vfrFileOriginal).Skip(firstFrameNum * 2).Take(framesAmount * 2));
await FFmpegCommands.FramesToVideoVfr(vfrFile, outPath, mode, i.current.outFps, AvProcess.LogMode.Hidden, true);
await FFmpegCommands.FramesToVideoConcat(vfrFile, outPath, mode, i.current.outFps, AvProcess.LogMode.Hidden, true);
}
static async Task Loop(string outPath, int looptimes)

View File

@@ -14,14 +14,25 @@ namespace Flowframes.Main
{
class FrameTiming
{
public enum Mode { CFR, VFR }
public static int timebase = 10000;
public static async Task CreateTimecodeFiles(string framesPath, bool loopEnabled, int times, bool noTimestamps)
public static async Task CreateTimecodeFiles(string framesPath, Mode mode, bool loopEnabled, int times, bool noTimestamps)
{
Logger.Log("Generating timecodes...");
try
{
if (mode == Mode.VFR)
await CreateTimecodeFile(framesPath, loopEnabled, times, false, noTimestamps);
if (mode == Mode.CFR)
await CreateEncFile(framesPath, loopEnabled, times, false);
Logger.Log($"Generating timecodes... Done.", false, true);
}
catch (Exception e)
{
Logger.Log($"Error generating timecodes: {e.Message}");
}
}
public static async Task CreateTimecodeFile(string framesPath, bool loopEnabled, int interpFactor, bool notFirstRun, bool noTimestamps)
{
@@ -138,5 +149,124 @@ namespace Flowframes.Main
File.Copy(frameFiles.First().FullName, loopFrameTargetPath);
}
}
static Dictionary<string, int> dupesDict = new Dictionary<string, int>();
static void LoadDupesFile (string path)
{
if (!File.Exists(path)) return;
dupesDict.Clear();
string[] dupesFileLines = IOUtils.ReadLines(path);
foreach(string line in dupesFileLines)
{
string[] values = line.Split(':');
dupesDict.Add(values[0], values[1].GetInt());
}
}
public static async Task CreateEncFile (string framesPath, bool loopEnabled, int interpFactor, bool notFirstRun)
{
if (Interpolate.canceled) return;
Logger.Log($"Generating timecodes for {interpFactor}x...", false, true);
bool loop = Config.GetBool("enableLoop");
bool sceneDetection = true;
string ext = InterpolateUtils.GetOutExt();
FileInfo[] frameFiles = new DirectoryInfo(framesPath).GetFiles($"*.png");
string vfrFile = Path.Combine(framesPath.GetParentDir(), $"vfr-{interpFactor}x.ini");
string fileContent = "";
string dupesFile = Path.Combine(framesPath.GetParentDir(), $"dupes.ini");
LoadDupesFile(dupesFile);
string scnFramesPath = Path.Combine(framesPath.GetParentDir(), Paths.scenesDir);
string interpPath = Paths.interpDir;
List<string> sceneFrames = new List<string>();
if (Directory.Exists(scnFramesPath))
sceneFrames = Directory.GetFiles(scnFramesPath).Select(file => Path.GetFileNameWithoutExtension(file)).ToList();
int totalFileCount = 1;
for (int i = 0; i < (frameFiles.Length - 1); i++)
{
if (Interpolate.canceled) return;
int interpFramesAmount = interpFactor;
string inputFilenameNoExt = Path.GetFileNameWithoutExtension(frameFiles[i].Name);
int dupesAmount = dupesDict.ContainsKey(inputFilenameNoExt) ? dupesDict[inputFilenameNoExt] : 0;
//Logger.Log($"{Path.GetFileNameWithoutExtension(frameFiles[i].Name)} has {dupesAmount} dupes", true);
bool discardThisFrame = (sceneDetection && (i + 2) < frameFiles.Length && sceneFrames.Contains(Path.GetFileNameWithoutExtension(frameFiles[i + 1].Name))); // i+2 is in scene detection folder, means i+1 is ugly interp frame
// TODO: Check if this is needed
// If loop is enabled, account for the extra frame added to the end for loop continuity
if (loopEnabled && i == (frameFiles.Length - 2))
interpFramesAmount = interpFramesAmount * 2;
//Logger.Log($"Writing out frames for in frame {i} which has {dupesAmount} dupes", true);
// Generate frames file lines
for (int frm = 0; frm < interpFramesAmount; frm++)
{
//Logger.Log($"Writing out frame {frm+1}/{interpFramesAmount}", true);
if (discardThisFrame && totalFileCount > 1) // If frame is scene cut frame
{
int lastNum = totalFileCount;
//Logger.Log($"Writing frame {totalFileCount} [Discarding Next]", true);
fileContent += $"file '{interpPath}/{totalFileCount.ToString().PadLeft(Padding.interpFrames, '0')}.{ext}'\n";
totalFileCount++;
//Logger.Log("Discarding interp frames with out num " + totalFileCount);
for (int dupeCount = 1; dupeCount < interpFramesAmount; dupeCount++)
{
//Logger.Log($"Writing frame {totalFileCount} which is actually repeated frame {lastNum}");
fileContent += $"file '{interpPath}/{lastNum.ToString().PadLeft(Padding.interpFrames, '0')}.{ext}'\n";
totalFileCount++;
}
frm = interpFramesAmount;
}
else
{
for(int writtenDupes = -1; writtenDupes < dupesAmount; writtenDupes++) // Write duplicates
{
//Logger.Log($"Writing frame {totalFileCount}", true, false);
fileContent += $"file '{interpPath}/{totalFileCount.ToString().PadLeft(Padding.interpFrames, '0')}.{ext}'\n";
}
totalFileCount++;
}
}
if ((i + 1) % 100 == 0)
await Task.Delay(1);
}
// Use average frame duration for last frame - TODO: Use real duration??
//string durationStrLast = ((totalDuration / (totalFileCount - 1)) / timebase).ToString("0.0000000", CultureInfo.InvariantCulture);
fileContent += $"file '{interpPath}/{totalFileCount.ToString().PadLeft(Padding.interpFrames, '0')}.{ext}'\n";
totalFileCount++;
string finalFileContent = fileContent.Trim();
if(loop)
finalFileContent = finalFileContent.Remove(finalFileContent.LastIndexOf("\n"));
File.WriteAllText(vfrFile, finalFileContent);
if (notFirstRun) return; // Skip all steps that only need to be done once
if (loop)
{
int lastFileNumber = frameFiles.Last().Name.GetInt() + 1;
string loopFrameTargetPath = Path.Combine(frameFiles.First().FullName.GetParentDir(), lastFileNumber.ToString().PadLeft(Padding.inputFrames, '0') + $".png");
if (File.Exists(loopFrameTargetPath))
{
Logger.Log($"Won't copy loop frame - {Path.GetFileName(loopFrameTargetPath)} already exists.", true);
return;
}
File.Copy(frameFiles.First().FullName, loopFrameTargetPath);
Logger.Log($"Copied loop frame to {loopFrameTargetPath}.", true);
}
}
}
}

View File

@@ -125,7 +125,7 @@ namespace Flowframes
if (canceled) return;
bool useTimestamps = Config.GetInt("timingMode") == 1; // TODO: Auto-Disable timestamps if input frames are sequential, not timestamped
await FrameTiming.CreateTimecodeFiles(current.framesFolder, Config.GetBool("enableLoop"), current.interpFactor, !useTimestamps);
await FrameTiming.CreateTimecodeFiles(current.framesFolder, FrameTiming.Mode.CFR, Config.GetBool("enableLoop"), current.interpFactor, !useTimestamps);
if (canceled) return;

View File

@@ -34,6 +34,7 @@ namespace Flowframes.Main
public static int currentFactor;
public static async void GetProgressByFrameAmount(string outdir, int target)
{
Logger.Log($"Starting GetProgressByFrameAmount() loop for outdir '{outdir}', target is {target} frames", true);
bool firstProgUpd = true;
Program.mainForm.SetProgress(0);
targetFrames = target;