mirror of
https://github.com/n00mkrad/flowframes.git
synced 2026-09-01 19:51:28 +02:00
Scene detection now works with frame sequence input, cleanup
This commit is contained in:
@@ -39,8 +39,6 @@ namespace Flowframes
|
||||
Application.Exit();
|
||||
}
|
||||
|
||||
Text = $"Flowframes {Updater.GetInstalledVer()}";
|
||||
|
||||
// Main Tab
|
||||
UIUtils.InitCombox(interpFactorCombox, 0);
|
||||
UIUtils.InitCombox(outModeCombox, 0);
|
||||
@@ -50,17 +48,14 @@ namespace Flowframes
|
||||
|
||||
Program.mainForm = this;
|
||||
Logger.textbox = logBox;
|
||||
|
||||
NvApi.Init();
|
||||
InitAis();
|
||||
InterpolateUtils.preview = previewPicturebox;
|
||||
|
||||
UpdateStepByStepControls();
|
||||
|
||||
Initialized();
|
||||
Checks();
|
||||
|
||||
HandleArguments();
|
||||
Text = $"Flowframes {Updater.GetInstalledVer()}";
|
||||
}
|
||||
|
||||
void Checks()
|
||||
@@ -204,9 +199,6 @@ namespace Flowframes
|
||||
|
||||
private void browseOutBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
Magick.SceneDetect.RunSceneDetection(inputTbox.Text.Trim());
|
||||
return;
|
||||
|
||||
CommonOpenFileDialog dialog = new CommonOpenFileDialog();
|
||||
dialog.InitialDirectory = inputTbox.Text.Trim();
|
||||
dialog.IsFolderPicker = true;
|
||||
|
||||
@@ -49,8 +49,8 @@ namespace Flowframes.Magick
|
||||
|
||||
static async Task ProcessFrame (FileInfo frame, FileInfo lastFrame, string outFolder)
|
||||
{
|
||||
MagickImage prevFrame = GetImage(lastFrame.FullName);
|
||||
MagickImage currFrame = GetImage(frame.FullName);
|
||||
MagickImage prevFrame = GetImage(lastFrame.FullName, false);
|
||||
MagickImage currFrame = GetImage(frame.FullName, false);
|
||||
|
||||
Size originalSize = new Size(currFrame.Width, currFrame.Height);
|
||||
int downscaleHeight = 144;
|
||||
@@ -60,17 +60,16 @@ namespace Flowframes.Magick
|
||||
double errNormalizedCrossCorrelation = currFrame.Compare(prevFrame, ErrorMetric.NormalizedCrossCorrelation);
|
||||
double errRootMeanSquared = currFrame.Compare(prevFrame, ErrorMetric.RootMeanSquared);
|
||||
|
||||
string str = $"Metrics of {frame.Name.Split('.')[0]} against {lastFrame.Name.Split('.')[0]}:\n";
|
||||
string str = $"\nMetrics of {frame.Name.Split('.')[0]} against {lastFrame.Name.Split('.')[0]}:\n";
|
||||
str += $"NormalizedCrossCorrelation: {errNormalizedCrossCorrelation.ToString("0.000")}\n";
|
||||
str += $"RootMeanSquared: {errRootMeanSquared.ToString("0.000")}\n";
|
||||
str += "\n\n";
|
||||
|
||||
bool nccTrigger = errNormalizedCrossCorrelation < 0.45f;
|
||||
bool rMeanSqrTrigger = errRootMeanSquared > 0.18f;
|
||||
bool rmsNccTrigger = errRootMeanSquared > 0.18f && errNormalizedCrossCorrelation < 0.6f;
|
||||
bool nccRmsTrigger = errNormalizedCrossCorrelation < 0.45f && errRootMeanSquared > 0.11f;
|
||||
|
||||
// if (nccTrigger) str += "\nNCC SCENE CHANGE TRIGGER!";
|
||||
// if (rMeanSqrTrigger) str += "\nROOTMEANSQR SCENE CHANGE TRIGGER!";
|
||||
if (rmsNccTrigger) str += "\n\nRMS -> NCC DOUBLE SCENE CHANGE TRIGGER!";
|
||||
if (nccRmsTrigger) str += "\n\nNCC -> RMS DOUBLE SCENE CHANGE TRIGGER!";
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Flowframes.Main
|
||||
for (int vfrLine = lastEncodedFrameNum; vfrLine < interpFramesLines.Length; vfrLine++)
|
||||
unencodedFrameLines.Add(vfrLine);
|
||||
|
||||
bool aiRunning = !AiProcess.currentAiProcess.HasExited;
|
||||
bool aiRunning = !AiProcess.lastAiProcess.HasExited;
|
||||
|
||||
if (unencodedFrameLines.Count > 0 && (unencodedFrameLines.Count >= (chunkSize + safetyBufferFrames) || !aiRunning)) // Encode every n frames, or after process has exited
|
||||
{
|
||||
@@ -167,7 +167,7 @@ namespace Flowframes.Main
|
||||
{
|
||||
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);
|
||||
return ((AiProcess.lastAiProcess != null && !AiProcess.lastAiProcess.HasExited) || encodedFrameLines.Count < interpFramesLines.Length);
|
||||
}
|
||||
|
||||
static int GetChunkSize(int targetFramesAmount)
|
||||
|
||||
@@ -69,25 +69,24 @@ namespace Flowframes
|
||||
Program.mainForm.SetStatus("Done interpolating!");
|
||||
}
|
||||
|
||||
public static async Task GetFrames (bool stepByStep = false)
|
||||
public static async Task GetFrames ()
|
||||
{
|
||||
current.RefreshAlpha();
|
||||
|
||||
if (Config.GetBool("scnDetect"))
|
||||
{
|
||||
Program.mainForm.SetStatus("Extracting scenes from video...");
|
||||
await FfmpegExtract.ExtractSceneChanges(current.inPath, Path.Combine(current.tempFolder, Paths.scenesDir), current.inFps, current.inputIsFrames);
|
||||
}
|
||||
|
||||
if (!current.inputIsFrames) // Extract if input is video, import if image sequence
|
||||
await ExtractFrames(current.inPath, current.framesFolder, current.alpha, !stepByStep);
|
||||
await ExtractFrames(current.inPath, current.framesFolder, current.alpha);
|
||||
else
|
||||
await FfmpegExtract.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 sceneDetect)
|
||||
public static async Task ExtractFrames(string inPath, string outPath, bool alpha)
|
||||
{
|
||||
if (sceneDetect && Config.GetBool("scnDetect"))
|
||||
{
|
||||
Program.mainForm.SetStatus("Extracting scenes from video...");
|
||||
await FfmpegExtract.ExtractSceneChanges(inPath, Path.Combine(current.tempFolder, Paths.scenesDir), current.inFps);
|
||||
await Task.Delay(10);
|
||||
}
|
||||
|
||||
if (canceled) return;
|
||||
Program.mainForm.SetStatus("Extracting frames from video...");
|
||||
bool mpdecimate = Config.GetInt("dedupMode") == 2;
|
||||
@@ -208,16 +207,11 @@ namespace Flowframes
|
||||
|
||||
public static void Cancel(string reason = "", bool noMsgBox = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
OSUtils.KillProcessTree(AiProcess.currentAiProcess.Id);
|
||||
OSUtils.KillProcessTree(AvProcess.lastProcess.Id);
|
||||
}
|
||||
catch { }
|
||||
|
||||
canceled = true;
|
||||
Program.mainForm.SetStatus("Canceled.");
|
||||
Program.mainForm.SetProgress(0);
|
||||
AiProcess.Kill();
|
||||
AvProcess.Kill();
|
||||
|
||||
if (!current.stepByStep && !Config.GetBool("keepTempFolder"))
|
||||
{
|
||||
|
||||
@@ -45,25 +45,10 @@ namespace Flowframes.Main
|
||||
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 FfmpegExtract.ExtractSceneChanges(current.inPath, scenesPath, current.inFps);
|
||||
await Task.Delay(10);
|
||||
}
|
||||
|
||||
public static async Task ExtractFramesStep()
|
||||
{
|
||||
if (Config.GetBool("scnDetect") && !current.inputIsFrames) // Input is video - extract frames first
|
||||
await ExtractSceneChanges();
|
||||
// if (Config.GetBool("scnDetect") && !current.inputIsFrames) // Input is video - extract frames first
|
||||
// await ExtractSceneChanges();
|
||||
|
||||
if (!IOUtils.TryDeleteIfExists(current.framesFolder))
|
||||
{
|
||||
@@ -74,7 +59,7 @@ namespace Flowframes.Main
|
||||
currentInputFrameCount = await InterpolateUtils.GetInputFrameCountAsync(current.inPath);
|
||||
AiProcess.filenameMap.Clear();
|
||||
|
||||
await GetFrames(true);
|
||||
await GetFrames();
|
||||
await PostProcessFrames(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Flowframes
|
||||
{
|
||||
class AvProcess
|
||||
{
|
||||
public static Process lastProcess;
|
||||
public static Process lastAvProcess;
|
||||
public static Stopwatch timeSinceLastOutput = new Stopwatch();
|
||||
public enum TaskType { ExtractFrames, ExtractOther, Encode, GetInfo, Merge, Other };
|
||||
public static TaskType lastTask = TaskType.Other;
|
||||
@@ -28,6 +28,20 @@ namespace Flowframes
|
||||
|
||||
static string defLogLevel = "warning";
|
||||
|
||||
public static void Kill()
|
||||
{
|
||||
if (lastAvProcess == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
OSUtils.KillProcessTree(lastAvProcess.Id);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"Failed to kill lastAvProcess process tree: {e.Message}", true);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task RunFfmpeg(string args, LogMode logMode, TaskType taskType = TaskType.Other, bool progressBar = false)
|
||||
{
|
||||
await RunFfmpeg(args, "", logMode, defLogLevel, taskType, progressBar);
|
||||
@@ -50,7 +64,7 @@ namespace Flowframes
|
||||
showProgressBar = progressBar;
|
||||
Process ffmpeg = OSUtils.NewProcess(true);
|
||||
timeSinceLastOutput.Restart();
|
||||
lastProcess = ffmpeg;
|
||||
lastAvProcess = ffmpeg;
|
||||
lastTask = taskType;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(loglevel))
|
||||
@@ -126,7 +140,7 @@ namespace Flowframes
|
||||
public static string GetFfmpegOutput (string args)
|
||||
{
|
||||
Process ffmpeg = OSUtils.NewProcess(true);
|
||||
lastProcess = ffmpeg;
|
||||
lastAvProcess = ffmpeg;
|
||||
ffmpeg.StartInfo.Arguments = $"{GetCmdArg()} cd /D {GetAvDir().Wrap()} & ffmpeg.exe -hide_banner -y -stats {args}";
|
||||
Logger.Log("cmd.exe " + ffmpeg.StartInfo.Arguments, true, false, "ffmpeg");
|
||||
ffmpeg.Start();
|
||||
@@ -144,7 +158,7 @@ namespace Flowframes
|
||||
lastOutputFfmpeg = "";
|
||||
showProgressBar = progressBar;
|
||||
Process ffmpeg = OSUtils.NewProcess(true);
|
||||
lastProcess = ffmpeg;
|
||||
lastAvProcess = ffmpeg;
|
||||
ffmpeg.StartInfo.Arguments = $"{GetCmdArg()} cd /D {GetAvDir().Wrap()} & ffmpeg.exe -hide_banner -y -stats {args}";
|
||||
Logger.Log("cmd.exe " + ffmpeg.StartInfo.Arguments, true, false, "ffmpeg");
|
||||
if (setBusy) Program.mainForm.SetWorking(true);
|
||||
@@ -204,7 +218,7 @@ namespace Flowframes
|
||||
if (Program.busy) return;
|
||||
|
||||
await Task.Delay(100);
|
||||
while(!lastProcess.HasExited)
|
||||
while(!lastAvProcess.HasExited)
|
||||
await Task.Delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,29 +16,40 @@ namespace Flowframes.Media
|
||||
{
|
||||
partial class FfmpegExtract : FfmpegCommands
|
||||
{
|
||||
public static async Task ExtractSceneChanges(string inputFile, string frameFolderPath, float rate)
|
||||
public static async Task ExtractSceneChanges(string inPath, string outDir, float rate, bool inputIsFrames = false)
|
||||
{
|
||||
Logger.Log("Extracting scene changes...");
|
||||
await VideoToFrames(inputFile, frameFolderPath, false, rate, false, false, new Size(320, 180), true);
|
||||
Directory.CreateDirectory(outDir);
|
||||
|
||||
string inArg = $"-i {inPath.Wrap()}";
|
||||
|
||||
if (inputIsFrames)
|
||||
{
|
||||
string concatFile = Path.Combine(Paths.GetDataPath(), "png-scndetect-concat-temp.ini");
|
||||
GetConcatFile(inPath, concatFile);
|
||||
inArg = $"-f concat -safe 0 -i {concatFile.Wrap()}";
|
||||
}
|
||||
|
||||
string scnDetect = $"-vf \"select='gt(scene,{Config.GetFloatString("scnDetectValue")})'\"";
|
||||
string rateArg = (rate > 0) ? $"-r {rate.ToStringDot()}" : "";
|
||||
string args = $"{rateArg} -vsync 0 {GetTrimArg(true)} {inArg} {compr} {scnDetect} -frame_pts true -s 256x144 {GetTrimArg(false)} \"{outDir}/%{Padding.inputFrames}d.png\"";
|
||||
|
||||
LogMode logMode = Interpolate.currentInputFrameCount > 50 ? LogMode.OnlyLastLine : LogMode.Hidden;
|
||||
await RunFfmpeg(args, logMode, inputIsFrames ? "panic" : "warning", TaskType.ExtractFrames, true);
|
||||
|
||||
bool hiddenLog = Interpolate.currentInputFrameCount <= 50;
|
||||
int amount = IOUtils.GetAmountOfFiles(frameFolderPath, false);
|
||||
int amount = IOUtils.GetAmountOfFiles(outDir, false);
|
||||
Logger.Log($"Detected {amount} scene {(amount == 1 ? "change" : "changes")}.".Replace(" 0 ", " no "), false, !hiddenLog);
|
||||
}
|
||||
|
||||
public static async Task VideoToFrames(string inputFile, string framesDir, bool alpha, float rate, bool deDupe, bool delSrc)
|
||||
public static async Task VideoToFrames(string inputFile, string framesDir, bool alpha, float rate, bool deDupe, bool delSrc, Size size)
|
||||
{
|
||||
await VideoToFrames(inputFile, framesDir, alpha, rate, deDupe, delSrc, new Size());
|
||||
}
|
||||
|
||||
public static async Task VideoToFrames(string inputFile, string framesDir, bool alpha, float rate, bool deDupe, bool delSrc, Size size, bool sceneDetect = false)
|
||||
{
|
||||
if (!sceneDetect) Logger.Log("Extracting video frames from input video...");
|
||||
Logger.Log("Extracting video frames from input video...");
|
||||
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
|
||||
IOUtils.CreateDir(framesDir);
|
||||
string timecodeStr = /* timecodes ? $"-copyts -r {FrameOrder.timebase} -frame_pts true" : */ "-frame_pts true";
|
||||
string scnDetect = sceneDetect ? $"\"select='gt(scene,{Config.GetFloatString("scnDetectValue")})'\"" : "";
|
||||
string mpStr = deDupe ? ((Config.GetInt("mpdecimateMode") == 0) ? mpDecDef : mpDecAggr) : "";
|
||||
string filters = FormatUtils.ConcatStrings(new string[] { divisionFilter, scnDetect, mpStr });
|
||||
string filters = FormatUtils.ConcatStrings(new string[] { divisionFilter, mpStr });
|
||||
string vf = filters.Length > 2 ? $"-vf {filters}" : "";
|
||||
string rateArg = (rate > 0) ? $" -r {rate.ToStringDot()}" : "";
|
||||
string pixFmt = alpha ? "-pix_fmt rgba" : "-pix_fmt rgb24"; // Use RGBA for GIF for alpha support
|
||||
@@ -46,7 +57,7 @@ namespace Flowframes.Media
|
||||
LogMode logMode = Interpolate.currentInputFrameCount > 50 ? LogMode.OnlyLastLine : LogMode.Hidden;
|
||||
await RunFfmpeg(args, logMode, TaskType.ExtractFrames, true);
|
||||
int amount = IOUtils.GetAmountOfFiles(framesDir, false, "*.png");
|
||||
if (!sceneDetect) Logger.Log($"Extracted {amount} {(amount == 1 ? "frame" : "frames")} from input.", false, true);
|
||||
Logger.Log($"Extracted {amount} {(amount == 1 ? "frame" : "frames")} from input.", false, true);
|
||||
await Task.Delay(1);
|
||||
if (delSrc)
|
||||
DeleteSource(inputFile);
|
||||
@@ -58,11 +69,7 @@ namespace Flowframes.Media
|
||||
Logger.Log($"Importing images from {inpath} to {outpath}.", true);
|
||||
IOUtils.CreateDir(outpath);
|
||||
string concatFile = Path.Combine(Paths.GetDataPath(), "png-concat-temp.ini");
|
||||
string concatFileContent = "";
|
||||
string[] files = IOUtils.GetFilesSorted(inpath);
|
||||
foreach (string img in files)
|
||||
concatFileContent += $"file '{img.Replace(@"\", "/")}'\n";
|
||||
File.WriteAllText(concatFile, concatFileContent);
|
||||
GetConcatFile(inpath, concatFile);
|
||||
|
||||
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
|
||||
string pixFmt = alpha ? "-pix_fmt rgba" : "-pix_fmt rgb24"; // Use RGBA for GIF for alpha support
|
||||
@@ -74,6 +81,17 @@ namespace Flowframes.Media
|
||||
DeleteSource(inpath);
|
||||
}
|
||||
|
||||
public static void GetConcatFile (string inputFilesDir, string concatFilePath)
|
||||
{
|
||||
string concatFileContent = "";
|
||||
string[] files = IOUtils.GetFilesSorted(inputFilesDir);
|
||||
|
||||
foreach (string img in files)
|
||||
concatFileContent += $"file '{img.Replace(@"\", "/")}'\n";
|
||||
|
||||
File.WriteAllText(concatFilePath, concatFileContent);
|
||||
}
|
||||
|
||||
public static string[] GetTrimArgs()
|
||||
{
|
||||
return new string[] { GetTrimArg(true), GetTrimArg(false) };
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Flowframes
|
||||
{
|
||||
public static bool hasShownError;
|
||||
|
||||
public static Process currentAiProcess;
|
||||
public static Process lastAiProcess;
|
||||
public static Stopwatch processTime = new Stopwatch();
|
||||
public static Stopwatch processTimeMulti = new Stopwatch();
|
||||
|
||||
@@ -31,11 +31,25 @@ namespace Flowframes
|
||||
|
||||
public static Dictionary<string, string> filenameMap = new Dictionary<string, string>(); // TODO: Store on disk instead for crashes?
|
||||
|
||||
public static void Kill ()
|
||||
{
|
||||
if (lastAiProcess == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
OSUtils.KillProcessTree(lastAiProcess.Id);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"Failed to kill currentAiProcess process tree: {e.Message}", true);
|
||||
}
|
||||
}
|
||||
|
||||
static void AiStarted (Process proc, int startupTimeMs, string inPath = "")
|
||||
{
|
||||
lastStartupTimeMs = startupTimeMs;
|
||||
processTime.Restart();
|
||||
currentAiProcess = proc;
|
||||
lastAiProcess = proc;
|
||||
lastInPath = string.IsNullOrWhiteSpace(inPath) ? Interpolate.current.framesFolder : inPath;
|
||||
hasShownError = false;
|
||||
}
|
||||
@@ -66,7 +80,7 @@ namespace Flowframes
|
||||
|
||||
while (Interpolate.currentlyUsingAutoEnc && Program.busy)
|
||||
{
|
||||
if (AvProcess.lastProcess != null && !AvProcess.lastProcess.HasExited && AvProcess.lastTask == AvProcess.TaskType.Encode)
|
||||
if (AvProcess.lastAvProcess != null && !AvProcess.lastAvProcess.HasExited && AvProcess.lastTask == AvProcess.TaskType.Encode)
|
||||
{
|
||||
string lastLine = AvProcess.lastOutputFfmpeg.SplitIntoLines().Last();
|
||||
Logger.Log(lastLine.Trim().TrimWhitespaces(), false, Logger.GetLastLine().Contains("frame"));
|
||||
|
||||
Reference in New Issue
Block a user