Case-sensitive rename operation (1/2)

This commit is contained in:
n00mkrad
2021-08-23 16:49:40 +02:00
parent 9162b5971b
commit 2c14fa9515
89 changed files with 12 additions and 36463 deletions

View File

@@ -1,166 +0,0 @@
using Flowframes.Data;
using Flowframes.MiscUtils;
using ImageMagick;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Flowframes.IO;
namespace Flowframes.Magick
{
class Blend
{
public static async Task BlendSceneChanges(string framesFilePath, bool setStatus = true)
{
Stopwatch sw = new Stopwatch();
sw.Restart();
int totalFrames = 0;
string keyword = "SCN:";
string fileContent = File.ReadAllText(framesFilePath);
if (!fileContent.Contains(keyword))
{
Logger.Log("Skipping BlendSceneChanges as there are no scene changes in this frames file.", true);
return;
}
string[] framesLines = fileContent.SplitIntoLines(); // Array with frame filenames
string oldStatus = Program.mainForm.GetStatus();
if (setStatus)
Program.mainForm.SetStatus("Blending scene transitions...");
int amountOfBlendFrames = (int)Interpolate.current.interpFactor - 1;
string[] frames = FrameRename.framesAreRenamed ? new string[0] : IOUtils.GetFilesSorted(Interpolate.current.framesFolder);
List<Task> runningTasks = new List<Task>();
int maxThreads = Environment.ProcessorCount * 2;
foreach (string line in framesLines)
{
try
{
if (line.Contains(keyword))
{
string trimmedLine = line.Split(keyword).Last();
string[] inputFrameNames = trimmedLine.Split('>');
string frameFrom = FrameRename.framesAreRenamed ? inputFrameNames[0] : frames[inputFrameNames[0].GetInt()];
string frameTo = FrameRename.framesAreRenamed ? inputFrameNames[1] : frames[inputFrameNames[1].GetInt()];
string img1 = Path.Combine(Interpolate.current.framesFolder, frameFrom);
string img2 = Path.Combine(Interpolate.current.framesFolder, frameTo);
string firstOutputFrameName = line.Split('/').Last().Remove("'").Split('#').First();
string ext = Path.GetExtension(firstOutputFrameName);
int firstOutputFrameNum = firstOutputFrameName.GetInt();
List<string> outputFilenames = new List<string>();
for (int blendFrameNum = 1; blendFrameNum <= amountOfBlendFrames; blendFrameNum++)
{
int outputNum = firstOutputFrameNum + blendFrameNum;
string outputPath = Path.Combine(Interpolate.current.interpFolder, outputNum.ToString().PadLeft(Padding.interpFrames, '0'));
outputPath = Path.ChangeExtension(outputPath, ext);
outputFilenames.Add(outputPath);
}
if (runningTasks.Count >= maxThreads)
{
do
{
await Task.Delay(10);
RemoveCompletedTasks(runningTasks);
} while (runningTasks.Count >= maxThreads);
}
Logger.Log($"Starting task for transition {inputFrameNames[0]} > {inputFrameNames[1]} ({runningTasks.Count}/{maxThreads} running)", true);
Task newTask = Task.Run(() => BlendImages(img1, img2, outputFilenames.ToArray()));
runningTasks.Add(newTask);
totalFrames += outputFilenames.Count;
await Task.Delay(1);
}
}
catch (Exception e)
{
Logger.Log("Failed to blend scene changes: " + e.Message, true);
}
}
while (true)
{
RemoveCompletedTasks(runningTasks);
if (runningTasks.Count < 1)
break;
await Task.Delay(10);
}
Logger.Log($"Created {totalFrames} blend frames in {FormatUtils.TimeSw(sw)} ({(totalFrames / (sw.ElapsedMilliseconds / 1000f)).ToString("0.00")} FPS)", true);
if (setStatus)
Program.mainForm.SetStatus(oldStatus);
}
static void RemoveCompletedTasks(List<Task> runningTasks)
{
foreach (Task task in new List<Task>(runningTasks))
{
if (task.IsCompleted)
runningTasks.Remove(task);
}
}
public static void BlendImages(string img1Path, string img2Path, string imgOutPath)
{
MagickImage img1 = new MagickImage(img1Path);
MagickImage img2 = new MagickImage(img2Path);
img2.Alpha(AlphaOption.Opaque);
img2.Evaluate(Channels.Alpha, EvaluateOperator.Set, new Percentage(50));
img1.Composite(img2, Gravity.Center, CompositeOperator.Over);
img1.Format = MagickFormat.Png24;
img1.Quality = 10;
img1.Write(imgOutPath);
}
public static async Task BlendImages(string img1Path, string img2Path, string[] imgOutPaths)
{
try
{
MagickImage img1 = new MagickImage(img1Path);
MagickImage img2 = new MagickImage(img2Path);
int alphaFraction = (100f / (imgOutPaths.Length + 1)).RoundToInt(); // Alpha percentage per image
int currentAlpha = alphaFraction;
foreach (string imgOutPath in imgOutPaths)
{
string outPath = imgOutPath.Trim();
MagickImage img1Inst = new MagickImage(img1);
MagickImage img2Inst = new MagickImage(img2);
img2Inst.Alpha(AlphaOption.Opaque);
img2Inst.Evaluate(Channels.Alpha, EvaluateOperator.Set, new Percentage(currentAlpha));
currentAlpha += alphaFraction;
img1Inst.Composite(img2Inst, Gravity.Center, CompositeOperator.Over);
img1Inst.Format = MagickFormat.Png24;
img1Inst.Quality = 10;
img1Inst.Write(outPath);
await Task.Delay(1);
}
}
catch (Exception e)
{
Logger.Log("BlendImages Error: " + e.Message);
}
}
}
}

View File

@@ -1,145 +0,0 @@
using Flowframes;
using Flowframes.IO;
using Flowframes.UI;
using ImageMagick;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Magick
{
class Converter
{
public static async Task Convert (string dir, MagickFormat format, int quality, string ext = "", bool print = true, bool setProgress = true)
{
var files = IOUtils.GetFilesSorted(dir);
if(print) Logger.Log($"Converting {files.Length} files in {dir}");
int counter = 0;
foreach (string file in files)
{
if (print) Logger.Log("Converting " + Path.GetFileName(file) + " to " + format.ToString().StripNumbers().ToUpper(), false, true);
MagickImage img = new MagickImage(file);
img.Format = format;
img.Quality = quality;
string outpath = file;
if (!string.IsNullOrWhiteSpace(ext)) outpath = Path.ChangeExtension(outpath, ext);
img.Write(outpath);
counter++;
if(setProgress)
Program.mainForm.SetProgress((int)Math.Round(((float)counter / files.Length) * 100f));
await Task.Delay(1);
}
}
public static async Task MakeBinary (string inputDir, string outputDir, bool print = true, bool setProgress = true)
{
try
{
var files = IOUtils.GetFilesSorted(inputDir);
if (print) Logger.Log($"Processing alpha channel...");
Directory.CreateDirectory(outputDir);
Stopwatch sw = new Stopwatch();
sw.Restart();
int counter = 0;
foreach (string file in files)
{
MagickImage img = new MagickImage(file);
img.Format = MagickFormat.Png24;
img.Quality = 10;
img.Threshold(new Percentage(75));
string outPath = Path.Combine(outputDir, Path.GetFileName(file));
img.Write(outPath);
counter++;
if (sw.ElapsedMilliseconds > 250)
{
if (setProgress)
Program.mainForm.SetProgress((int)Math.Round(((float)counter / files.Length) * 100f));
await Task.Delay(1);
sw.Restart();
}
}
}
catch (Exception e)
{
Logger.Log("MakeBinary Error: " + e.Message);
}
}
public static async Task ExtractAlpha (string inputDir, string outputDir, bool print = true, bool setProgress = true, bool removeInputAlpha = true)
{
try
{
var files = IOUtils.GetFilesSorted(inputDir);
if (print) Logger.Log($"Extracting alpha channel from images...");
Directory.CreateDirectory(outputDir);
Stopwatch sw = new Stopwatch();
sw.Restart();
int counter = 0;
foreach (string file in files)
{
MagickImage alphaImg = new MagickImage(file);
if (removeInputAlpha)
{
MagickImage rgbImg = alphaImg;
rgbImg.Format = MagickFormat.Png24;
rgbImg.Quality = 10;
MagickImage bg = new MagickImage(MagickColors.Black, rgbImg.Width, rgbImg.Height);
bg.Composite(rgbImg, CompositeOperator.Over);
rgbImg = bg;
rgbImg.Write(file);
}
alphaImg.Format = MagickFormat.Png24;
alphaImg.Quality = 10;
alphaImg.FloodFill(MagickColors.None, 0, 0); // Fill the image with a transparent background
alphaImg.InverseOpaque(MagickColors.None, MagickColors.White); // Change all the pixels that are not transparent to white.
alphaImg.ColorAlpha(MagickColors.Black); // Change the transparent pixels to black.
string outPath = Path.Combine(outputDir, Path.GetFileName(file));
alphaImg.Write(outPath);
counter++;
if (sw.ElapsedMilliseconds > 250)
{
if (setProgress)
Program.mainForm.SetProgress((int)Math.Round(((float)counter / files.Length) * 100f));
await Task.Delay(1);
sw.Restart();
}
}
}
catch (Exception e)
{
Logger.Log("ExtractAlpha Error: " + e.Message);
}
}
public static async Task Preprocess (string dir, bool setProgress = true)
{
var files = IOUtils.GetFilesSorted(dir);
Logger.Log($"Preprocessing {files} files in {dir}");
int counter = 0;
foreach (string file in files)
{
//Logger.Log("Converting " + Path.GetFileName(file) + " to " + format, false, true);
MagickImage img = new MagickImage(file);
//img.Format = MagickFormat.Bmp;
//img.Write(file);
//img = new MagickImage(file);
img.Format = MagickFormat.Png24;
img.Quality = 10;
counter++;
if (setProgress)
Program.mainForm.SetProgress((int)Math.Round(((float)counter / files.Length) * 100f));
await Task.Delay(1);
}
}
}
}

View File

@@ -1,238 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Flowframes.IO;
using ImageMagick;
using Flowframes.OS;
using Flowframes.Data;
using System.Drawing;
using Paths = Flowframes.IO.Paths;
namespace Flowframes.Magick
{
class Dedupe
{
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)
{
if (path == null || !Directory.Exists(path) || Interpolate.canceled)
return;
currentMode = Mode.Auto;
if(setStatus)
Program.mainForm.SetStatus("Running frame de-duplication");
currentThreshold = Config.GetFloat(Config.Key.dedupThresh);
Logger.Log("Running accurate frame de-duplication...");
if (currentMode == Mode.Enabled || currentMode == Mode.Auto)
await RemoveDupeFrames(path, currentThreshold, "*", testRun, false, (currentMode == Mode.Auto));
}
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();
}
public static async Task RemoveDupeFrames(string path, float threshold, string ext, bool testRun = false, bool debugLog = false, bool skipIfNoDupes = false)
{
Stopwatch sw = new Stopwatch();
sw.Restart();
Logger.Log("Removing duplicate frames - Threshold: " + threshold.ToString("0.00"));
FileInfo[] framePaths = IOUtils.GetFileInfosSorted(path, false, "*." + ext);
List<string> framesToDelete = new List<string>();
int bufferSize = await GetBufferSize();
int currentOutFrame = 1;
int currentDupeCount = 0;
int statsFramesKept = 0;
int statsFramesDeleted = 0;
int skipAfterNoDupesFrames = Config.GetInt(Config.Key.autoDedupFrames);
bool hasEncounteredAnyDupes = false;
bool skipped = false;
bool hasReachedEnd = false;
string fileContent = "";
for (int i = 0; i < framePaths.Length; i++) // Loop through frames
{
if (hasReachedEnd)
break;
string frame1 = framePaths[i].FullName;
int compareWithIndex = i + 1;
while (true) // Loop dupes
{
//compareWithIndex++;
if (compareWithIndex >= framePaths.Length)
{
hasReachedEnd = true;
break;
}
if (framesToDelete.Contains(framePaths[compareWithIndex].FullName) || !File.Exists(framePaths[compareWithIndex].FullName))
{
//Logger.Log($"Frame {compareWithIndex} was already deleted - skipping");
compareWithIndex++;
}
else
{
string frame2 = framePaths[compareWithIndex].FullName;
float diff = GetDifference(frame1, frame2);
if (diff < threshold) // Is a duped frame.
{
if (!testRun)
{
framesToDelete.Add(frame2);
if (debugLog) Logger.Log("Deduplication: Deleted " + Path.GetFileName(frame2));
hasEncounteredAnyDupes = true;
}
statsFramesDeleted++;
currentDupeCount++;
}
else
{
fileContent += $"{Path.GetFileNameWithoutExtension(framePaths[i].Name)}:{currentDupeCount}\n";
statsFramesKept++;
currentOutFrame++;
currentDupeCount = 0;
break;
}
}
}
if (sw.ElapsedMilliseconds >= 500 || (i + 1) == framePaths.Length) // Print every 0.5s (or when done)
{
sw.Restart();
Logger.Log($"Deduplication: Running de-duplication ({i}/{framePaths.Length}), deleted {statsFramesDeleted} ({(((float)statsFramesDeleted / framePaths.Length) * 100f).ToString("0")}%) duplicate frames so far...", false, true);
Program.mainForm.SetProgress((int)Math.Round(((float)i / framePaths.Length) * 100f));
if (imageCache.Count > bufferSize || (imageCache.Count > 50 && OSUtils.GetFreeRamMb() < 3500))
ClearCache();
}
// int oldIndex = -1; // TODO: Compare with 1st to fix loops?
// if (i >= framePaths.Length) // If this is the last frame, compare with 1st to avoid OutOfRange error
// {
// oldIndex = i;
// i = 0;
// }
if (i % 3 == 0)
await Task.Delay(1);
if (Interpolate.canceled) return;
if (!testRun && skipIfNoDupes && !hasEncounteredAnyDupes && skipAfterNoDupesFrames > 0 && i >= skipAfterNoDupesFrames)
{
skipped = true;
break;
}
}
foreach (string frame in framesToDelete)
IOUtils.TryDeleteIfExists(frame);
string testStr = testRun ? " [TestRun]" : "";
if (Interpolate.canceled) return;
int framesLeft = IOUtils.GetAmountOfFiles(path, false, "*" + Interpolate.current.framesExt);
int framesDeleted = framePaths.Length - framesLeft;
float percentDeleted = ((float)framesDeleted / framePaths.Length) * 100f;
string keptPercent = $"{(100f - percentDeleted).ToString("0.0")}%";
if (skipped)
Logger.Log($"Deduplication: First {skipAfterNoDupesFrames} frames did not have any duplicates - Skipping the rest!", false, true);
else
Logger.Log($"[Deduplication]{testStr} Done. Kept {framesLeft} ({keptPercent}) frames, deleted {framesDeleted} frames.", false, true);
if (statsFramesKept <= 0)
Interpolate.Cancel("No frames were left after de-duplication!\n\nTry decreasing the de-duplication threshold.");
}
static float GetDifference (string img1Path, string img2Path)
{
MagickImage img2 = GetImage(img2Path);
MagickImage img1 = GetImage(img1Path);
double err = img1.Compare(img2, ErrorMetric.Fuzz);
float errPercent = (float)err * 100f;
return errPercent;
}
static async Task<int> GetBufferSize ()
{
Size res = await 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 = 1800;
if (pixels >= 518400) bufferSize = 1400;
if (pixels >= 921600) bufferSize = 800;
if (pixels >= 2073600) bufferSize = 400;
if (pixels >= 3686400) bufferSize = 200;
if (pixels >= 8294400) bufferSize = 100;
if (pixels == 0) bufferSize = 100;
Logger.Log($"Using magick dedupe buffer size {bufferSize} for frame resolution {res.Width}x{res.Height}", true);
return bufferSize;
}
public static async Task CreateDupesFile (string framesPath, int lastFrameNum, string ext)
{
bool debug = Config.GetBool("dupeScanDebug", false);
string infoFile = Path.Combine(framesPath.GetParentDir(), "dupes.ini");
string fileContent = "";
FileInfo[] frameFiles = IOUtils.GetFileInfosSorted(framesPath, false, "*" + ext);
if (debug)
Logger.Log($"Running CreateDupesFile for '{framesPath}' ({frameFiles.Length} files), lastFrameNum = {lastFrameNum}, ext = {ext}.", true, false, "dupes");
for(int i = 0; i < frameFiles.Length; i++)
{
bool isLastItem = (i + 1) == frameFiles.Length;
int frameNum1 = frameFiles[i].Name.GetInt();
int frameNum2 = isLastItem ? lastFrameNum : frameFiles[i+1].Name.GetInt();
int diff = frameNum2 - frameNum1;
int dupes = diff - 1;
if(debug)
Logger.Log($"{(isLastItem ? "[isLastItem] " : "")}frameNum1 (frameFiles[{i}]) = {frameNum1}, frameNum2 (frameFiles[{i+1}]) = {frameNum2} => dupes = {dupes}", true, false, "dupes");
fileContent += $"{Path.GetFileNameWithoutExtension(frameFiles[i].Name)}:{dupes}\n";
}
File.WriteAllText(infoFile, fileContent);
}
}
}

View File

@@ -1,164 +0,0 @@
using Flowframes.MiscUtils;
using ImageMagick;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
namespace Flowframes.Magick
{
public enum BitmapDensity
{
/// <summary>
/// Ignore the density of the image when creating the bitmap.
/// </summary>
Ignore,
/// <summary>
/// Use the density of the image when creating the bitmap.
/// </summary>
Use,
}
public static class MagickExtensions
{
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "False positive.")]
public static Bitmap ToBitmap(this MagickImage magickImg, BitmapDensity density)
{
string mapping = "BGR";
var format = PixelFormat.Format24bppRgb;
var image = magickImg;
try
{
if (image.ColorSpace != ColorSpace.sRGB)
{
image = (MagickImage)magickImg.Clone();
image.ColorSpace = ColorSpace.sRGB;
}
if (image.HasAlpha)
{
mapping = "BGRA";
format = PixelFormat.Format32bppArgb;
}
using (var pixels = image.GetPixelsUnsafe())
{
var bitmap = new Bitmap(image.Width, image.Height, format);
var data = bitmap.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadWrite, format);
var destination = data.Scan0;
for (int y = 0; y < image.Height; y++)
{
byte[] bytes = pixels.ToByteArray(0, y, image.Width, 1, mapping);
Marshal.Copy(bytes, 0, destination, bytes.Length);
destination = new IntPtr(destination.ToInt64() + data.Stride);
}
bitmap.UnlockBits(data);
SetBitmapDensity(magickImg, bitmap, density);
return bitmap;
}
}
finally
{
if (!ReferenceEquals(image, magickImg))
image.Dispose();
}
}
public static Bitmap ToBitmap(this MagickImage imageMagick) => ToBitmap(imageMagick, BitmapDensity.Ignore);
public static Bitmap ToBitmap(this MagickImage imageMagick, ImageFormat imageFormat) => ToBitmap(imageMagick, imageFormat, BitmapDensity.Ignore);
public static Bitmap ToBitmap(this MagickImage imageMagick, ImageFormat imageFormat, BitmapDensity bitmapDensity)
{
imageMagick.Format = InternalMagickFormatInfo.GetFormat(imageFormat);
MemoryStream memStream = new MemoryStream();
imageMagick.Write(memStream);
memStream.Position = 0;
/* Do not dispose the memStream, the bitmap owns it. */
var bitmap = new Bitmap(memStream);
SetBitmapDensity(imageMagick, bitmap, bitmapDensity);
return bitmap;
}
public static void FromBitmap(this MagickImage imageMagick, Bitmap bitmap)
{
using (MemoryStream memStream = new MemoryStream())
{
if (IsSupportedImageFormat(bitmap.RawFormat))
bitmap.Save(memStream, bitmap.RawFormat);
else
bitmap.Save(memStream, ImageFormat.Bmp);
memStream.Position = 0;
imageMagick.Read(memStream);
}
}
private static bool IsSupportedImageFormat(ImageFormat format)
{
return
format.Guid.Equals(ImageFormat.Bmp.Guid) ||
format.Guid.Equals(ImageFormat.Gif.Guid) ||
format.Guid.Equals(ImageFormat.Icon.Guid) ||
format.Guid.Equals(ImageFormat.Jpeg.Guid) ||
format.Guid.Equals(ImageFormat.Png.Guid) ||
format.Guid.Equals(ImageFormat.Tiff.Guid);
}
private static void SetBitmapDensity(MagickImage imageMagick, Bitmap bitmap, BitmapDensity bitmapDensity)
{
if (bitmapDensity == BitmapDensity.Use)
{
var dpi = GetDpi(imageMagick, bitmapDensity);
bitmap.SetResolution((float)dpi.X, (float)dpi.Y);
}
}
private static Density GetDpi(MagickImage imageMagick, BitmapDensity bitmapDensity)
{
if (bitmapDensity == BitmapDensity.Ignore || (imageMagick.Density.Units == DensityUnit.Undefined && imageMagick.Density.X == 0 && imageMagick.Density.Y == 0))
return new Density(96);
return imageMagick.Density.ChangeUnits(DensityUnit.PixelsPerInch);
}
}
public class InternalMagickFormatInfo
{
internal static MagickFormat GetFormat(ImageFormat format)
{
if (format == ImageFormat.Bmp || format == ImageFormat.MemoryBmp)
return MagickFormat.Bmp;
else if (format == ImageFormat.Gif)
return MagickFormat.Gif;
else if (format == ImageFormat.Icon)
return MagickFormat.Icon;
else if (format == ImageFormat.Jpeg)
return MagickFormat.Jpeg;
else if (format == ImageFormat.Png)
return MagickFormat.Png;
else if (format == ImageFormat.Tiff)
return MagickFormat.Tiff;
else
throw new NotSupportedException("Unsupported image format: " + format.ToString());
}
}
}

View File

@@ -1,94 +0,0 @@
using Flowframes.IO;
using ImageMagick;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Magick
{
class SceneDetect
{
public static async Task RunSceneDetection (string path)
{
string outFolder = path + "-analyzed";
Directory.CreateDirectory(outFolder);
string ext = "png";
FileInfo[] frames = IOUtils.GetFileInfosSorted(path, false, "*." + ext);
for (int i = 1; i < frames.Length; i++)
{
FileInfo frame = frames[i];
FileInfo lastFrame = frames[i - 1];
Task.Run(() => ProcessFrame(frame, lastFrame, outFolder));
}
}
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.Count >= 30)
ClearCache();
if (!imageCache.ContainsKey(path))
imageCache.Add(path, new MagickImage(path));
return imageCache[path];
}
public static void ClearCache()
{
imageCache.Clear();
}
static async Task ProcessFrame (FileInfo frame, FileInfo lastFrame, string outFolder)
{
MagickImage prevFrame = GetImage(lastFrame.FullName, false);
MagickImage currFrame = GetImage(frame.FullName, false);
Size originalSize = new Size(currFrame.Width, currFrame.Height);
int downscaleHeight = 144;
prevFrame.Scale(currFrame.Width / (currFrame.Height / downscaleHeight), downscaleHeight);
currFrame.Scale(currFrame.Width / (currFrame.Height / downscaleHeight), downscaleHeight);
double errNormalizedCrossCorrelation = currFrame.Compare(prevFrame, ErrorMetric.NormalizedCrossCorrelation);
double errRootMeanSquared = currFrame.Compare(prevFrame, ErrorMetric.RootMeanSquared);
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 (rmsNccTrigger) str += "\n\nRMS -> NCC DOUBLE SCENE CHANGE TRIGGER!";
if (nccRmsTrigger) str += "\n\nNCC -> RMS DOUBLE SCENE CHANGE TRIGGER!";
currFrame.Scale(originalSize.Width / 2, originalSize.Height / 2);
new Drawables()
.FontPointSize(12)
.Font("Consolas", FontStyleType.Normal, FontWeight.Bold, FontStretch.Normal)
.FillColor(MagickColors.Red)
.TextAlignment(TextAlignment.Left)
.Text(1, 10, str)
.Draw(currFrame);
currFrame.Write(Path.Combine(outFolder, frame.Name));
prevFrame.Dispose();
currFrame.Dispose();
await Task.Delay(1);
}
}
}