Basic support for blended scene changes

This commit is contained in:
N00MKRAD 2021-03-19 19:34:48 +01:00
parent 0e95e62979
commit ddf68715fb
10 changed files with 202 additions and 38 deletions

View File

@ -180,5 +180,10 @@ namespace Flowframes
else
return f.ToString(format).Replace(",", ".");
}
public static string[] Split(this string str, string trimStr)
{
return str.Split(new string[] { trimStr }, StringSplitOptions.None);
}
}
}

View File

@ -234,6 +234,7 @@
</Compile>
<Compile Include="IO\ConfigParser.cs" />
<Compile Include="IO\ModelDownloader.cs" />
<Compile Include="Magick\Blend.cs" />
<Compile Include="Magick\SceneDetect.cs" />
<Compile Include="Main\AutoEncode.cs" />
<Compile Include="Main\BatchProcessing.cs" />

18
Code/Form1.Designer.cs generated
View File

@ -124,6 +124,7 @@
this.htButton1 = new HTAlt.WinForms.HTButton();
this.runStepBtn = new System.Windows.Forms.Button();
this.stepSelector = new System.Windows.Forms.ComboBox();
this.scnDetectTestBtn = new HTAlt.WinForms.HTButton();
this.panel1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.pictureBox4)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.pictureBox3)).BeginInit();
@ -893,6 +894,7 @@
//
this.interpOptsTab.AllowDrop = true;
this.interpOptsTab.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.interpOptsTab.Controls.Add(this.scnDetectTestBtn);
this.interpOptsTab.Controls.Add(this.inputInfo);
this.interpOptsTab.Controls.Add(this.label1);
this.interpOptsTab.Controls.Add(this.browseOutBtn);
@ -1430,6 +1432,21 @@
this.stepSelector.Size = new System.Drawing.Size(203, 24);
this.stepSelector.TabIndex = 73;
//
// scnDetectTestBtn
//
this.scnDetectTestBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.scnDetectTestBtn.FlatAppearance.BorderSize = 0;
this.scnDetectTestBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.scnDetectTestBtn.ForeColor = System.Drawing.Color.White;
this.scnDetectTestBtn.Location = new System.Drawing.Point(692, 229);
this.scnDetectTestBtn.Name = "scnDetectTestBtn";
this.scnDetectTestBtn.Size = new System.Drawing.Size(206, 23);
this.scnDetectTestBtn.TabIndex = 38;
this.scnDetectTestBtn.Text = "Scn Detect Test";
this.scnDetectTestBtn.UseVisualStyleBackColor = false;
this.scnDetectTestBtn.Visible = false;
this.scnDetectTestBtn.Click += new System.EventHandler(this.scnDetectTestBtn_Click);
//
// Form1
//
this.AllowDrop = true;
@ -1594,6 +1611,7 @@
private System.Windows.Forms.Panel mpDedupePanel;
private System.Windows.Forms.ComboBox mpdecimateMode;
private System.Windows.Forms.LinkLabel linkLabel1;
private HTAlt.WinForms.HTButton scnDetectTestBtn;
}
}

View File

@ -61,6 +61,16 @@ namespace Flowframes
private void Form1_Shown(object sender, EventArgs e)
{
Checks();
if (Debugger.IsAttached)
{
Logger.Log("Debugger is attached - Flowframes seems to be running within VS.");
scnDetectTestBtn.Visible = true;
}
// string path = @"F:\AI\Testing\RIFE\temp\ScnDetectTests\blendTest\";
// string[] blendImgs = new string[] { path + "1-blend1.png", path + "1-blend2.png", path + "1-blend3.png", path + "1-blend4.png", path + "1-blend5.png", path + "1-blend6.png", path + "1-blend7.png" };
// Magick.Blend.BlendImages(path + "1.png", path + "2.png", blendImgs);
}
async Task Checks()
@ -526,5 +536,10 @@ namespace Flowframes
}
#endregion
private void scnDetectTestBtn_Click(object sender, EventArgs e)
{
Magick.SceneDetect.RunSceneDetection(inputTbox.Text.Trim());
}
}
}

106
Code/Magick/Blend.cs Normal file
View File

@ -0,0 +1,106 @@
using Flowframes.Data;
using Flowframes.IO;
using Flowframes.MiscUtils;
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 Blend
{
public static async Task BlendSceneChanges(string framesFilePath)
{
Stopwatch sw = new Stopwatch();
sw.Restart();
int totalFrames = 0;
string keyword = "SCN:";
string[] framesLines = IOUtils.ReadLines(framesFilePath); // Array with frame filenames
int amountOfBlendFrames = (int)Interpolate.current.interpFactor - 1;
//Logger.Log($"BlendSceneChanges: Blending with {amountOfBlendFrames} frames", true);
foreach (string line in framesLines)
{
try
{
if (line.Contains(keyword))
{
string trimmedLine = line.Split(keyword).Last();
string[] inputFrameNames = trimmedLine.Split('>');
string img1 = Path.Combine(Interpolate.current.framesFolder, inputFrameNames[0]);
string img2 = Path.Combine(Interpolate.current.framesFolder, inputFrameNames[1]);
string firstOutputFrameName = line.Split('/').Last().Remove("'").Split('#').First();
string ext = Path.GetExtension(firstOutputFrameName);
int firstOutputFrameNum = firstOutputFrameName.GetInt();
List<string> outputFilenames = new List<string>();
//Logger.Log("BlendSceneChanges: 1 = " + img1, true);
//Logger.Log("BlendSceneChanges: 2 = " + img2, true);
for (int blendFrameNum = 1; blendFrameNum <= amountOfBlendFrames; blendFrameNum++)
{
int outputNum = firstOutputFrameNum + blendFrameNum + 1;
string outputPath = Path.Combine(Interpolate.current.interpFolder, outputNum.ToString().PadLeft(Padding.interpFrames, '0'));
outputPath = Path.ChangeExtension(outputPath, ext);
outputFilenames.Add(outputPath);
//Logger.Log("BlendSceneChanges: Added output path " + outputPath, true);
}
BlendImages(img1, img2, outputFilenames.ToArray());
totalFrames += outputFilenames.Count;
await Task.Delay(1);
}
}
catch (Exception e)
{
Logger.Log("Failed to blend scene changes: " + e.Message, true);
}
}
Logger.Log($"Created {totalFrames} blend frames in {FormatUtils.TimeSw(sw)} ({(totalFrames / (sw.ElapsedMilliseconds / 1000f)).ToString("0.00")} FPS)", true);
}
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 void BlendImages (string img1Path, string img2Path, string[] imgOutPaths)
{
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)
{
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(imgOutPath);
}
}
}
}

View File

@ -19,10 +19,16 @@ namespace Flowframes.Main
{
class CreateVideo
{
static string currentOutFile; // Keeps track of the out file, in case it gets renamed (FPS limiting, looping, etc) before finishing export
public static async Task Export(string path, string outFolder, I.OutMode mode, bool stepByStep)
{
if(Config.GetInt("sceneChangeFillMode") == 1)
{
string frameFile = Path.Combine(I.current.tempFolder, Paths.GetFrameOrderFilename(I.current.interpFactor));
await Blend.BlendSceneChanges(frameFile);
}
if (!mode.ToString().ToLower().Contains("vid")) // Copy interp frames out of temp folder and skip video export for image seq export
{
try
@ -30,7 +36,7 @@ namespace Flowframes.Main
string folder = Path.Combine(outFolder, IOUtils.GetCurrentExportFilename(false, false));
await CopyOutputFrames(path, folder, stepByStep);
}
catch(Exception e)
catch (Exception e)
{
Logger.Log("Failed to move interp frames folder: " + e.Message);
}
@ -54,7 +60,7 @@ namespace Flowframes.Main
bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt("maxFpsMode") == 0;
if(!dontEncodeFullFpsVid)
if (!dontEncodeFullFpsVid)
await Encode(mode, path, Path.Combine(outFolder, IOUtils.GetCurrentExportFilename(false, true)), I.current.outFps);
if (fpsLimit)
@ -67,7 +73,7 @@ namespace Flowframes.Main
}
}
static async Task CopyOutputFrames (string framesPath, string folderName, bool dontMove)
static async Task CopyOutputFrames(string framesPath, string folderName, bool dontMove)
{
Program.mainForm.SetStatus("Copying output frames...");
string copyPath = Path.Combine(I.current.outPath, folderName);
@ -82,7 +88,7 @@ namespace Flowframes.Main
for (int idx = 1; idx <= vfrLines.Length; idx++)
{
string line = vfrLines[idx-1];
string line = vfrLines[idx - 1];
string inFilename = line.RemoveComments().Split('/').Last().Remove("'").Trim();
string framePath = Path.Combine(framesPath, inFilename);
string outFilename = Path.Combine(copyPath, idx.ToString().PadLeft(Padding.interpFrames, '0')) + Path.GetExtension(framePath);
@ -103,7 +109,7 @@ namespace Flowframes.Main
static async Task Encode(I.OutMode mode, string framesPath, string outPath, float fps, float resampleFps = -1)
{
currentOutFile = outPath;
string currentOutFile = outPath;
string vfrFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(I.current.interpFactor));
if (!File.Exists(vfrFile))
@ -138,7 +144,7 @@ namespace Flowframes.Main
try
{
DirectoryInfo chunksDir = new DirectoryInfo(chunksFolder);
foreach(DirectoryInfo dir in chunksDir.GetDirectories())
foreach (DirectoryInfo dir in chunksDir.GetDirectories())
{
string suffix = dir.Name.Replace("chunks", "");
string tempConcatFile = Path.Combine(tempFolder, $"chunks-concat{suffix}.ini");
@ -179,7 +185,7 @@ namespace Flowframes.Main
bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt("maxFpsMode") == 0;
if(!dontEncodeFullFpsVid)
if (!dontEncodeFullFpsVid)
await FfmpegEncode.FramesToVideoConcat(vfrFile, outPath, mode, I.current.outFps, AvProcess.LogMode.Hidden, true); // Encode
if (fpsLimit)

View File

@ -45,19 +45,19 @@ namespace Flowframes.Main
static Dictionary<string, int> dupesDict = new Dictionary<string, int>();
static void LoadDupesFile (string path)
static void LoadDupesFile(string path)
{
dupesDict.Clear();
if (!File.Exists(path)) return;
string[] dupesFileLines = IOUtils.ReadLines(path);
foreach(string line in dupesFileLines)
foreach (string line in dupesFileLines)
{
string[] values = line.Split(':');
dupesDict.Add(values[0], values[1].GetInt());
}
}
public static async Task CreateEncFile (string framesPath, bool loopEnabled, float interpFactor, bool notFirstRun)
public static async Task CreateEncFile(string framesPath, bool loopEnabled, float interpFactor, bool notFirstRun)
{
if (Interpolate.canceled) return;
Logger.Log($"Generating frame order information for {interpFactor}x...", false, true);
@ -90,7 +90,7 @@ namespace Flowframes.Main
int linesPerTask = 400 / (int)interpFactor;
int num = 0;
for (int i = 0; i < frameFilesWithoutLast.Length; i+= linesPerTask)
for (int i = 0; i < frameFilesWithoutLast.Length; i += linesPerTask)
{
tasks.Add(GenerateFrameLines(num, i, linesPerTask, (int)interpFactor, loopEnabled, sceneDetection, debug));
num++;
@ -104,7 +104,7 @@ namespace Flowframes.Main
lastOutFileCount++;
fileContent += $"file '{Paths.interpDir}/{lastOutFileCount.ToString().PadLeft(Padding.interpFrames, '0')}.{ext}'"; // Last frame (source)
if(loop)
if (loop)
fileContent = fileContent.Remove(fileContent.LastIndexOf("\n"));
File.WriteAllText(vfrFile, fileContent);
@ -125,14 +125,14 @@ namespace Flowframes.Main
}
}
static async Task GenerateFrameLines (int number, int startIndex, int count, int factor, bool loopEnabled, bool sceneDetection, bool debug)
static async Task GenerateFrameLines(int number, int startIndex, int count, int factor, bool loopEnabled, bool sceneDetection, bool debug)
{
int totalFileCount = (startIndex) * factor;
int interpFramesAmount = factor;
string ext = InterpolateUtils.GetOutExt();
string fileContent = "";
for (int i = startIndex; i < (startIndex + count); i++)
{
if (Interpolate.canceled) return;
@ -152,17 +152,30 @@ namespace Flowframes.Main
{
if (discardThisFrame) // If frame is scene cut frame
{
totalFileCount++;
int lastNum = totalFileCount;
fileContent = WriteFrameWithDupes(dupesAmount, fileContent, totalFileCount, ext, debug, $"[In: {inputFilenameNoExt}] [{((frm == 0) ? " Source " : $"Interp {frm}")}] [DiscardNext]");
string frameBeforeScn = Path.GetFileName((frameFiles[i].Name.GetInt() + 1).ToString().PadLeft(Padding.inputFramesRenamed, '0')) + frameFiles[i].Extension;
string frameAfterScn = Path.GetFileName((frameFiles[i + 1].Name.GetInt() + 1).ToString().PadLeft(Padding.inputFramesRenamed, '0')) + frameFiles[i + 1].Extension;
string scnChangeNote = $"SCN:{frameBeforeScn}>{frameAfterScn}";
fileContent = WriteFrameWithDupes(dupesAmount, fileContent, totalFileCount, ext, debug, $"[In: {inputFilenameNoExt}] [{((frm == 0) ? " Source " : $"Interp {frm}")}]", scnChangeNote);
for (int dupeCount = 1; dupeCount < interpFramesAmount; dupeCount++)
if (Config.GetInt("sceneChangeFillMode") == 0) // Duplicate last frame
{
totalFileCount++;
fileContent = WriteFrameWithDupes(dupesAmount, fileContent, lastNum, ext, debug, $"[In: {inputFilenameNoExt}] [DISCARDED]");
}
int lastNum = totalFileCount;
frm = interpFramesAmount;
for (int dupeCount = 1; dupeCount < interpFramesAmount; dupeCount++)
{
totalFileCount++;
fileContent = WriteFrameWithDupes(dupesAmount, fileContent, lastNum, ext, debug, $"[In: {inputFilenameNoExt}] [DISCARDED]");
}
frm = interpFramesAmount;
}
else
{
totalFileCount++;
fileContent = WriteFrameWithDupes(dupesAmount, fileContent, totalFileCount, ext, debug, $"[In: {inputFilenameNoExt}] [DISCARDED - BLEND]");
frm++;
}
}
else
{
@ -172,16 +185,16 @@ namespace Flowframes.Main
}
}
if(totalFileCount > lastOutFileCount)
if (totalFileCount > lastOutFileCount)
lastOutFileCount = totalFileCount;
frameFileContents[number] = fileContent;
}
static string WriteFrameWithDupes (int dupesAmount, string fileContent, int frameNum, string ext, bool debug, string note = "")
static string WriteFrameWithDupes(int dupesAmount, string fileContent, int frameNum, string ext, bool debug, string debugNote = "", string forcedNote = "")
{
for (int writtenDupes = -1; writtenDupes < dupesAmount; writtenDupes++) // Write duplicates
fileContent += $"file '{Paths.interpDir}/{frameNum.ToString().PadLeft(Padding.interpFrames, '0')}.{ext}'{(debug ? ($" # Dupe {(writtenDupes+1).ToString("000")} {note}").Replace("Dupe 000", " ") : "" )}\n";
fileContent += $"file '{Paths.interpDir}/{frameNum.ToString().PadLeft(Padding.interpFrames, '0')}.{ext}' # {(debug ? ($"Dupe {(writtenDupes + 1).ToString("000")} {debugNote}").Replace("Dupe 000", " ") : "")}{forcedNote}\n";
return fileContent;
}

View File

@ -334,7 +334,7 @@ namespace Flowframes.Main
public static void PathAsciiCheck(string path, string pathTitle)
{
if (IOUtils.HasBadChars(path) || OSUtils.HasNonAsciiChars(path))
ShowMessage($"Warning: Your {pathTitle} includes special characters. This might cause problems.");
ShowMessage($"Warning: Your {pathTitle} includes special characters. This might cause problems. If you get an error, try removing those symbols.");
}
public static bool CheckAiAvailable(AI ai)

View File

@ -46,13 +46,20 @@ namespace Flowframes
{
if (busy)
{
string drivePath = Interpolate.current.tempFolder.Substring(0, 2);
long mb = IOUtils.GetDiskSpace(Interpolate.current.tempFolder);
try
{
string drivePath = Interpolate.current.tempFolder.Substring(0, 2);
long mb = IOUtils.GetDiskSpace(Interpolate.current.tempFolder);
Logger.Log($"Disk space check for '{drivePath}/': {(mb / 1024f).ToString("0.0")} GB free.", true);
Logger.Log($"Disk space check for '{drivePath}/': {(mb / 1024f).ToString("0.0")} GB free.", true);
if (!Interpolate.canceled && mb < (Config.GetInt("minDiskSpaceGb", 6) * 1024))
Interpolate.Cancel("Running out of disk space!");
if (!Interpolate.canceled && mb < (Config.GetInt("minDiskSpaceGb", 6) * 1024))
Interpolate.Cancel("Running out of disk space!");
}
catch
{
// Disk space check failed, this is not critical and might just be caused by a null ref
}
}
await Task.Delay(15000);

View File

@ -43,13 +43,6 @@ base = os.path.basename(path)
interp_input_path = os.path.join(dname, args.input)
interp_output_path = os.path.join(dname, args.output)
if args.input.endswith("/"):
video_name = args.input.split("/")[-2].split(input_ext)[0]
else:
video_name = args.input.split("/")[-1].split(input_ext)[0]
output_video = os.path.join(video_name + f"_{args.factor}x" + str(args.output_ext))
torch.set_grad_enabled(False)
if torch.cuda.is_available():