2020-11-23 16:51:05 +01:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
2020-11-26 20:17:18 +01:00
|
|
|
|
using System.Diagnostics;
|
2020-11-23 16:51:05 +01:00
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using Flowframes.IO;
|
|
|
|
|
|
using Flowframes.Main;
|
2020-11-26 20:17:18 +01:00
|
|
|
|
using Flowframes.UI;
|
|
|
|
|
|
using Microsoft.VisualBasic.Devices;
|
2020-11-23 16:51:05 +01:00
|
|
|
|
using ImageMagick;
|
2020-11-26 20:17:18 +01:00
|
|
|
|
using Flowframes.OS;
|
2020-12-02 17:23:24 +01:00
|
|
|
|
using Flowframes.Data;
|
2020-12-22 23:45:07 +01:00
|
|
|
|
using System.Drawing;
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
|
|
|
|
|
namespace Flowframes.Magick
|
|
|
|
|
|
{
|
2020-11-24 02:23:19 +01:00
|
|
|
|
class MagickDedupe
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
|
|
|
|
|
public enum Mode { None, Info, Enabled, Auto }
|
|
|
|
|
|
public static Mode currentMode;
|
|
|
|
|
|
public static float currentThreshold;
|
|
|
|
|
|
|
|
|
|
|
|
public static async Task Run(string path, bool testRun = false, bool setStatus = true)
|
|
|
|
|
|
{
|
2020-12-13 23:44:23 +01:00
|
|
|
|
if (path == null || !Directory.Exists(path) || Interpolate.canceled)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2020-12-07 12:34:12 +01:00
|
|
|
|
currentMode = Mode.Auto;
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
2020-12-13 23:44:23 +01:00
|
|
|
|
if(setStatus)
|
|
|
|
|
|
Program.mainForm.SetStatus("Running frame de-duplication");
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
|
|
|
|
|
currentThreshold = Config.GetFloat("dedupThresh");
|
2020-12-07 12:34:12 +01:00
|
|
|
|
Logger.Log("Running accurate frame de-duplication...");
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
|
|
|
|
|
if (currentMode == Mode.Enabled || currentMode == Mode.Auto)
|
2020-12-22 23:45:07 +01:00
|
|
|
|
await RemoveDupeFrames(path, currentThreshold, "png", testRun, false, (currentMode == Mode.Auto));
|
2020-11-23 16:51:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-11-26 20:17:18 +01:00
|
|
|
|
public static Dictionary<string, MagickImage> imageCache = new Dictionary<string, MagickImage>();
|
|
|
|
|
|
static MagickImage GetImage(string path)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool allowCaching = true;
|
|
|
|
|
|
|
|
|
|
|
|
if (!allowCaching)
|
|
|
|
|
|
return new MagickImage(path);
|
|
|
|
|
|
|
|
|
|
|
|
if (!imageCache.ContainsKey(path))
|
|
|
|
|
|
imageCache.Add(path, new MagickImage(path));
|
|
|
|
|
|
|
|
|
|
|
|
return imageCache[path];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static void ClearCache ()
|
|
|
|
|
|
{
|
|
|
|
|
|
imageCache.Clear();
|
|
|
|
|
|
}
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
|
|
|
|
|
public static async Task RemoveDupeFrames(string path, float threshold, string ext, bool testRun = false, bool debugLog = false, bool skipIfNoDupes = false)
|
|
|
|
|
|
{
|
2020-11-26 20:17:18 +01:00
|
|
|
|
Stopwatch sw = new Stopwatch();
|
|
|
|
|
|
sw.Restart();
|
2020-11-23 16:51:05 +01:00
|
|
|
|
Logger.Log("Removing duplicate frames - Threshold: " + threshold.ToString("0.00"));
|
|
|
|
|
|
|
2020-12-22 19:52:37 +01:00
|
|
|
|
FileInfo[] framePaths = IOUtils.GetFileInfosSorted(path, false, "*." + ext);
|
|
|
|
|
|
List<string> framesToDelete = new List<string>();
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
2020-12-22 23:45:07 +01:00
|
|
|
|
int bufferSize = GetBufferSize();
|
|
|
|
|
|
|
2020-11-23 16:51:05 +01:00
|
|
|
|
int currentOutFrame = 1;
|
|
|
|
|
|
int currentDupeCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
int statsFramesKept = 0;
|
|
|
|
|
|
int statsFramesDeleted = 0;
|
|
|
|
|
|
|
|
|
|
|
|
int skipAfterNoDupesFrames = Config.GetInt("autoDedupFrames");
|
|
|
|
|
|
bool hasEncounteredAnyDupes = false;
|
|
|
|
|
|
bool skipped = false;
|
|
|
|
|
|
|
2020-12-22 19:52:37 +01:00
|
|
|
|
bool hasReachedEnd = false;
|
|
|
|
|
|
|
2021-01-02 16:20:21 +01:00
|
|
|
|
string infoFile = Path.Combine(path.GetParentDir(), $"dupes.ini");
|
|
|
|
|
|
string fileContent = "";
|
|
|
|
|
|
|
2020-12-22 19:52:37 +01:00
|
|
|
|
for (int i = 0; i < framePaths.Length; i++) // Loop through frames
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
2020-12-22 19:52:37 +01:00
|
|
|
|
if (hasReachedEnd)
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2020-11-23 16:51:05 +01:00
|
|
|
|
string frame1 = framePaths[i].FullName;
|
2020-12-22 19:52:37 +01:00
|
|
|
|
//if (!File.Exists(framePaths[i].FullName)) // Skip if file doesn't exist (already deleted / used to be a duped frame)
|
|
|
|
|
|
// continue;
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
2020-12-22 19:52:37 +01:00
|
|
|
|
int compareWithIndex = i + 1;
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
2020-12-22 19:52:37 +01:00
|
|
|
|
while (true) // Loop dupes
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
2020-12-22 19:52:37 +01:00
|
|
|
|
//compareWithIndex++;
|
|
|
|
|
|
if (compareWithIndex >= framePaths.Length)
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
2020-12-22 19:52:37 +01:00
|
|
|
|
hasReachedEnd = true;
|
2020-11-23 16:51:05 +01:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-12-22 19:52:37 +01:00
|
|
|
|
if (framesToDelete.Contains(framePaths[compareWithIndex].FullName) || !File.Exists(framePaths[compareWithIndex].FullName))
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
2020-12-22 23:45:07 +01:00
|
|
|
|
//Logger.Log($"Frame {compareWithIndex} was already deleted - skipping");
|
2020-12-22 19:52:37 +01:00
|
|
|
|
compareWithIndex++;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
//if (compareWithIndex >= framePaths.Length)
|
|
|
|
|
|
// hasReachedEnd = true;
|
|
|
|
|
|
|
|
|
|
|
|
string frame2 = framePaths[compareWithIndex].FullName;
|
|
|
|
|
|
// if (oldIndex >= 0)
|
|
|
|
|
|
// i = oldIndex;
|
|
|
|
|
|
|
|
|
|
|
|
float diff = GetDifference(frame1, frame2);
|
|
|
|
|
|
|
|
|
|
|
|
string delStr = "Keeping";
|
|
|
|
|
|
if (diff < threshold) // Is a duped frame.
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!testRun)
|
|
|
|
|
|
{
|
|
|
|
|
|
delStr = "Deleting";
|
|
|
|
|
|
//File.Delete(frame2);
|
|
|
|
|
|
framesToDelete.Add(frame2);
|
|
|
|
|
|
if (debugLog) Logger.Log("[FrameDedup] Deleted " + Path.GetFileName(frame2));
|
|
|
|
|
|
hasEncounteredAnyDupes = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
statsFramesDeleted++;
|
|
|
|
|
|
currentDupeCount++;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2021-01-02 16:20:21 +01:00
|
|
|
|
fileContent += $"{Path.GetFileNameWithoutExtension(framePaths[i].Name)}:{currentDupeCount}\n";
|
2020-12-22 19:52:37 +01:00
|
|
|
|
statsFramesKept++;
|
|
|
|
|
|
currentOutFrame++;
|
|
|
|
|
|
currentDupeCount = 0;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-02 16:20:21 +01:00
|
|
|
|
if (sw.ElapsedMilliseconds >= 1000 || (i+1) == framePaths.Length) // Print every 1s (or when done)
|
2020-12-22 19:52:37 +01:00
|
|
|
|
{
|
2020-12-22 23:45:07 +01:00
|
|
|
|
sw.Restart();
|
2021-01-02 16:20:21 +01:00
|
|
|
|
Logger.Log($"[FrameDedup] Difference from {Path.GetFileName(frame1)} to {Path.GetFileName(frame2)}: {diff.ToString("0.00")}% - {delStr}.", false, true);
|
2020-12-22 19:52:37 +01:00
|
|
|
|
Program.mainForm.SetProgress((int)Math.Round(((float)i / framePaths.Length) * 100f));
|
2020-12-22 23:45:07 +01:00
|
|
|
|
if (imageCache.Count > bufferSize || (imageCache.Count > 50 && OSUtils.GetFreeRamMb() < 2500))
|
2020-12-22 19:52:37 +01:00
|
|
|
|
ClearCache();
|
|
|
|
|
|
}
|
2020-11-23 16:51:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-12-22 23:45:07 +01:00
|
|
|
|
// int oldIndex = -1; // TODO: Compare with 1st to fix loops?
|
2020-12-22 19:52:37 +01:00
|
|
|
|
// if (i >= framePaths.Length) // If this is the last frame, compare with 1st to avoid OutOfRange error
|
|
|
|
|
|
// {
|
|
|
|
|
|
// oldIndex = i;
|
|
|
|
|
|
// i = 0;
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
2020-11-26 20:17:18 +01:00
|
|
|
|
if(i % 5 == 0)
|
|
|
|
|
|
await Task.Delay(1);
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
2020-11-25 14:04:31 +01:00
|
|
|
|
if (Interpolate.canceled) return;
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
2020-12-07 12:34:12 +01:00
|
|
|
|
if (!testRun && skipIfNoDupes && !hasEncounteredAnyDupes && skipAfterNoDupesFrames > 0 && i >= skipAfterNoDupesFrames)
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
|
|
|
|
|
skipped = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-02 16:20:21 +01:00
|
|
|
|
File.WriteAllText(infoFile, fileContent);
|
|
|
|
|
|
|
2020-12-22 23:45:07 +01:00
|
|
|
|
foreach (string frame in framesToDelete)
|
|
|
|
|
|
IOUtils.TryDeleteIfExists(frame);
|
|
|
|
|
|
|
2020-12-15 14:46:33 +01:00
|
|
|
|
string testStr = testRun ? " [TestRun]" : "";
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
2020-11-25 14:04:31 +01:00
|
|
|
|
if (Interpolate.canceled) return;
|
2020-12-22 23:45:07 +01:00
|
|
|
|
|
2021-01-02 16:20:21 +01:00
|
|
|
|
int framesLeft = IOUtils.GetAmountOfFiles(path, false, $"*.png");
|
|
|
|
|
|
int framesDeleted = framePaths.Length - framesLeft;
|
|
|
|
|
|
float percentDeleted = ((float)framesDeleted / framePaths.Length) * 100f;
|
|
|
|
|
|
string keptPercent = $"{(100f - percentDeleted).ToString("0.0")}%";
|
|
|
|
|
|
|
2020-11-23 16:51:05 +01:00
|
|
|
|
if (skipped)
|
2020-12-07 12:34:12 +01:00
|
|
|
|
Logger.Log($"[FrameDedup] First {skipAfterNoDupesFrames} frames did not have any duplicates - Skipping the rest!", false, true);
|
2020-11-23 16:51:05 +01:00
|
|
|
|
else
|
2021-01-02 16:20:21 +01:00
|
|
|
|
Logger.Log($"[FrameDedup]{testStr} Done. Kept {framesLeft} ({keptPercent}) frames, deleted {framesDeleted} frames.", false, true);
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
2020-11-25 12:40:17 +01:00
|
|
|
|
if (statsFramesKept <= 0)
|
2020-12-22 23:45:07 +01:00
|
|
|
|
Interpolate.Cancel("No frames were left after de-duplication!\n\nTry decreasing the de-duplication threshold.");
|
2020-11-23 16:51:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-12-22 19:52:37 +01:00
|
|
|
|
static float GetDifference (string img1Path, string img2Path)
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
2020-12-22 19:52:37 +01:00
|
|
|
|
MagickImage img2 = GetImage(img2Path);
|
|
|
|
|
|
MagickImage img1 = GetImage(img1Path);
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
2020-12-22 19:52:37 +01:00
|
|
|
|
double err = img1.Compare(img2, ErrorMetric.Fuzz);
|
|
|
|
|
|
float errPercent = (float)err * 100f;
|
|
|
|
|
|
return errPercent;
|
2020-11-23 16:51:05 +01:00
|
|
|
|
}
|
2020-12-22 23:45:07 +01:00
|
|
|
|
|
|
|
|
|
|
static int GetBufferSize ()
|
|
|
|
|
|
{
|
|
|
|
|
|
Size res = Interpolate.current.GetScaledRes();
|
|
|
|
|
|
long pixels = res.Width * res.Height; // 4K = 8294400, 1440p = 3686400, 1080p = 2073600, 720p = 921600, 540p = 518400, 360p = 230400
|
|
|
|
|
|
int bufferSize = 100;
|
|
|
|
|
|
if (pixels < 518400) bufferSize = 2800;
|
|
|
|
|
|
if (pixels >= 518400) return 2000;
|
|
|
|
|
|
if (pixels >= 921600) return 1200;
|
|
|
|
|
|
if (pixels >= 2073600) return 800;
|
|
|
|
|
|
if (pixels >= 3686400) return 400;
|
|
|
|
|
|
if (pixels >= 8294400) return 200;
|
|
|
|
|
|
Logger.Log($"Using magick dedupe buffer size {bufferSize} for frame resolution {res.Width}x{res.Height}", true);
|
|
|
|
|
|
return bufferSize;
|
|
|
|
|
|
}
|
2020-11-23 16:51:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|