Port to .NET 5

This commit is contained in:
Dankrushen
2021-01-26 02:37:16 -05:00
parent 0a236361e0
commit 72dcf90b68
95 changed files with 24769 additions and 1 deletions

178
Code5/Main/AutoEncode.cs Normal file
View File

@@ -0,0 +1,178 @@
using Flowframes.AudioVideo;
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.MiscUtils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Main
{
class AutoEncode
{
static string interpFramesFolder;
static string videoChunksFolder;
public static int chunkSize = 125; // Encode every n frames
public static int safetyBufferFrames = 90; // Ignore latest n frames to avoid using images that haven't been fully encoded yet
public static string[] interpFramesLines;
public static List<int> encodedFrameLines = new List<int>();
public static List<int> unencodedFrameLines = new List<int>();
public static bool busy;
public static bool paused;
public static void UpdateChunkAndBufferSizes ()
{
chunkSize = GetChunkSize(IOUtils.GetAmountOfFiles(Interpolate.current.framesFolder, false, "*.png") * Interpolate.current.interpFactor);
bool isNcnn = Interpolate.current.ai.aiName.ToUpper().Contains("NCNN");
safetyBufferFrames = isNcnn ? Config.GetInt("autoEncSafeBufferNcnn", 90) : Config.GetInt("autoEncSafeBufferCuda", 30); // Use bigger safety buffer for NCNN
}
public static async Task MainLoop(string interpFramesPath)
{
try
{
UpdateChunkAndBufferSizes();
interpFramesFolder = interpFramesPath;
videoChunksFolder = Path.Combine(interpFramesPath.GetParentDir(), Paths.chunksDir);
if (Interpolate.currentlyUsingAutoEnc)
Directory.CreateDirectory(videoChunksFolder);
encodedFrameLines.Clear();
unencodedFrameLines.Clear();
Logger.Log($"[AutoEnc] Starting AutoEncode MainLoop - Chunk Size: {chunkSize} Frames - Safety Buffer: {safetyBufferFrames} Frames", true);
int videoIndex = 1;
string encFile = Path.Combine(interpFramesPath.GetParentDir(), $"vfr-{Interpolate.current.interpFactor}x.ini");
interpFramesLines = IOUtils.ReadLines(encFile).Select(x => x.Split('/').Last().Remove("'").Split('#').First()).ToArray(); // Array with frame filenames
while (!Interpolate.canceled && GetInterpFramesAmount() < 2)
await Task.Delay(2000);
int lastEncodedFrameNum = 0;
while (HasWorkToDo()) // Loop while proc is running and not all frames have been encoded
{
if (Interpolate.canceled) return;
if (paused)
{
//Logger.Log("autoenc paused");
await Task.Delay(200);
continue;
}
unencodedFrameLines.Clear();
for (int vfrLine = lastEncodedFrameNum; vfrLine < interpFramesLines.Length; vfrLine++)
unencodedFrameLines.Add(vfrLine);
bool aiRunning = !AiProcess.currentAiProcess.HasExited;
if (unencodedFrameLines.Count > 0 && (unencodedFrameLines.Count >= (chunkSize + safetyBufferFrames) || !aiRunning)) // Encode every n frames, or after process has exited
{
List<int> frameLinesToEncode = aiRunning ? unencodedFrameLines.Take(chunkSize).ToList() : unencodedFrameLines; // Take all remaining frames if process is done
string lastOfChunk = Path.Combine(interpFramesPath, interpFramesLines[frameLinesToEncode.Last()]);
if (!File.Exists(lastOfChunk))
{
await Task.Delay(500);
continue;
}
busy = true;
string outpath = Path.Combine(videoChunksFolder, "chunks", $"{videoIndex.ToString().PadLeft(4, '0')}{FFmpegUtils.GetExt(Interpolate.current.outMode)}");
//int firstFrameNum = frameLinesToEncode[0];
int firstLineNum = frameLinesToEncode.First();
int lastLineNum = frameLinesToEncode.Last();
Logger.Log($"[AutoEnc] Encoding Chunk #{videoIndex} to '{outpath}' using line {firstLineNum} ({Path.GetFileName(interpFramesLines[firstLineNum])}) through {lastLineNum} ({Path.GetFileName(Path.GetFileName(interpFramesLines[frameLinesToEncode.Last()]))})", true, false, "ffmpeg");
await CreateVideo.EncodeChunk(outpath, Interpolate.current.outMode, firstLineNum, frameLinesToEncode.Count);
if (Interpolate.canceled) return;
if (Config.GetInt("autoEncMode") == 2)
Task.Run(() => DeleteOldFramesAsync(interpFramesPath, frameLinesToEncode));
if (Interpolate.canceled) return;
encodedFrameLines.AddRange(frameLinesToEncode);
Logger.Log("Done Encoding Chunk #" + videoIndex, true, false, "ffmpeg");
lastEncodedFrameNum = (frameLinesToEncode.Last() + 1 );
videoIndex++;
busy = false;
}
await Task.Delay(50);
}
if (Interpolate.canceled) return;
IOUtils.ReverseRenaming(AiProcess.filenameMap, true); // Get timestamps back
await CreateVideo.ChunksToVideos(Interpolate.current.tempFolder, videoChunksFolder, Interpolate.current.outFilename);
}
catch (Exception e)
{
Logger.Log($"AutoEnc Error: {e.Message}. Stack Trace:\n{e.StackTrace}");
Interpolate.Cancel("Auto-Encode encountered an error.");
}
}
static async Task DeleteOldFramesAsync (string interpFramesPath, List<int> frameLinesToEncode)
{
Logger.Log("[AutoEnc] Starting DeleteOldFramesAsync.", true, false, "ffmpeg");
Stopwatch sw = new Stopwatch();
sw.Restart();
foreach (int frame in frameLinesToEncode)
{
bool delete = !FrameIsStillNeeded(interpFramesLines[frame], frame);
if (delete) // Make sure frames are no longer needed (e.g. for dupes) before deleting!
{
string framePath = Path.Combine(interpFramesPath, interpFramesLines[frame]);
File.WriteAllText(framePath, "THIS IS A DUMMY FILE - DO NOT DELETE ME"); // Overwrite to save space without breaking progress counter
await Task.Delay(1);
}
}
Logger.Log("[AutoEnc] DeleteOldFramesAsync finished in " + FormatUtils.TimeSw(sw), true, false, "ffmpeg");
}
static bool FrameIsStillNeeded (string frameName, int frameIndex)
{
if ((frameIndex + 1) < interpFramesLines.Length && interpFramesLines[frameIndex+1].Contains(frameName))
return true;
return false;
}
public static bool HasWorkToDo ()
{
if (Interpolate.canceled || interpFramesFolder == null) return false;
// Logger.Log($"HasWorkToDo - Process Running: {(AiProcess.currentAiProcess != null && !AiProcess.currentAiProcess.HasExited)} - encodedFrameLines.Count: {encodedFrameLines.Count} - interpFramesLines.Length: {interpFramesLines.Length}");
return ((AiProcess.currentAiProcess != null && !AiProcess.currentAiProcess.HasExited) || encodedFrameLines.Count < interpFramesLines.Length);
}
static int GetChunkSize(int targetFramesAmount)
{
if (targetFramesAmount > 50000) return 2400;
if (targetFramesAmount > 20000) return 1200;
if (targetFramesAmount > 5000) return 600;
if (targetFramesAmount > 1000) return 300;
return 150;
}
static int GetInterpFramesAmount()
{
return IOUtils.GetAmountOfFiles(interpFramesFolder, false, $"*.{InterpolateUtils.GetOutExt()}");
}
}
}

View File

@@ -0,0 +1,110 @@
using Flowframes.Forms;
using Flowframes.IO;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Main
{
class BatchProcessing
{
public static bool stopped = false;
public static BatchForm currentBatchForm;
public static bool busy = false;
public static async void Start()
{
if (Config.GetBool("clearLogOnInput"))
Logger.ClearLogBox();
stopped = false;
Program.mainForm.SetTab("preview");
int initTaskCount = Program.batchQueue.Count;
for (int i = 0; i < initTaskCount; i++)
{
if (!stopped && Program.batchQueue.Count > 0)
{
Logger.Log($"[Queue] Running queue task {i + 1}/{initTaskCount}, {Program.batchQueue.Count} tasks left.");
await RunEntry(Program.batchQueue.Peek());
if (currentBatchForm != null)
currentBatchForm.RefreshGui();
}
await Task.Delay(1000);
}
Logger.Log("[Queue] Finished queue processing.");
SetBusy(false);
Program.mainForm.SetTab("interpolation");
}
public static void Stop()
{
stopped = true;
}
static async Task RunEntry(InterpSettings entry)
{
if (!EntryIsValid(entry))
{
Logger.Log("[Queue] Skipping entry because it's invalid.");
Program.batchQueue.Dequeue();
return;
}
string fname = Path.GetFileName(entry.inPath);
if (IOUtils.IsPathDirectory(entry.inPath)) fname = Path.GetDirectoryName(entry.inPath);
Logger.Log($"[Queue] Processing {fname} ({entry.interpFactor}x {entry.ai.aiNameShort}).");
SetBusy(true);
Program.mainForm.LoadBatchEntry(entry); // Load entry into GUI
Interpolate.current = entry;
Program.mainForm.runBtn_Click(null, null);
await Task.Delay(2000);
while (Program.busy)
await Task.Delay(1000);
SetBusy(false);
Program.batchQueue.Dequeue();
Logger.Log($"[Queue] Done processing {fname} ({entry.interpFactor}x {entry.ai.aiNameShort}).");
}
static void SetBusy(bool state)
{
busy = state;
if (currentBatchForm != null)
currentBatchForm.SetWorking(state);
Program.mainForm.SetWorking(state);
Program.mainForm.GetMainTabControl().Enabled = !state; // Lock GUI
}
static bool EntryIsValid(InterpSettings entry)
{
if (entry.inPath == null || (IOUtils.IsPathDirectory(entry.inPath) && !Directory.Exists(entry.inPath)) || (!IOUtils.IsPathDirectory(entry.inPath) && !File.Exists(entry.inPath)))
{
Logger.Log("[Queue] Can't process queue entry: Input path is invalid.");
return false;
}
if (entry.outPath == null || !Directory.Exists(entry.outPath))
{
Logger.Log("[Queue] Can't process queue entry: Output path is invalid.");
return false;
}
if (!PkgUtils.IsAiAvailable(entry.ai))
{
Logger.Log("[Queue] Can't process queue entry: Selected AI is not available.");
return false;
}
return true;
}
}
}

233
Code5/Main/CreateVideo.cs Normal file
View File

@@ -0,0 +1,233 @@
using Flowframes;
using Flowframes.IO;
using Flowframes.Magick;
using Flowframes.Main;
using Flowframes.OS;
using Flowframes.UI;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using Padding = Flowframes.Data.Padding;
using i = Flowframes.Interpolate;
using System.Diagnostics;
using Flowframes.AudioVideo;
namespace Flowframes.Main
{
class CreateVideo
{
static string currentOutFile; // Keeps track of the out file, in case it gets renamed (FPS limiting, looping, etc) before finishing export
public static async Task Export(string path, string outPath, i.OutMode mode)
{
if (!mode.ToString().ToLower().Contains("vid")) // Copy interp frames out of temp folder and skip video export for image seq export
{
try
{
await CopyOutputFrames(path, Path.GetFileNameWithoutExtension(outPath));
}
catch(Exception e)
{
Logger.Log("Failed to move interp frames folder: " + e.Message);
}
return;
}
if (IOUtils.GetAmountOfFiles(path, false, $"*.{InterpolateUtils.GetOutExt()}") <= 1)
{
i.Cancel("Output folder does not contain frames - An error must have occured during interpolation!", AiProcess.hasShownError);
return;
}
await Task.Delay(10);
Program.mainForm.SetStatus("Creating output video from frames...");
try
{
float maxFps = Config.GetFloat("maxFps");
bool fpsLimit = maxFps != 0 && i.current.outFps > maxFps;
bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt("maxFpsMode") == 0;
if(!dontEncodeFullFpsVid)
await Encode(mode, path, outPath, i.current.outFps);
if (fpsLimit)
await Encode(mode, path, outPath.FilenameSuffix($"-{maxFps.ToStringDot("0.00")}fps"), i.current.outFps, maxFps);
}
catch (Exception e)
{
Logger.Log("FramesToVideo Error: " + e.Message, false);
MessageBox.Show("An error occured while trying to convert the interpolated frames to a video.\nCheck the log for details.");
}
}
static async Task CopyOutputFrames (string framesPath, string folderName)
{
Program.mainForm.SetStatus("Copying output frames...");
string copyPath = Path.Combine(i.current.outPath, folderName);
Logger.Log($"Moving output frames to '{copyPath}'");
IOUtils.TryDeleteIfExists(copyPath);
IOUtils.CreateDir(copyPath);
Stopwatch sw = new Stopwatch();
sw.Restart();
string vfrFile = Path.Combine(framesPath.GetParentDir(), $"vfr-{i.current.interpFactor}x.ini");
string[] vfrLines = IOUtils.ReadLines(vfrFile);
for (int idx = 1; idx <= vfrLines.Length; idx++)
{
string line = vfrLines[idx-1];
string inFilename = line.Split('/').Last().Remove("'").RemoveComments();
string framePath = Path.Combine(framesPath, inFilename);
string outFilename = Path.Combine(copyPath, idx.ToString().PadLeft(Padding.interpFrames, '0')) + Path.GetExtension(framePath);
if ((idx < vfrLines.Length) && vfrLines[idx].Contains(inFilename)) // If file is re-used in the next line, copy instead of move
File.Copy(framePath, outFilename);
else
File.Move(framePath, outFilename);
if (sw.ElapsedMilliseconds >= 500 || idx == vfrLines.Length)
{
sw.Restart();
Logger.Log($"Moving output frames to '{Path.GetFileName(copyPath)}' - {idx}/{vfrLines.Length}", false, true);
await Task.Delay(1);
}
}
}
static async Task Encode(i.OutMode mode, string framesPath, string outPath, float fps, float resampleFps = -1)
{
currentOutFile = outPath;
string vfrFile = Path.Combine(framesPath.GetParentDir(), $"vfr-{i.current.interpFactor}x.ini");
if (mode == i.OutMode.VidGif)
{
await FFmpegCommands.FramesToGifConcat(vfrFile, outPath, fps, true, Config.GetInt("gifColors"), resampleFps);
}
else
{
await FFmpegCommands.FramesToVideoConcat(vfrFile, outPath, mode, fps, resampleFps);
await MergeAudio(i.current.inPath, outPath);
int looptimes = GetLoopTimes();
if (looptimes > 0)
await Loop(currentOutFile, looptimes);
}
}
public static async Task ChunksToVideos(string tempFolder, string chunksFolder, string baseOutPath)
{
if (IOUtils.GetAmountOfFiles(chunksFolder, true, $"*{FFmpegUtils.GetExt(i.current.outMode)}") < 1)
{
i.Cancel("No video chunks found - An error must have occured during chunk encoding!", AiProcess.hasShownError);
return;
}
await Task.Delay(10);
Program.mainForm.SetStatus("Merging video chunks...");
try
{
DirectoryInfo chunksDir = new DirectoryInfo(chunksFolder);
foreach(DirectoryInfo dir in chunksDir.GetDirectories())
{
string suffix = dir.Name.Replace("chunks", "");
string tempConcatFile = Path.Combine(tempFolder, $"chunks-concat{suffix}.ini");
string concatFileContent = "";
foreach (string vid in IOUtils.GetFilesSorted(dir.FullName))
concatFileContent += $"file '{Paths.chunksDir}/{dir.Name}/{Path.GetFileName(vid)}'\n";
File.WriteAllText(tempConcatFile, concatFileContent);
Logger.Log($"CreateVideo: Running MergeChunks() for vfrFile '{Path.GetFileName(tempConcatFile)}'", true);
await MergeChunks(tempConcatFile, baseOutPath.FilenameSuffix(suffix));
}
}
catch (Exception e)
{
Logger.Log("ChunksToVideo Error: " + e.Message, false);
MessageBox.Show("An error occured while trying to merge the video chunks.\nCheck the log for details.");
}
}
static async Task MergeChunks(string vfrFile, string outPath)
{
await FFmpegCommands.ConcatVideos(vfrFile, outPath, -1);
await MergeAudio(i.current.inPath, outPath);
int looptimes = GetLoopTimes();
if (looptimes > 0)
await Loop(outPath, looptimes);
}
public static async Task EncodeChunk(string outPath, i.OutMode mode, int firstFrameNum, int framesAmount)
{
string vfrFileOriginal = Path.Combine(i.current.tempFolder, $"vfr-{i.current.interpFactor}x.ini");
string vfrFile = Path.Combine(i.current.tempFolder, $"vfr-chunk-{firstFrameNum}-{firstFrameNum + framesAmount}.ini");
File.WriteAllLines(vfrFile, IOUtils.ReadLines(vfrFileOriginal).Skip(firstFrameNum).Take(framesAmount));
float maxFps = Config.GetFloat("maxFps");
bool fpsLimit = maxFps != 0 && i.current.outFps > maxFps;
bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt("maxFpsMode") == 0;
if(!dontEncodeFullFpsVid)
await FFmpegCommands.FramesToVideoConcat(vfrFile, outPath, mode, i.current.outFps, AvProcess.LogMode.Hidden, true); // Encode
if (fpsLimit)
{
string filename = Path.GetFileName(outPath);
string newParentDir = outPath.GetParentDir() + "-" + maxFps.ToStringDot("0.00") + "fps";
outPath = Path.Combine(newParentDir, filename);
await FFmpegCommands.FramesToVideoConcat(vfrFile, outPath, mode, i.current.outFps, maxFps, AvProcess.LogMode.Hidden, true); // Encode with limited fps
}
}
static async Task Loop(string outPath, int looptimes)
{
Logger.Log($"Looping {looptimes} {(looptimes == 1 ? "time" : "times")} to reach target length of {Config.GetInt("minOutVidLength")}s...");
await FFmpegCommands.LoopVideo(outPath, looptimes, Config.GetInt("loopMode") == 0);
}
static int GetLoopTimes()
{
int times = -1;
int minLength = Config.GetInt("minOutVidLength");
int minFrameCount = (minLength * i.current.outFps).RoundToInt();
int outFrames = i.currentInputFrameCount * i.current.interpFactor;
if (outFrames / i.current.outFps < minLength)
times = (int)Math.Ceiling((double)minFrameCount / (double)outFrames);
times--; // Not counting the 1st play (0 loops)
if (times <= 0) return -1; // Never try to loop 0 times, idk what would happen, probably nothing
return times;
}
public static async Task MergeAudio(string inputPath, string outVideo, int looptimes = -1)
{
if (!Config.GetBool("keepAudio")) return;
try
{
string audioFileBasePath = Path.Combine(i.current.tempFolder, "audio");
if (inputPath != null && IOUtils.IsPathDirectory(inputPath) && !File.Exists(IOUtils.GetAudioFile(audioFileBasePath))) // Try loading out of same folder as input if input is a folder
audioFileBasePath = Path.Combine(i.current.tempFolder.GetParentDir(), "audio");
if (!File.Exists(IOUtils.GetAudioFile(audioFileBasePath)))
await FFmpegCommands.ExtractAudio(inputPath, audioFileBasePath); // Extract from sourceVideo to audioFile unless it already exists
if (!File.Exists(IOUtils.GetAudioFile(audioFileBasePath)) || new FileInfo(IOUtils.GetAudioFile(audioFileBasePath)).Length < 4096)
{
Logger.Log("No compatible audio stream found.", true);
return;
}
await FFmpegCommands.MergeAudioAndSubs(outVideo, IOUtils.GetAudioFile(audioFileBasePath), i.current.tempFolder); // Merge from audioFile into outVideo
}
catch (Exception e)
{
Logger.Log("Failed to copy audio!");
Logger.Log("MergeAudio() Exception: " + e.Message, true);
}
}
}
}

160
Code5/Main/FrameOrder.cs Normal file
View File

@@ -0,0 +1,160 @@
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.UI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Main
{
class FrameOrder
{
public enum Mode { CFR, VFR }
public static int timebase = 10000;
public static async Task CreateFrameOrderFile(string framesPath, bool loopEnabled, int times)
{
Logger.Log("Generating frame order information...");
try
{
await CreateEncFile(framesPath, loopEnabled, times, false);
Logger.Log($"Generating frame order information... Done.", false, true);
}
catch (Exception e)
{
Logger.Log($"Error generating frame order information: {e.Message}");
}
}
static Dictionary<string, int> dupesDict = new Dictionary<string, int>();
static void LoadDupesFile (string path)
{
dupesDict.Clear();
if (!File.Exists(path)) return;
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 frame order information 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();
bool debug = Config.GetBool("frameOrderDebug", false);
int totalFileCount = 0;
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;
if(debug) 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
// 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;
if (debug) 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++)
{
//if (debug) Logger.Log($"Writing out frame {frm+1}/{interpFramesAmount}", true);
if (discardThisFrame) // If frame is scene cut frame
{
//if (debug) Logger.Log($"Writing frame {totalFileCount} [Discarding Next]", true);
totalFileCount++;
int lastNum = totalFileCount;
fileContent = WriteFrameWithDupes(dupesAmount, fileContent, totalFileCount, interpPath, ext, debug, $"[In: {inputFilenameNoExt}] [{((frm == 0) ? " Source " : $"Interp {frm}")}] [DiscardNext]");
//if (debug) Logger.Log("Discarding interp frames with out num " + totalFileCount);
for (int dupeCount = 1; dupeCount < interpFramesAmount; dupeCount++)
{
totalFileCount++;
if (debug) Logger.Log($"Writing frame {totalFileCount} which is actually repeated frame {lastNum}", true);
fileContent = WriteFrameWithDupes(dupesAmount, fileContent, lastNum, interpPath, ext, debug, $"[In: {inputFilenameNoExt}] [DISCARDED]");
}
frm = interpFramesAmount;
}
else
{
totalFileCount++;
fileContent = WriteFrameWithDupes(dupesAmount, fileContent, totalFileCount, interpPath, ext, debug, $"[In: {inputFilenameNoExt}] [{((frm == 0) ? " Source " : $"Interp {frm}")}]");
}
}
if ((i + 1) % 100 == 0)
await Task.Delay(1);
}
// if(debug) Logger.Log("target: " + ((frameFiles.Length * interpFactor) - (interpFactor - 1)), true);
// if(debug) Logger.Log("totalFileCount: " + totalFileCount, true);
totalFileCount++;
fileContent += $"file '{interpPath}/{totalFileCount.ToString().PadLeft(Padding.interpFrames, '0')}.{ext}'\n";
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))
{
if (debug) Logger.Log($"Won't copy loop frame - {Path.GetFileName(loopFrameTargetPath)} already exists.", true);
return;
}
File.Copy(frameFiles.First().FullName, loopFrameTargetPath);
if (debug) Logger.Log($"Copied loop frame to {loopFrameTargetPath}.", true);
}
}
static string WriteFrameWithDupes (int dupesAmount, string fileContent, int frameNum, string interpPath, string ext, bool debug, string note = "")
{
for (int writtenDupes = -1; writtenDupes < dupesAmount; writtenDupes++) // Write duplicates
{
if (debug) Logger.Log($"Writing frame {frameNum} (writtenDupes {writtenDupes})", true, false);
fileContent += $"file '{interpPath}/{frameNum.ToString().PadLeft(Padding.interpFrames, '0')}.{ext}'{(debug ? ($" # Dupe {(writtenDupes+1).ToString("000")} {note}").Replace("Dupe 000", " ") : "" )}\n";
}
return fileContent;
}
}
}

216
Code5/Main/Interpolate.cs Normal file
View File

@@ -0,0 +1,216 @@
using Flowframes;
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.Magick;
using Flowframes.Main;
using Flowframes.MiscUtils;
using Flowframes.OS;
using Flowframes.UI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using Padding = Flowframes.Data.Padding;
using Utils = Flowframes.Main.InterpolateUtils;
namespace Flowframes
{
public class Interpolate
{
public enum OutMode { VidMp4, VidMkv, VidWebm, VidProRes, VidAvi, VidGif, ImgPng }
public static int currentInputFrameCount;
public static bool currentlyUsingAutoEnc;
public static InterpSettings current;
public static bool canceled = false;
static Stopwatch sw = new Stopwatch();
public static async Task Start()
{
canceled = false;
if (!Utils.InputIsValid(current.inPath, current.outPath, current.outFps, current.interpFactor, current.outMode)) return; // General input checks
if (!Utils.CheckAiAvailable(current.ai)) return; // Check if selected AI pkg is installed
if (!Utils.CheckDeleteOldTempFolder()) return; // Try to delete temp folder if an old one exists
if(!Utils.CheckPathValid(current.inPath)) return; // Check if input path/file is valid
Utils.PathAsciiCheck(current.inPath, current.outPath);
currentInputFrameCount = await Utils.GetInputFrameCountAsync(current.inPath);
Program.mainForm.SetStatus("Starting...");
Program.mainForm.SetWorking(true);
await GetFrames();
if (canceled) return;
sw.Restart();
await PostProcessFrames();
if (canceled) return;
await RunAi(current.interpFolder, current.ai);
if (canceled) return;
Program.mainForm.SetProgress(100);
if(!currentlyUsingAutoEnc)
await CreateVideo.Export(current.interpFolder, current.outFilename, current.outMode);
IOUtils.ReverseRenaming(AiProcess.filenameMap, true); // Get timestamps back
Cleanup(current.interpFolder);
Program.mainForm.SetWorking(false);
Logger.Log("Total processing time: " + FormatUtils.Time(sw.Elapsed));
sw.Stop();
Program.mainForm.SetStatus("Done interpolating!");
}
public static async Task GetFrames ()
{
if (!current.inputIsFrames) // Input is video - extract frames first
{
current.alpha = (Config.GetBool("enableAlpha", false) && (Path.GetExtension(current.inPath).ToLower() == ".gif"));
await ExtractFrames(current.inPath, current.framesFolder, current.alpha);
}
else
{
current.alpha = (Config.GetBool("enableAlpha", false) && Path.GetExtension(IOUtils.GetFilesSorted(current.inPath).First()).ToLower() == ".gif");
await FFmpegCommands.ImportImages(current.inPath, current.framesFolder, current.alpha, await Utils.GetOutputResolution(current.inPath, true));
}
}
public static async Task ExtractFrames(string inPath, string outPath, bool alpha, bool allowSceneDetect = true, bool extractAudio = true)
{
if (Config.GetBool("scnDetect"))
{
Program.mainForm.SetStatus("Extracting scenes from video...");
await FFmpegCommands.ExtractSceneChanges(inPath, Path.Combine(current.tempFolder, Paths.scenesDir), current.inFps);
await Task.Delay(10);
}
Program.mainForm.SetStatus("Extracting frames from video...");
bool mpdecimate = Config.GetInt("dedupMode") == 2;
await FFmpegCommands.VideoToFrames(inPath, outPath, alpha, current.inFps, mpdecimate, false, await Utils.GetOutputResolution(inPath, true), false);
if (mpdecimate)
{
int framesLeft = IOUtils.GetAmountOfFiles(outPath, false, $"*.png");
int framesDeleted = currentInputFrameCount - framesLeft;
float percentDeleted = ((float)framesDeleted / currentInputFrameCount) * 100f;
string keptPercent = $"{(100f - percentDeleted).ToString("0.0")}%";
Logger.Log($"[Deduplication] Kept {framesLeft} ({keptPercent}) frames, deleted {framesDeleted} frames.");
}
if(!Config.GetBool("allowConsecutiveSceneChanges", true))
Utils.FixConsecutiveSceneFrames(Path.Combine(current.tempFolder, Paths.scenesDir), current.framesFolder);
if (extractAudio)
{
string audioFile = Path.Combine(current.tempFolder, "audio");
if (audioFile != null && !File.Exists(audioFile))
await FFmpegCommands.ExtractAudio(inPath, audioFile);
}
await FFmpegCommands.ExtractSubtitles(inPath, current.tempFolder, current.outMode);
}
public static async Task PostProcessFrames (bool sbsMode = false)
{
if (canceled) return;
int extractedFrames = IOUtils.GetAmountOfFiles(current.framesFolder, false, "*.png");
if (!Directory.Exists(current.framesFolder) || currentInputFrameCount <= 0 || extractedFrames < 2)
{
if(extractedFrames == 1)
Cancel("Only a single frame was extracted from your input file!\n\nPossibly your input is an image, not a video?");
else
Cancel("Frame extraction failed!\n\nYour input file might be incompatible.");
}
if (Config.GetInt("dedupMode") == 1)
await Dedupe.Run(current.framesFolder);
else
Dedupe.ClearCache();
if(!Config.GetBool("enableLoop"))
await Utils.CopyLastFrame(currentInputFrameCount);
if (Config.GetInt("dedupMode") > 0)
await Dedupe.CreateDupesFile(current.framesFolder, currentInputFrameCount);
if (canceled) return;
if(current.alpha)
await Converter.ExtractAlpha(current.framesFolder, current.framesFolder + "-a");
await FrameOrder.CreateFrameOrderFile(current.framesFolder, Config.GetBool("enableLoop"), current.interpFactor);
if (canceled) return;
AiProcess.filenameMap = IOUtils.RenameCounterDirReversible(current.framesFolder, "png", 1, 8);
}
public static async Task RunAi(string outpath, AI ai, bool stepByStep = false)
{
Program.mainForm.SetStatus("Downloading models...");
await ModelDownloader.DownloadModelFiles(Path.GetFileNameWithoutExtension(ai.pkg.fileName), current.model);
if (canceled) return;
currentlyUsingAutoEnc = Utils.CanUseAutoEnc(stepByStep, current);
IOUtils.CreateDir(outpath);
List<Task> tasks = new List<Task>();
if (ai.aiName == Networks.rifeCuda.aiName)
tasks.Add(AiProcess.RunRifeCuda(current.framesFolder, current.interpFactor, current.model));
if (ai.aiName == Networks.rifeNcnn.aiName)
tasks.Add(AiProcess.RunRifeNcnn(current.framesFolder, outpath, current.interpFactor, current.model));
if (ai.aiName == Networks.dainNcnn.aiName)
tasks.Add(AiProcess.RunDainNcnn(current.framesFolder, outpath, current.interpFactor, current.model, Config.GetInt("dainNcnnTilesize", 512)));
if (currentlyUsingAutoEnc)
{
Logger.Log($"{Logger.GetLastLine()} (Using Auto-Encode)", true);
tasks.Add(AutoEncode.MainLoop(outpath));
}
Program.mainForm.SetStatus("Running AI...");
await Task.WhenAll(tasks);
}
public static void Cancel(string reason = "", bool noMsgBox = false)
{
if (AiProcess.currentAiProcess != null && !AiProcess.currentAiProcess.HasExited)
OSUtils.KillProcessTree(AiProcess.currentAiProcess.Id);
if (AvProcess.lastProcess != null && !AvProcess.lastProcess.HasExited)
OSUtils.KillProcessTree(AvProcess.lastProcess.Id);
canceled = true;
Program.mainForm.SetStatus("Canceled.");
Program.mainForm.SetProgress(0);
if (Config.GetInt("processingMode") == 0 && !Config.GetBool("keepTempFolder"))
IOUtils.TryDeleteIfExists(current.tempFolder);
AutoEncode.busy = false;
Program.mainForm.SetWorking(false);
Program.mainForm.SetTab("interpolation");
if(!Logger.GetLastLine().Contains("Canceled interpolation."))
Logger.Log("Canceled interpolation.");
if (!string.IsNullOrWhiteSpace(reason) && !noMsgBox)
Utils.ShowMessage($"Canceled:\n\n{reason}");
}
public static void Cleanup(string interpFramesDir, bool ignoreKeepSetting = false)
{
if (!ignoreKeepSetting && Config.GetBool("keepTempFolder")) return;
Logger.Log("Deleting temporary files...");
try
{
if (Config.GetBool("keepFrames"))
IOUtils.Copy(interpFramesDir, Path.Combine(current.tempFolder.GetParentDir(), Path.GetFileName(current.tempFolder).Replace("-temp", "-interpframes")));
Directory.Delete(current.tempFolder, true);
}
catch (Exception e)
{
Logger.Log("Cleanup Error: " + e.Message, true);
}
}
}
}

View File

@@ -0,0 +1,142 @@
using Flowframes.AudioVideo;
using Flowframes.Data;
using Flowframes.IO;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Main
{
using static Interpolate;
class InterpolateSteps
{
public enum Step { ExtractScnChanges, ExtractFrames, Interpolate, CreateVid, Reset }
//public static string current.inPath;
//public static string currentOutPath;
//public static string current.interpFolder;
//public static AI currentAi;
//public static OutMode currentOutMode;
public static async Task Run(string step)
{
Logger.Log($"[SBS] Running step '{step}'", true);
canceled = false;
Program.mainForm.SetWorking(true);
current = Program.mainForm.GetCurrentSettings();
if (!InterpolateUtils.InputIsValid(current.inPath, current.outPath, current.outFps, current.interpFactor, current.outMode)) return; // General input checks
if (step.Contains("Extract Scene Changes"))
{
if (!current.inputIsFrames) // Input is video - extract frames first
await ExtractSceneChanges();
else
InterpolateUtils.ShowMessage("Scene changes can only be extracted from videos, not frames!", "Error");
}
if (step.Contains("Extract Frames"))
{
await GetFrames();
}
if (step.Contains("Run Interpolation"))
await DoInterpolate();
if (step.Contains("Export"))
await CreateOutputVid();
if (step.Contains("Cleanup"))
await Reset();
Program.mainForm.SetWorking(false);
Logger.Log("Done running this step.");
}
public static async Task ExtractSceneChanges()
{
string scenesPath = Path.Combine(current.tempFolder, Paths.scenesDir);
if (!IOUtils.TryDeleteIfExists(scenesPath))
{
InterpolateUtils.ShowMessage("Failed to delete existing scenes folder - Make sure no file is opened in another program!", "Error");
return;
}
Program.mainForm.SetStatus("Extracting scenes from video...");
await FFmpegCommands.ExtractSceneChanges(current.inPath, scenesPath, current.inFps);
await Task.Delay(10);
}
public static async Task ExtractVideoFrames()
{
if (!IOUtils.TryDeleteIfExists(current.framesFolder))
{
InterpolateUtils.ShowMessage("Failed to delete existing frames folder - Make sure no file is opened in another program!", "Error");
return;
}
currentInputFrameCount = await InterpolateUtils.GetInputFrameCountAsync(current.inPath);
AiProcess.filenameMap.Clear();
await ExtractFrames(current.inPath, current.framesFolder, false, true);
}
public static async Task DoInterpolate()
{
current.framesFolder = Path.Combine(current.tempFolder, Paths.framesDir);
if (!Directory.Exists(current.framesFolder) || IOUtils.GetAmountOfFiles(current.framesFolder, false, "*.png") < 2)
{
InterpolateUtils.ShowMessage("There are no extracted frames that can be interpolated!\nDid you run the extraction step?", "Error");
return;
}
if (!IOUtils.TryDeleteIfExists(current.interpFolder))
{
InterpolateUtils.ShowMessage("Failed to delete existing frames folder - Make sure no file is opened in another program!", "Error");
return;
}
currentInputFrameCount = await InterpolateUtils.GetInputFrameCountAsync(current.inPath);
foreach (string ini in Directory.GetFiles(current.tempFolder, "*.ini", SearchOption.TopDirectoryOnly))
IOUtils.TryDeleteIfExists(ini);
IOUtils.ReverseRenaming(AiProcess.filenameMap, true); // Get timestamps back
// TODO: Check if this works lol, remove if it does
//if (Config.GetBool("sbsAllowAutoEnc"))
// nextOutPath = Path.Combine(currentOutPath, Path.GetFileNameWithoutExtension(current.inPath) + IOUtils.GetAiSuffix(current.ai, current.interpFactor) + InterpolateUtils.GetExt(current.outMode));
await PostProcessFrames(true);
int frames = IOUtils.GetAmountOfFiles(current.framesFolder, false, "*.png");
int targetFrameCount = frames * current.interpFactor;
if (canceled) return;
Program.mainForm.SetStatus("Running AI...");
await RunAi(current.interpFolder, current.ai, true);
Program.mainForm.SetProgress(0);
}
public static async Task CreateOutputVid()
{
string[] outFrames = IOUtils.GetFilesSorted(current.interpFolder, $"*.{InterpolateUtils.GetOutExt()}");
if (outFrames.Length > 0 && !IOUtils.CheckImageValid(outFrames[0]))
{
InterpolateUtils.ShowMessage("Invalid frame files detected!\n\nIf you used Auto-Encode, this is normal, and you don't need to run " +
"this step as the video was already created in the \"Interpolate\" step.", "Error");
return;
}
string outPath = Path.Combine(current.outPath, Path.GetFileNameWithoutExtension(current.inPath) + IOUtils.GetCurrentExportSuffix() + FFmpegUtils.GetExt(current.outMode));
await CreateVideo.Export(current.interpFolder, outPath, current.outMode);
}
public static async Task Reset()
{
Cleanup(current.interpFolder, true);
}
}
}

View File

@@ -0,0 +1,444 @@
using Flowframes.Data;
using Flowframes.Forms;
using Flowframes.IO;
using Flowframes.MiscUtils;
using Flowframes.OS;
using Flowframes.UI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using i = Flowframes.Interpolate;
using Padding = Flowframes.Data.Padding;
namespace Flowframes.Main
{
class InterpolateUtils
{
public static PictureBox preview;
public static BigPreviewForm bigPreviewForm;
public static async Task CopyLastFrame (int lastFrameNum)
{
try
{
lastFrameNum--; // We have to do this as extracted frames start at 0, not 1
bool frameFolderInput = IOUtils.IsPathDirectory(i.current.inPath);
string targetPath = Path.Combine(i.current.framesFolder, lastFrameNum.ToString().PadLeft(Padding.inputFrames, '0') + ".png");
if (File.Exists(targetPath)) return;
Size res = IOUtils.GetImage(IOUtils.GetFilesSorted(i.current.framesFolder, false).First()).Size;
if (frameFolderInput)
{
string lastFramePath = IOUtils.GetFilesSorted(i.current.inPath, false).Last();
await FFmpegCommands.ExtractLastFrame(lastFramePath, targetPath, res);
}
else
{
await FFmpegCommands.ExtractLastFrame(i.current.inPath, targetPath, res);
}
}
catch (Exception e)
{
Logger.Log("CopyLastFrame Error: " + e.Message);
}
}
public static string GetOutExt (bool withDot = false)
{
string dotStr = withDot ? "." : "";
if (Config.GetBool("jpegInterp"))
return dotStr + "jpg";
return dotStr + "png";
}
public static int targetFrames;
public static string currentOutdir;
public static int currentFactor;
public static bool progressPaused = false;
public static bool progCheckRunning = false;
public static async void GetProgressByFrameAmount(string outdir, int target)
{
progCheckRunning = true;
targetFrames = target;
currentOutdir = outdir;
Logger.Log($"Starting GetProgressByFrameAmount() loop for outdir '{currentOutdir}', target is {target} frames", true);
bool firstProgUpd = true;
Program.mainForm.SetProgress(0);
while (Program.busy)
{
if (!progressPaused && AiProcess.processTime.IsRunning && Directory.Exists(currentOutdir))
{
if (firstProgUpd && Program.mainForm.IsInFocus())
Program.mainForm.SetTab("preview");
firstProgUpd = false;
string[] frames = IOUtils.GetFilesSorted(currentOutdir, $"*.{GetOutExt()}");
if (frames.Length > 1)
UpdateInterpProgress(frames.Length, targetFrames, frames[frames.Length - 1]);
if (frames.Length >= targetFrames)
break;
await Task.Delay(GetProgressWaitTime(frames.Length));
}
else
{
await Task.Delay(200);
}
}
progCheckRunning = false;
if (i.canceled)
Program.mainForm.SetProgress(0);
}
public static void UpdateInterpProgress(int frames, int target, string latestFramePath = "")
{
if (i.canceled) return;
frames = frames.Clamp(0, target);
int percent = (int)Math.Round(((float)frames / target) * 100f);
Program.mainForm.SetProgress(percent);
float generousTime = ((AiProcess.processTime.ElapsedMilliseconds - AiProcess.lastStartupTimeMs) / 1000f);
float fps = (float)frames / generousTime;
string fpsIn = (fps / currentFactor).ToString("0.00");
string fpsOut = fps.ToString("0.00");
float secondsPerFrame = generousTime / (float)frames;
int framesLeft = target - frames;
float eta = framesLeft * secondsPerFrame;
string etaStr = FormatUtils.Time(new TimeSpan(0, 0, eta.RoundToInt()), false);
bool replaceLine = Regex.Split(Logger.textbox.Text, "\r\n|\r|\n").Last().Contains("Average Speed: ");
string logStr = $"Interpolated {frames}/{target} frames ({percent}%) - Average Speed: {fpsIn} FPS In / {fpsOut} FPS Out - ";
logStr += $"Time: {FormatUtils.Time(AiProcess.processTime.Elapsed)} - ETA: {etaStr}";
if (AutoEncode.busy) logStr += " - Encoding...";
Logger.Log(logStr, false, replaceLine);
try
{
if (!string.IsNullOrWhiteSpace(latestFramePath) && frames > currentFactor)
{
if (bigPreviewForm == null && !preview.Visible /* ||Program.mainForm.WindowState != FormWindowState.Minimized */ /* || !Program.mainForm.IsInFocus()*/) return; // Skip if the preview is not visible or the form is not in focus
Image img = IOUtils.GetImage(latestFramePath);
SetPreviewImg(img);
}
}
catch { }
}
public static void SetPreviewImg (Image img)
{
if (img == null)
return;
preview.Image = img;
if (bigPreviewForm != null)
bigPreviewForm.SetImage(img);
}
public static Dictionary<string, int> frameCountCache = new Dictionary<string, int>();
public static async Task<int> GetInputFrameCountAsync (string path)
{
string hash = await IOUtils.GetHashAsync(path, IOUtils.Hash.xxHash); // Get checksum for caching
if (hash.Length > 1 && frameCountCache.ContainsKey(hash))
{
Logger.Log($"FrameCountCache contains this hash ({hash}), using cached frame count.", true);
return frameCountCache[hash];
}
else
{
Logger.Log($"Hash ({hash}) not cached, reading frame count.", true);
}
int frameCount = 0;
if (IOUtils.IsPathDirectory(path))
frameCount = IOUtils.GetAmountOfFiles(path, false);
else
frameCount = await FFmpegCommands.GetFrameCountAsync(path);
if (hash.Length > 1 && frameCount > 5000) // Cache if >5k frames to avoid re-reading it every single time
{
Logger.Log($"Adding hash ({hash}) with frame count {frameCount} to cache.", true);
frameCountCache[hash] = frameCount; // Use CRC32 instead of path to avoid using cached value if file was changed
}
return frameCount;
}
public static int GetProgressWaitTime(int numFrames)
{
float hddMultiplier = !Program.lastInputPathIsSsd ? 2f : 1f;
int waitMs = 200;
if (numFrames > 100)
waitMs = 500;
if (numFrames > 1000)
waitMs = 1000;
if (numFrames > 2500)
waitMs = 1500;
if (numFrames > 5000)
waitMs = 2500;
return (waitMs * hddMultiplier).RoundToInt();
}
public static string GetTempFolderLoc (string inPath, string outPath)
{
string basePath = inPath.GetParentDir();
if(Config.GetInt("tempFolderLoc") == 1)
basePath = outPath.GetParentDir();
if (Config.GetInt("tempFolderLoc") == 2)
basePath = outPath;
if (Config.GetInt("tempFolderLoc") == 3)
basePath = IOUtils.GetExeDir();
if (Config.GetInt("tempFolderLoc") == 4)
{
string custPath = Config.Get("tempDirCustom");
if(IOUtils.IsDirValid(custPath))
basePath = custPath;
}
return Path.Combine(basePath, Path.GetFileNameWithoutExtension(inPath).StripBadChars().Remove(" ").Trunc(30, false) + "-temp");
}
public static bool InputIsValid(string inDir, string outDir, float fpsOut, int interp, Interpolate.OutMode outMode)
{
bool passes = true;
bool isFile = !IOUtils.IsPathDirectory(inDir);
if ((passes && isFile && !IOUtils.IsFileValid(inDir)) || (!isFile && !IOUtils.IsDirValid(inDir)))
{
ShowMessage("Input path is not valid!");
passes = false;
}
if (passes && !IOUtils.IsDirValid(outDir))
{
ShowMessage("Output path is not valid!");
passes = false;
}
if (passes && interp != 2 && interp != 4 && interp != 8)
{
ShowMessage("Interpolation factor is not valid!");
passes = false;
}
if (passes && outMode == i.OutMode.VidGif && fpsOut > 50)
{
ShowMessage("Invalid output frame rate!\nGIF does not properly support frame rates above 40 FPS.\nPlease use MP4, WEBM or another video format.");
passes = false;
}
if (passes && fpsOut < 1 || fpsOut > 500)
{
ShowMessage("Invalid output frame rate - Must be 1-500.");
passes = false;
}
if (!passes)
i.Cancel("Invalid settings detected.", true);
return passes;
}
public static void PathAsciiCheck (string inpath, string outpath)
{
bool shownMsg = false;
if (OSUtils.HasNonAsciiChars(inpath))
{
ShowMessage("Warning: Input path includes non-ASCII characters. This might cause problems.");
shownMsg = true;
}
if (!shownMsg && OSUtils.HasNonAsciiChars(outpath))
ShowMessage("Warning: Output path includes non-ASCII characters. This might cause problems.");
}
public static void GifCompatCheck (Interpolate.OutMode outMode, float fpsOut, int targetFrameCount)
{
if (outMode != Interpolate.OutMode.VidGif)
return;
if(fpsOut >= 50f)
Logger.Log("Warning: GIFs above 50 FPS might play slower on certain software/hardware! MP4 is recommended for higher frame rates.");
int maxGifFrames = 200;
if (targetFrameCount > maxGifFrames)
{
ShowMessage($"You can't use GIF with more than {maxGifFrames} output frames!\nPlease use MP4 for this.", "Error");
i.Cancel($"Can't use GIF encoding with more than {maxGifFrames} frames!");
}
}
public static bool CheckAiAvailable (AI ai)
{
if (!PkgUtils.IsAiAvailable(ai))
{
ShowMessage("The selected AI is not installed!\nYou can download it from the Package Installer.", "Error");
i.Cancel("Selected AI not available.", true);
return false;
}
return true;
}
public static bool CheckDeleteOldTempFolder ()
{
if (!IOUtils.TryDeleteIfExists(i.current.tempFolder))
{
ShowMessage("Failed to remove an existing temp folder of this video!\nMake sure you didn't open any frames in an editor.", "Error");
i.Cancel();
return false;
}
return true;
}
public static bool CheckPathValid (string path)
{
if (IOUtils.IsPathDirectory(path))
{
if (!IOUtils.IsDirValid(path))
{
ShowMessage("Input directory is not valid.");
i.Cancel();
return false;
}
}
else
{
if (!IsVideoValid(path))
{
ShowMessage("Input video file is not valid.");
return false;
}
}
return true;
}
public static bool IsVideoValid(string videoPath)
{
if (videoPath == null || !IOUtils.IsFileValid(videoPath))
return false;
// string ext = Path.GetExtension(videoPath).ToLower();
// if (!Formats.supported.Contains(ext))
// return false;
return true;
}
public static void ShowMessage(string msg, string title = "Message")
{
if (!BatchProcessing.busy)
MessageBox.Show(msg, title);
Logger.Log("Message: " + msg, true);
}
public static async Task<Size> GetOutputResolution (string inputPath, bool print)
{
Size resolution = await IOUtils.GetVideoOrFramesRes(inputPath);
return GetOutputResolution(resolution, print);
}
public static Size GetOutputResolution(Size inputRes, bool print = false)
{
int maxHeight = RoundDiv2(Config.GetInt("maxVidHeight"));
if (inputRes.Height > maxHeight)
{
float factor = (float)maxHeight / inputRes.Height;
Logger.Log($"Un-rounded downscaled size: {(inputRes.Width * factor).ToString("0.00")}x{Config.GetInt("maxVidHeight")}", true);
int width = RoundDiv2((inputRes.Width * factor).RoundToInt());
if (print)
Logger.Log($"Video is bigger than the maximum - Downscaling to {width}x{maxHeight}.");
return new Size(width, maxHeight);
}
else
{
return new Size(RoundDiv2(inputRes.Width), RoundDiv2(inputRes.Height));
}
}
public static int RoundDiv2(int n) // Round to a number that's divisible by 2 (for h264 etc)
{
int a = (n / 2) * 2; // Smaller multiple
int b = a + 2; // Larger multiple
return (n - a > b - n) ? b : a; // Return of closest of two
}
public static bool CanUseAutoEnc (bool stepByStep, InterpSettings current)
{
AutoEncode.UpdateChunkAndBufferSizes();
if (current.alpha)
{
Logger.Log($"Not Using AutoEnc: Alpha mode is enabled.", true);
return false;
}
if (!current.outMode.ToString().ToLower().Contains("vid") || current.outMode.ToString().ToLower().Contains("gif"))
{
Logger.Log($"Not Using AutoEnc: Out Mode is not video ({current.outMode.ToString()})", true);
return false;
}
if(stepByStep && !Config.GetBool("sbsAllowAutoEnc"))
{
Logger.Log($"Not Using AutoEnc: Using step-by-step mode, but 'sbsAllowAutoEnc' is false.", true);
return false;
}
if (!stepByStep && Config.GetInt("autoEncMode") == 0)
{
Logger.Log($"Not Using AutoEnc: 'autoEncMode' is 0.", true);
return false;
}
int inFrames = IOUtils.GetAmountOfFiles(current.framesFolder, false);
if (inFrames * current.interpFactor < (AutoEncode.chunkSize + AutoEncode.safetyBufferFrames) * 1.2f)
{
Logger.Log($"Not Using AutoEnc: Input frames ({inFrames}) * factor ({current.interpFactor}) is smaller than (chunkSize ({AutoEncode.chunkSize}) + safetyBufferFrames ({AutoEncode.safetyBufferFrames}) * 1.2f)", true);
return false;
}
return true;
}
public static async Task<bool> UseUHD ()
{
return (await GetOutputResolution(i.current.inPath, false)).Height >= Config.GetInt("uhdThresh");
}
public static void FixConsecutiveSceneFrames (string sceneFramesPath, string sourceFramesPath)
{
if (!Directory.Exists(sceneFramesPath) || IOUtils.GetAmountOfFiles(sceneFramesPath, false) < 1)
return;
List<string> sceneFrames = IOUtils.GetFilesSorted(sceneFramesPath).Select(x => Path.GetFileNameWithoutExtension(x)).ToList();
List<string> sourceFrames = IOUtils.GetFilesSorted(sourceFramesPath).Select(x => Path.GetFileNameWithoutExtension(x)).ToList();
List<string> sceneFramesToDelete = new List<string>();
foreach(string scnFrame in sceneFrames)
{
if (sceneFramesToDelete.Contains(scnFrame))
continue;
int sourceIndexForScnFrame = sourceFrames.IndexOf(scnFrame); // Get source index of scene frame
if ((sourceIndexForScnFrame + 1) == sourceFrames.Count)
continue;
string followingFrame = sourceFrames[sourceIndexForScnFrame + 1]; // Get filename/timestamp of the next source frame
if (sceneFrames.Contains(followingFrame)) // If next source frame is in scene folder, add to deletion list
sceneFramesToDelete.Add(followingFrame);
}
foreach (string frame in sceneFramesToDelete)
IOUtils.TryDeleteIfExists(Path.Combine(sceneFramesPath, frame + ".png"));
}
}
}