Scene detection now works with frame sequence input, cleanup

This commit is contained in:
N00MKRAD
2021-03-10 20:45:48 +01:00
parent da52ebcdb9
commit eebba121c9
8 changed files with 93 additions and 77 deletions

View File

@@ -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;

View File

@@ -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!";

View File

@@ -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)

View File

@@ -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"))
{

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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) };

View File

@@ -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"));