mirror of
https://github.com/n00mkrad/flowframes.git
synced 2025-12-16 16:37:48 +01:00
WIP: StepByStep fixes to be able to re-use existing files, dynamically use AutoEnc
+ faster thumbnai (single frame) extraction using lower png compression + some null checks & cancel checks
This commit is contained in:
@@ -58,7 +58,7 @@ namespace Flowframes
|
||||
public static async Task ExtractSingleFrame(string inputFile, string outputPath, int frameNum, bool hdr, bool delSrc)
|
||||
{
|
||||
string hdrStr = hdr ? hdrFilter : "";
|
||||
string args = $"-i {inputFile.Wrap()} {hdrStr }-vf \"select=eq(n\\,{frameNum})\" -vframes 1 {outputPath.Wrap()}";
|
||||
string args = $"-i {inputFile.Wrap()} {pngComprArg} {hdrStr }-vf \"select=eq(n\\,{frameNum})\" -vframes 1 {outputPath.Wrap()}";
|
||||
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
|
||||
if (delSrc)
|
||||
DeleteSource(inputFile);
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
@@ -435,5 +436,17 @@ namespace Flowframes.IO
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetFileMd5 (string filename)
|
||||
{
|
||||
using (var md5 = MD5.Create())
|
||||
{
|
||||
using (var stream = File.OpenRead(filename))
|
||||
{
|
||||
var hash = md5.ComputeHash(stream);
|
||||
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace Flowframes.Main
|
||||
{
|
||||
static string interpFramesFolder;
|
||||
static string videoChunksFolder;
|
||||
static int chunkSize = 125; // Encode every n frames
|
||||
static int safetyBufferFrames = 25;
|
||||
public static int chunkSize = 125; // Encode every n frames
|
||||
public static int safetyBufferFrames = 25;
|
||||
public static List<string> encodedFrames = new List<string>();
|
||||
public static List<string> unencodedFrames = new List<string>();
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Flowframes.Main
|
||||
|
||||
public static bool HasWorkToDo ()
|
||||
{
|
||||
if (Interpolate.canceled) return false;
|
||||
if (Interpolate.canceled || interpFramesFolder == null) return false;
|
||||
return ((AiProcess.currentAiProcess != null && !AiProcess.currentAiProcess.HasExited) || encodedFrames.Count < GetInterpFramesAmount());
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ namespace Flowframes
|
||||
public static float currentInFps;
|
||||
public static float currentOutFps;
|
||||
public static OutMode currentOutMode;
|
||||
|
||||
public static bool currentInputIsFrames;
|
||||
public static bool currentlyUsingAutoEnc;
|
||||
public static int lastInterpFactor;
|
||||
public static string lastInputPath;
|
||||
public static string nextOutPath;
|
||||
@@ -59,10 +60,11 @@ namespace Flowframes
|
||||
if(!Utils.CheckPathValid(inPath)) return; // Check if input path/file is valid
|
||||
Utils.PathAsciiCheck(inPath, outDir);
|
||||
lastAi = ai;
|
||||
currentInputIsFrames = IOUtils.IsPathDirectory(inPath);
|
||||
Program.mainForm.SetStatus("Starting...");
|
||||
Program.mainForm.SetWorking(true);
|
||||
await Task.Delay(10);
|
||||
if (!IOUtils.IsPathDirectory(inPath)) // Input is video - extract frames first
|
||||
if (!currentInputIsFrames) // Input is video - extract frames first
|
||||
await ExtractFrames(inPath, currentFramesPath);
|
||||
else
|
||||
IOUtils.Copy(inPath, currentFramesPath);
|
||||
@@ -81,7 +83,8 @@ namespace Flowframes
|
||||
await RunAi(interpFramesDir, targetFrameCount, tilesize, ai);
|
||||
if (canceled) return;
|
||||
Program.mainForm.SetProgress(100);
|
||||
//await CreateVideo.FramesToVideo(interpFramesDir, nextOutPath, outMode); // TODO: DISABLE NORMAL ENCODING IF PARALLEL ENC WAS USED
|
||||
if(!currentlyUsingAutoEnc)
|
||||
await CreateVideo.FramesToVideo(interpFramesDir, nextOutPath, outMode);
|
||||
Cleanup(interpFramesDir);
|
||||
Program.mainForm.SetWorking(false);
|
||||
Logger.Log("Total processing time: " + FormatUtils.Time(sw.Elapsed));
|
||||
@@ -145,7 +148,7 @@ namespace Flowframes
|
||||
Cancel("Failed to extract frames from input video!");
|
||||
}
|
||||
|
||||
string hasPreprocessedFile = Path.Combine(currentTempDir, ".preprocessed");
|
||||
string hasPreprocessedFile = Path.Combine(currentTempDir, ".preprocessed"); // TODO: DUMP THIS WHOLE IDEA
|
||||
if (File.Exists(hasPreprocessedFile)) return;
|
||||
|
||||
if (Config.GetInt("dedupMode") == 1)
|
||||
@@ -175,6 +178,7 @@ namespace Flowframes
|
||||
|
||||
public static async Task RunAi(string outpath, int targetFrames, int tilesize, AI ai)
|
||||
{
|
||||
currentlyUsingAutoEnc = IOUtils.GetAmountOfFiles(currentFramesPath, false) >= (AutoEncode.chunkSize + AutoEncode.safetyBufferFrames) * 1.2f;
|
||||
Directory.CreateDirectory(outpath);
|
||||
|
||||
List<Task> tasks = new List<Task>();
|
||||
@@ -191,6 +195,7 @@ namespace Flowframes
|
||||
if (ai.aiName == Networks.rifeNcnn.aiName)
|
||||
tasks.Add(AiProcess.RunRifeNcnnMulti(currentFramesPath, outpath, tilesize, interpFactor));
|
||||
|
||||
if(currentlyUsingAutoEnc)
|
||||
tasks.Add(AutoEncode.MainLoop(outpath));
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,8 @@ namespace Flowframes.Main
|
||||
|
||||
if (string.IsNullOrWhiteSpace(currentFramesPath))
|
||||
currentFramesPath = Path.Combine(currentTempDir, Paths.framesDir);
|
||||
|
||||
currentInterpFramesDir = Path.Combine(currentTempDir, Paths.interpDir);
|
||||
}
|
||||
|
||||
public static async Task ExtractSceneChanges ()
|
||||
@@ -106,14 +108,18 @@ namespace Flowframes.Main
|
||||
|
||||
public static async Task DoInterpolate ()
|
||||
{
|
||||
IOUtils.TryDeleteIfExists(currentInterpFramesDir);
|
||||
foreach (string ini in Directory.GetFiles(currentTempDir, "*.ini", SearchOption.TopDirectoryOnly))
|
||||
IOUtils.TryDeleteIfExists(ini);
|
||||
|
||||
await PostProcessFrames(true);
|
||||
|
||||
string interpFramesDir = Path.Combine(currentTempDir, Paths.interpDir);
|
||||
if (!IOUtils.TryDeleteIfExists(interpFramesDir))
|
||||
{
|
||||
InterpolateUtils.ShowMessage("Failed to delete old \"interpolated-frames folder\" - Make sure none of the files are opened in another program!", "Error");
|
||||
return;
|
||||
}
|
||||
// if (!IOUtils.TryDeleteIfExists(interpFramesDir))
|
||||
// {
|
||||
// InterpolateUtils.ShowMessage("Failed to delete old \"interpolated-frames folder\" - Make sure none of the files are opened in another program!", "Error");
|
||||
// return;
|
||||
// }
|
||||
lastInterpFactor = interpFactor;
|
||||
int frames = IOUtils.GetAmountOfFiles(currentFramesPath, false, "*.png");
|
||||
int targetFrameCount = frames * lastInterpFactor;
|
||||
@@ -127,7 +133,6 @@ namespace Flowframes.Main
|
||||
|
||||
public static async Task CreateOutputVid ()
|
||||
{
|
||||
currentInterpFramesDir = Path.Combine(currentTempDir, Paths.interpDir);
|
||||
string outPath = Path.Combine(currentOutPath, Path.GetFileNameWithoutExtension(currentInPath) + IOUtils.GetAiSuffix(currentAi, lastInterpFactor) + InterpolateUtils.GetExt(currentOutMode));
|
||||
await CreateVideo.FramesToVideo(currentInterpFramesDir, outPath, currentOutMode);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,11 @@ namespace Flowframes.Main
|
||||
string scnFramesPath = Path.Combine(framesPath.GetParentDir(), Paths.scenesDir);
|
||||
string interpPath = Paths.interpDir; // framesPath.Replace(@"\", "/") + "-interpolated";
|
||||
|
||||
List<string> sceneFrames = Directory.GetFiles(scnFramesPath).Select(file => Path.GetFileName(file)).ToList();
|
||||
List<string> sceneFrames = new List<string>();
|
||||
Logger.Log($"Scn path {scnFramesPath} exists: {Directory.Exists(scnFramesPath)}");
|
||||
if (Directory.Exists(scnFramesPath))
|
||||
sceneFrames = Directory.GetFiles(scnFramesPath).Select(file => Path.GetFileName(file)).ToList();
|
||||
Logger.Log($"Found {sceneFrames.Count} scn frames");
|
||||
|
||||
int lastFrameDuration = 1;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Flowframes
|
||||
|
||||
static void AiStarted (Process proc, int startupTimeMs, string defaultExt = "png")
|
||||
{
|
||||
SwapFilenames(false);
|
||||
lastStartupTimeMs = startupTimeMs;
|
||||
InterpolateUtils.lastExt = defaultExt;
|
||||
if (Config.GetBool("jpegInterps")) InterpolateUtils.lastExt = "jpg";
|
||||
@@ -37,12 +38,32 @@ namespace Flowframes
|
||||
{
|
||||
Program.mainForm.SetProgress(100);
|
||||
string logStr = $"Done running {aiName} - Interpolation took {FormatUtils.Time(processTime.Elapsed)}";
|
||||
if (AutoEncode.HasWorkToDo())
|
||||
if (Interpolate.currentlyUsingAutoEnc && AutoEncode.HasWorkToDo())
|
||||
logStr += " - Waiting for encoding to finish...";
|
||||
Logger.Log(logStr);
|
||||
processTime.Stop();
|
||||
}
|
||||
|
||||
static Dictionary<string, string> filenameMap = new Dictionary<string, string>(); // TODO: Store on disk instead for crashes?
|
||||
public static void SwapFilenames (bool restore) // Renames files with a counter or restores original timecode names via MD5/filename mapping
|
||||
{
|
||||
Stopwatch filenameSwapSw = new Stopwatch();
|
||||
if (!restore) // Rename with counter and store original names
|
||||
{
|
||||
filenameSwapSw.Start();
|
||||
filenameMap.Clear();
|
||||
foreach (string file in Directory.GetFiles(Path.Combine(Interpolate.currentFramesPath)))
|
||||
filenameMap.Add(Path.GetFileName(file), IOUtils.GetFileMd5(file));
|
||||
Logger.Log($"Stored file/md5 pairs in {filenameSwapSw.ElapsedMilliseconds}ms");
|
||||
}
|
||||
else
|
||||
{
|
||||
filenameSwapSw.Start();
|
||||
// add restore code!
|
||||
Logger.Log($"Restored original file/md5 pairs in {filenameSwapSw.ElapsedMilliseconds}ms");
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task RunDainNcnn(string framesPath, string outPath, int targetFrames, int tilesize)
|
||||
{
|
||||
string args = $" -v -i {framesPath.Wrap()} -o {outPath.Wrap()} -n {targetFrames} -t {tilesize} -g {Config.Get("ncnnGpus")}";
|
||||
@@ -137,7 +158,7 @@ namespace Flowframes
|
||||
public static async Task RunRifeCuda(string framesPath, int interpFactor)
|
||||
{
|
||||
string script = "interp-parallel.py";
|
||||
if(Config.GetInt("rifeMode") == 0 || IOUtils.GetAmountOfFiles(framesPath, false) < 10)
|
||||
if(Config.GetInt("rifeMode") == 0 || IOUtils.GetAmountOfFiles(framesPath, false) < 6)
|
||||
script = "interp-basic.py";
|
||||
|
||||
string rifeDir = Path.Combine(Paths.GetPkgPath(), Path.GetFileNameWithoutExtension(Packages.rifeCuda.fileName));
|
||||
@@ -163,33 +184,6 @@ namespace Flowframes
|
||||
AiFinished("RIFE");
|
||||
}
|
||||
|
||||
public static async Task RunRifeNcnn (string framesPath, string outPath, int interpFactor, int tilesize)
|
||||
{
|
||||
string args = $" -v -i {framesPath.Wrap()} -o {outPath.Wrap()} -t {tilesize} -g {Config.Get("ncnnGpus")} -f {InterpolateUtils.lastExt} -j 4:{Config.Get("ncnnThreads")}:4";
|
||||
Process rifeNcnn = OSUtils.NewProcess(!OSUtils.ShowHiddenCmd());
|
||||
AiStarted(rifeNcnn, 750);
|
||||
rifeNcnn.StartInfo.Arguments = $"{OSUtils.GetCmdArg()} cd /D {PkgUtils.GetPkgFolder(Packages.rifeNcnn).Wrap()} & rife-ncnn-vulkan.exe {args}";
|
||||
Logger.Log("Running RIFE...", false);
|
||||
Logger.Log("cmd.exe " + rifeNcnn.StartInfo.Arguments, true);
|
||||
if (!OSUtils.ShowHiddenCmd())
|
||||
{
|
||||
rifeNcnn.OutputDataReceived += (sender, outLine) => { LogOutput("[O] " + outLine.Data, "rife-ncnn-log.txt"); };
|
||||
rifeNcnn.ErrorDataReceived += (sender, outLine) => { LogOutput("[E] " + outLine.Data, "rife-ncnn-log.txt"); };
|
||||
}
|
||||
rifeNcnn.Start();
|
||||
if (!OSUtils.ShowHiddenCmd())
|
||||
{
|
||||
rifeNcnn.BeginOutputReadLine();
|
||||
rifeNcnn.BeginErrorReadLine();
|
||||
}
|
||||
while (!rifeNcnn.HasExited) await Task.Delay(1);
|
||||
|
||||
if (Interpolate.canceled) return;
|
||||
Magick.MagickDedupe.ZeroPadDir(outPath, InterpolateUtils.lastExt, 8);
|
||||
|
||||
AiFinished("RIFE");
|
||||
}
|
||||
|
||||
public static async Task RunRifeNcnnMulti(string framesPath, string outPath, int tilesize, int times)
|
||||
{
|
||||
processTimeMulti.Restart();
|
||||
|
||||
@@ -36,21 +36,43 @@ namespace Flowframes.UI
|
||||
else
|
||||
Logger.Log($"Video FPS: {fpsStr} - Total Number Of Frames: {FFmpegCommands.GetFrameCount(path)}");
|
||||
await Task.Delay(10);
|
||||
Size res = FFmpegCommands.GetSize(path);
|
||||
Logger.Log($"Video Resolution: {res.Width}x{res.Height}");
|
||||
await PrintResolution(path);
|
||||
MagickDedupe.ClearCache();
|
||||
await Task.Delay(10);
|
||||
InterpolateUtils.SetPreviewImg(await GetThumbnail(path));
|
||||
}
|
||||
|
||||
static async Task<Image> GetThumbnail (string videoPath)
|
||||
static async Task PrintResolution (string path)
|
||||
{
|
||||
Size res = new Size();
|
||||
if (!IOUtils.IsPathDirectory(path)) // If path is video
|
||||
{
|
||||
res = FFmpegCommands.GetSize(path);
|
||||
}
|
||||
else // Path is frame folder
|
||||
{
|
||||
Image thumb = await GetThumbnail(path);
|
||||
res = new Size(thumb.Width, thumb.Height);
|
||||
}
|
||||
if (res.Width > 1 && res.Height > 1)
|
||||
Logger.Log($"Input Resolution: {res.Width}x{res.Height}");
|
||||
}
|
||||
|
||||
static async Task<Image> GetThumbnail (string path)
|
||||
{
|
||||
string imgOnDisk = Path.Combine(Paths.GetDataPath(), "thumb-temp.png");
|
||||
try
|
||||
{
|
||||
await FFmpegCommands.ExtractSingleFrame(videoPath, imgOnDisk, 1, false, false);
|
||||
if (!IOUtils.IsPathDirectory(path)) // If path is video - Extract first frame
|
||||
{
|
||||
await FFmpegCommands.ExtractSingleFrame(path, imgOnDisk, 1, false, false);
|
||||
return IOUtils.GetImage(imgOnDisk);
|
||||
}
|
||||
else // Path is frame folder - Get first frame
|
||||
{
|
||||
return IOUtils.GetImage(Directory.GetFiles(path)[0]);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log("GetThumbnail Error: " + e.Message, true);
|
||||
|
||||
Reference in New Issue
Block a user