WIP: Added "auto-encode" mode for encoding while interpolating

- Added "CopyTo" IOUtils method for copying file to a folder
- Fixed custom tile size not saving
- Started to remove timingMode 0 compatibility
This commit is contained in:
N00MKRAD
2020-11-30 02:14:04 +01:00
parent 361f6548a1
commit ee29608123
10 changed files with 259 additions and 39 deletions

View File

@@ -83,13 +83,30 @@ namespace Flowframes
string enc = useH265 ? "libx265" : "libx264";
string loopStr = (looptimes > 0) ? $"-stream_loop {looptimes}" : "";
string presetStr = $"-preset {Config.Get("ffEncPreset")}";
//Logger.Log("#1");
string vsyncStr = Config.GetInt("vfrMode") == 0 ? "-vsync 1" : "-vsync 2";
//Logger.Log("#2");
string args = $" {loopStr} {vsyncStr} -f concat -safe 0 -i {framesFile.Wrap()} -r {fps.ToString().Replace(",", ".")} -c:v {enc} -crf {crf} {presetStr} {videoEncArgs} -threads {Config.GetInt("ffEncThreads")} -c:a copy {outPath.Wrap()}";
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.OnlyLastLine);
}
public static async Task FramesToMp4VfrChunk(string framesFile, string outPath, bool useH265, int crf, float fps)
{
//Logger.Log($"Encoding MP4 chunk with CRF {crf}...");
string enc = useH265 ? "libx265" : "libx264";
string presetStr = $"-preset {Config.Get("ffEncPreset")}";
string vsyncStr = Config.GetInt("vfrMode") == 0 ? "-vsync 1" : "-vsync 2";
string args = $" {vsyncStr} -f concat -safe 0 -i {framesFile.Wrap()} -r {fps.ToString().Replace(",", ".")} -c:v {enc} -crf {crf} {presetStr} {videoEncArgs} -threads {Config.GetInt("ffEncThreads")} -c:a copy {outPath.Wrap()}";
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
}
public static async Task ConcatVideos (string concatFile, string outPath, float fps, int looptimes = -1)
{
Logger.Log($"Merging videos...");
string loopStr = (looptimes > 0) ? $"-stream_loop {looptimes}" : "";
string vsyncStr = Config.GetInt("vfrMode") == 0 ? "-vsync 1" : "-vsync 2";
string args = $" {loopStr} {vsyncStr} -f concat -safe 0 -i {concatFile.Wrap()} -r {fps.ToString().Replace(",", ".")} -c copy -pix_fmt yuv420p -movflags +faststart {outPath.Wrap()}";
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
}
public static async Task ConvertFramerate (string inputPath, string outPath, bool useH265, int crf, float newFps, bool delSrc = false)
{
Logger.Log($"Changing video frame rate...");
@@ -334,7 +351,7 @@ namespace Flowframes
static void DeleteSource (string path)
{
Logger.Log("Deleting input file/dir: " + path);
Logger.Log("[FFCmds] Deleting input file/dir: " + path, true);
if (IOUtils.IsPathDirectory(path) && Directory.Exists(path))
Directory.Delete(path, true);

View File

@@ -202,6 +202,7 @@
<Compile Include="IO\PkgInstaller.cs" />
<Compile Include="Data\Packages.cs" />
<Compile Include="IO\PkgUtils.cs" />
<Compile Include="Main\AutoEncode.cs" />
<Compile Include="Main\BatchProcessing.cs" />
<Compile Include="Main\CreateVideo.cs" />
<Compile Include="Main\InterpolateSteps.cs" />

20
Code/Form1.Designer.cs generated
View File

@@ -119,6 +119,7 @@
this.htButton1 = new HTAlt.WinForms.HTButton();
this.runStepBtn = new System.Windows.Forms.Button();
this.stepSelector = new System.Windows.Forms.ComboBox();
this.label22 = new System.Windows.Forms.Label();
this.panel1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.pictureBox4)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.pictureBox3)).BeginInit();
@@ -167,7 +168,7 @@
this.tilesize.Name = "tilesize";
this.tilesize.Size = new System.Drawing.Size(125, 23);
this.tilesize.TabIndex = 25;
this.tilesize.SelectedIndexChanged += new System.EventHandler(this.tilesize_SelectedIndexChanged);
this.tilesize.TextChanged += new System.EventHandler(this.tilesize_TextChanged);
//
// aiCombox
//
@@ -1239,6 +1240,7 @@
// previewTab
//
this.previewTab.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.previewTab.Controls.Add(this.label22);
this.previewTab.Controls.Add(this.previewPicturebox);
this.previewTab.Location = new System.Drawing.Point(4, 27);
this.previewTab.Margin = new System.Windows.Forms.Padding(0);
@@ -1325,6 +1327,20 @@
this.stepSelector.TabIndex = 73;
this.stepSelector.Visible = false;
//
// label22
//
this.label22.AutoSize = true;
this.label22.ForeColor = System.Drawing.Color.Silver;
this.label22.Location = new System.Drawing.Point(3, 225);
this.label22.Margin = new System.Windows.Forms.Padding(3);
this.label22.MaximumSize = new System.Drawing.Size(160, 0);
this.label22.Name = "label22";
this.label22.Size = new System.Drawing.Size(158, 30);
this.label22.TabIndex = 38;
this.label22.Text = "Click on the preview to open it in a separate window.";
this.label22.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
this.label22.Visible = false;
//
// Form1
//
this.AllowDrop = true;
@@ -1379,6 +1395,7 @@
this.videoUtilsTab.ResumeLayout(false);
this.videoUtilsTab.PerformLayout();
this.previewTab.ResumeLayout(false);
this.previewTab.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.previewPicturebox)).EndInit();
this.abtTab.ResumeLayout(false);
this.ResumeLayout(false);
@@ -1477,6 +1494,7 @@
private System.Windows.Forms.Label welcomeLabel2;
private System.Windows.Forms.Button runStepBtn;
private System.Windows.Forms.ComboBox stepSelector;
private System.Windows.Forms.Label label22;
}
}

View File

@@ -356,12 +356,6 @@ namespace Flowframes
new UpdaterForm().ShowDialog();
}
private void tilesize_SelectedIndexChanged(object sender, EventArgs e)
{
if (!initialized || !GetAi().supportsTiling) return;
Config.Set($"tilesize_{GetAi().aiName}", tilesize.GetInt().ToString());
}
private void welcomeLabel2_Click(object sender, EventArgs e)
{
SetTab("interpolation");
@@ -390,5 +384,11 @@ namespace Flowframes
if (!initialized) return;
aiCombox_SelectedIndexChanged(null, null);
}
private void tilesize_TextChanged(object sender, EventArgs e)
{
if (!initialized || !GetAi().supportsTiling) return;
Config.Set($"tilesize_{GetAi().aiName}", tilesize.GetInt().Clamp(32, 4096).ToString());
}
}
}

View File

@@ -83,7 +83,7 @@
this.downloadPackagesBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.downloadPackagesBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
this.downloadPackagesBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.downloadPackagesBtn.ForeColor = System.Drawing.Color.White;
this.downloadPackagesBtn.ForeColor = System.Drawing.Color.LightGreen;
this.downloadPackagesBtn.Location = new System.Drawing.Point(416, 62);
this.downloadPackagesBtn.Name = "downloadPackagesBtn";
this.downloadPackagesBtn.Size = new System.Drawing.Size(200, 60);

View File

@@ -412,9 +412,26 @@ namespace Flowframes.IO
}
catch (Exception e)
{
Logger.Log($"Can't write to {dir}!", errMode == ErrorMode.HiddenLog);
Logger.Log($"Can't write to {dir}! {e.Message}", errMode == ErrorMode.HiddenLog);
if (errMode == ErrorMode.Messagebox && !BatchProcessing.busy)
MessageBox.Show($"Can't write to {dir}!", "Error");
MessageBox.Show($"Can't write to {dir}!\n\n{e.Message}", "Error");
return false;
}
}
public static bool CopyTo (string file, string targetFolder, bool overwrite = true)
{
string targetPath = Path.Combine(targetFolder, Path.GetFileName(file));
try
{
if (!Directory.Exists(targetFolder))
Directory.CreateDirectory(targetFolder);
File.Copy(file, targetPath, overwrite);
return true;
}
catch (Exception e)
{
Logger.Log($"Failed to copy {file} to {targetFolder}: {e.Message}");
return false;
}
}

99
Code/Main/AutoEncode.cs Normal file
View File

@@ -0,0 +1,99 @@
using Flowframes.IO;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flowframes.Main
{
class AutoEncode
{
static string interpFramesFolder;
static string videoChunksFolder;
static int chunkSize = 125; // Encode every n frames
static int safetyBufferFrames = 25;
public static List<string> encodedFrames = new List<string>();
public static List<string> unencodedFrames = new List<string>();
public static bool busy;
public static async Task MainLoop(string interpFramesPath)
{
interpFramesFolder = interpFramesPath;
videoChunksFolder = Path.Combine(interpFramesPath.GetParentDir(), "video-chunks");
encodedFrames.Clear();
unencodedFrames.Clear();
int interpFramesAmount = GetInterpFramesAmount();
chunkSize = GetChunkSize(IOUtils.GetAmountOfFiles(Interpolate.currentFramesPath, false, "*.png") * Interpolate.lastInterpFactor);
int videoIndex = 1;
while (!Interpolate.canceled && GetInterpFramesAmount() < 2)
await Task.Delay(100);
while ((AiProcess.currentAiProcess != null && !AiProcess.currentAiProcess.HasExited) || encodedFrames.Count < interpFramesAmount) // Loop while proc is running and not all frames have been encoded
{
if (Interpolate.canceled) return;
string[] interpFrames = Directory.GetFiles(interpFramesFolder, $"*.{InterpolateUtils.lastExt}");
interpFramesAmount = interpFrames.Length;
unencodedFrames = interpFrames.ToList().Except(encodedFrames).ToList();
Directory.CreateDirectory(videoChunksFolder);
bool aiRunning = !AiProcess.currentAiProcess.HasExited;
if (unencodedFrames.Count >= (chunkSize + safetyBufferFrames) || !aiRunning) // Encode every n frames, or after process has exited
{
busy = true;
Logger.Log("Encoding Chunk #" + videoIndex, true, false, "ffmpeg.txt");
List<string> framesToEncode = aiRunning ? unencodedFrames.Take(chunkSize).ToList() : unencodedFrames; // Take all remaining frames if process is done
string outpath = Path.Combine(videoChunksFolder, $"{videoIndex.ToString().PadLeft(4, '0')}{InterpolateUtils.GetExt(Interpolate.currentOutMode)}");
int firstFrameNum = Path.GetFileNameWithoutExtension(framesToEncode[0]).GetInt();
await CreateVideo.EncodeChunk(outpath, firstFrameNum, framesToEncode.Count);
videoIndex++;
foreach (string frame in framesToEncode)
File.WriteAllText(frame, "THIS IS A DUMMY FILE - DO NOT DELETE ME"); // Overwrite to save disk space without breaking progress counter
encodedFrames.AddRange(framesToEncode);
Logger.Log("Done Encoding Chunk #" + videoIndex, true, false, "ffmpeg.txt");
busy = false;
}
await Task.Delay(50);
}
if (Interpolate.canceled) return;
string concatFile = Path.Combine(interpFramesPath.GetParentDir(), "chunks-concat.ini");
string concatFileContent = "";
foreach (string vid in Directory.GetFiles(videoChunksFolder))
concatFileContent += $"file '{vid.Replace("\\", "/")}'\n";
File.WriteAllText(concatFile, concatFileContent);
await CreateVideo.ChunksToVideo(videoChunksFolder, concatFile, Interpolate.nextOutPath);
}
static int GetChunkSize(int targetFramesAmount)
{
if (targetFramesAmount > 50000) return 2000;
if (targetFramesAmount > 5000) return 500;
if (targetFramesAmount > 1000) return 200;
return 100;
}
static int GetInterpFramesAmount()
{
return IOUtils.GetAmountOfFiles(interpFramesFolder, false, $"*.{InterpolateUtils.lastExt}");
}
}
}

View File

@@ -9,6 +9,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using i = Flowframes.Interpolate;
@@ -27,8 +28,6 @@ namespace Flowframes.Main
return;
}
await Task.Delay(10);
//if(Config.GetInt("timingMode") == 1)
// await MagickDedupe.Reduplicate(path);
Program.mainForm.SetStatus("Creating output video from frames...");
try
{
@@ -36,15 +35,10 @@ namespace Flowframes.Main
if (maxFps == 0) maxFps = 500;
if (mode == i.OutMode.VidMp4 && i.currentOutFps > maxFps)
{
bool createSecondVid = (Config.GetInt("maxFpsMode") == 1);
await Encode(mode, path, outPath, i.currentOutFps, maxFps, createSecondVid);
}
await Encode(mode, path, outPath, i.currentOutFps, maxFps, (Config.GetInt("maxFpsMode") == 1));
else
{
await Encode(mode, path, outPath, i.currentOutFps);
}
}
catch (Exception e)
{
Logger.Log("FramesToVideo Error: " + e.Message, false);
@@ -66,9 +60,7 @@ namespace Flowframes.Main
if (mode == i.OutMode.VidMp4)
{
string ext = InterpolateUtils.lastExt;
int looptimes = GetLoopTimes(framesPath);
bool h265 = Config.GetInt("mp4Enc") == 1;
int crf = h265 ? Config.GetInt("h265Crf") : Config.GetInt("h264Crf");
@@ -79,7 +71,7 @@ namespace Flowframes.Main
}
else
{
await FFmpegCommands.FramesToMp4(framesPath, outPath, h265, crf, fps, "", false, -1, ext); // Create video
await FFmpegCommands.FramesToMp4(framesPath, outPath, h265, crf, fps, "", false, -1, InterpolateUtils.lastExt); // Create video
}
await MergeAudio(i.lastInputPath, outPath);
@@ -99,13 +91,77 @@ namespace Flowframes.Main
}
}
static async Task Loop (string outPath, int looptimes)
public static async Task ChunksToVideo(string chunksPath, string vfrFile, string outPath)
{
if (IOUtils.GetAmountOfFiles(chunksPath, false, "*.mp4") < 1)
{
i.Cancel("No video chunks found - An error must have occured during chunk encoding!", AiProcess.hasShownError);
return;
}
await Task.Delay(10);
Program.mainForm.SetStatus("Merging video chunks...");
try
{
int maxFps = Config.GetInt("maxFps");
if (maxFps == 0) maxFps = 500;
if (i.currentOutFps > maxFps)
await MergeChunks(chunksPath, vfrFile, outPath, i.currentOutFps, maxFps, (Config.GetInt("maxFpsMode") == 1));
else
await MergeChunks(chunksPath, vfrFile, outPath, i.currentOutFps);
}
catch (Exception e)
{
Logger.Log("ChunksToVideo Error: " + e.Message, false);
MessageBox.Show("An error occured while trying to merge the video chunks.\nCheck the log for details.");
}
}
static async Task MergeChunks(string chunksPath, string vfrFile, string outPath, float fps, float changeFps = -1, bool keepOriginalFpsVid = true)
{
int looptimes = GetLoopTimes(Path.Combine(chunksPath.GetParentDir(), "frames-interpolated"));
bool h265 = Config.GetInt("mp4Enc") == 1;
int crf = h265 ? Config.GetInt("h265Crf") : Config.GetInt("h264Crf");
//string vfrFile = Path.Combine(chunksPath.GetParentDir(), $"vfr-x{i.lastInterpFactor}.ini");
await FFmpegCommands.ConcatVideos(vfrFile, outPath, fps, -1);
await MergeAudio(i.lastInputPath, outPath);
if (looptimes > 0)
await Loop(outPath, looptimes);
if (changeFps > 0)
{
string newOutPath = IOUtils.FilenameSuffix(outPath, $"-{changeFps.ToString("0")}fps");
Program.mainForm.SetStatus("Creating video with desired frame rate...");
await FFmpegCommands.ConvertFramerate(outPath, newOutPath, h265, crf, changeFps, !keepOriginalFpsVid);
await MergeAudio(i.lastInputPath, newOutPath);
if (looptimes > 0)
await Loop(newOutPath, looptimes);
}
}
public static async Task EncodeChunk(string outPath, int firstFrameNum, int framesAmount)
{
bool h265 = Config.GetInt("mp4Enc") == 1;
int crf = h265 ? Config.GetInt("h265Crf") : Config.GetInt("h264Crf");
string vfrFileOriginal = Path.Combine(i.currentTempDir, $"vfr-x{i.lastInterpFactor}.ini");
string vfrFile = Path.Combine(i.currentTempDir, $"vfr-chunk-temp.ini");
File.WriteAllLines(vfrFile, IOUtils.ReadLines(vfrFileOriginal).Skip(firstFrameNum * 2).Take(framesAmount * 2));
await FFmpegCommands.FramesToMp4VfrChunk(vfrFile, outPath, h265, crf, i.currentOutFps);
IOUtils.TryDeleteIfExists(vfrFile);
}
static async Task Loop(string outPath, int looptimes)
{
Logger.Log($"Looping {looptimes} times to reach target length");
await FFmpegCommands.LoopVideo(outPath, looptimes, Config.GetInt("loopMode") == 0);
}
static int GetLoopTimes(string framesOutPath)
{
int times = -1;
@@ -119,17 +175,17 @@ namespace Flowframes.Main
return times;
}
public static async Task MergeAudio(string sourceVideo, string outVideo, int looptimes = -1)
public static async Task MergeAudio(string inputPath, string outVideo, int looptimes = -1)
{
if (!Config.GetBool("enableAudio")) return;
try
{
Logger.Log("Adding input audio to output video...");
string audioFileBasePath = Path.Combine(i.currentTempDir, "audio");
if(IOUtils.IsPathDirectory(sourceVideo) && !File.Exists(IOUtils.GetAudioFile(audioFileBasePath))) // Try loading out of same folder as input if input is a folder
if (IOUtils.IsPathDirectory(inputPath) && !File.Exists(IOUtils.GetAudioFile(audioFileBasePath))) // Try loading out of same folder as input if input is a folder
audioFileBasePath = Path.Combine(i.currentTempDir.GetParentDir(), "audio");
if (!File.Exists(IOUtils.GetAudioFile(audioFileBasePath)))
await FFmpegCommands.ExtractAudio(sourceVideo, audioFileBasePath); // Extract from sourceVideo to audioFile unless it already exists
await FFmpegCommands.ExtractAudio(inputPath, audioFileBasePath); // Extract from sourceVideo to audioFile unless it already exists
if (!File.Exists(IOUtils.GetAudioFile(audioFileBasePath)) || new FileInfo(IOUtils.GetAudioFile(audioFileBasePath)).Length < 4096)
{
Logger.Log("No compatible audio stream found.");

View File

@@ -27,9 +27,11 @@ namespace Flowframes
public static int interpFactor;
public static float currentInFps;
public static float currentOutFps;
public static OutMode currentOutMode;
public static int lastInterpFactor;
public static string lastInputPath;
public static string nextOutPath;
public static AI lastAi;
public static bool canceled = false;
@@ -52,6 +54,7 @@ namespace Flowframes
lastInputPath = inPath;
currentTempDir = Utils.GetTempFolderLoc(inPath, outDir);
currentFramesPath = Path.Combine(currentTempDir, "frames");
currentOutMode = outMode;
if (!Utils.CheckDeleteOldTempFolder()) return; // Try to delete temp folder if an old one exists
if(!Utils.CheckPathValid(inPath)) return; // Check if input path/file is valid
Utils.PathAsciiCheck(inPath, outDir);
@@ -69,7 +72,7 @@ namespace Flowframes
await PostProcessFrames();
if (canceled) return;
string interpFramesDir = Path.Combine(currentTempDir, "frames-interpolated");
string outPath = Path.Combine(outDir, Path.GetFileNameWithoutExtension(inPath) + IOUtils.GetAiSuffix(ai, interpFactor) + Utils.GetExt(outMode));
nextOutPath = Path.Combine(outDir, Path.GetFileNameWithoutExtension(inPath) + IOUtils.GetAiSuffix(ai, interpFactor) + Utils.GetExt(outMode));
int frames = IOUtils.GetAmountOfFiles(currentFramesPath, false, "*.png");
int targetFrameCount = frames * interpFactor;
GetProgressByFrameAmount(interpFramesDir, targetFrameCount);
@@ -78,7 +81,7 @@ namespace Flowframes
await RunAi(interpFramesDir, targetFrameCount, tilesize, ai);
if (canceled) return;
Program.mainForm.SetProgress(100);
await CreateVideo.FramesToVideo(interpFramesDir, outPath, outMode);
//await CreateVideo.FramesToVideo(interpFramesDir, nextOutPath, outMode); // TODO: DISABLE NORMAL ENCODING IF PARALLEL ENC WAS USED
Cleanup(interpFramesDir);
Program.mainForm.SetWorking(false);
Logger.Log("Total processing time: " + FormatUtils.Time(sw.Elapsed));
@@ -172,17 +175,22 @@ namespace Flowframes
{
Directory.CreateDirectory(outpath);
List<Task> tasks = new List<Task>();
if (ai.aiName == Networks.dainNcnn.aiName)
await AiProcess.RunDainNcnn(currentFramesPath, outpath, targetFrames, tilesize);
tasks.Add(AiProcess.RunDainNcnn(currentFramesPath, outpath, targetFrames, tilesize));
if (ai.aiName == Networks.cainNcnn.aiName)
await AiProcess.RunCainNcnnMulti(currentFramesPath, outpath, tilesize, interpFactor);
tasks.Add(AiProcess.RunCainNcnnMulti(currentFramesPath, outpath, tilesize, interpFactor));
if (ai.aiName == Networks.rifeCuda.aiName)
await AiProcess.RunRifeCuda(currentFramesPath, interpFactor);
tasks.Add(AiProcess.RunRifeCuda(currentFramesPath, interpFactor));
if (ai.aiName == Networks.rifeNcnn.aiName)
await AiProcess.RunRifeNcnnMulti(currentFramesPath, outpath, tilesize, interpFactor);
tasks.Add(AiProcess.RunRifeNcnnMulti(currentFramesPath, outpath, tilesize, interpFactor));
tasks.Add(AutoEncode.MainLoop(outpath));
await Task.WhenAll(tasks);
}
public static async void GetProgressByFrameAmount(string outdir, int target)

View File

@@ -28,7 +28,7 @@ namespace Flowframes.Main
float generousTime = ((AiProcess.processTime.ElapsedMilliseconds - AiProcess.lastStartupTimeMs) / 1000f);
float fps = (float)frames / generousTime;
string fpsIn = (fps / Interpolate.interpFactor).ToString("0.00");
string fpsIn = (fps / i.interpFactor).ToString("0.00");
string fpsOut = fps.ToString("0.00");
float secondsPerFrame = generousTime / (float)frames;
@@ -37,11 +37,15 @@ namespace Flowframes.Main
string etaStr = FormatUtils.Time(new TimeSpan(0, 0, eta.RoundToInt()));
bool replaceLine = Regex.Split(Logger.textbox.Text, "\r\n|\r|\n").Last().Contains("Average Speed: ");
Logger.Log($"Interpolated {frames}/{target} frames ({percent}%) - Average Speed: {fpsIn} FPS In / {fpsOut} FPS Out - Time: {FormatUtils.Time(AiProcess.processTime.Elapsed)} - ETA: {etaStr}", false, replaceLine);
string logStr = $"Interpolated {frames}/{target} frames ({percent}%) - Average Speed: {fpsIn} FPS In / {fpsOut} FPS Out - ";
logStr += $"Time: {FormatUtils.Time(AiProcess.processTime.Elapsed)} - ETA: {etaStr}";
if (AutoEncode.busy) logStr += " - Encoding...";
Logger.Log(logStr, false, replaceLine);
try
{
if (!string.IsNullOrWhiteSpace(latestFramePath) && frames > Interpolate.interpFactor)
if (!string.IsNullOrWhiteSpace(latestFramePath) && frames > i.interpFactor)
{
if (bigPreviewForm == null && !preview.Visible /* ||Program.mainForm.WindowState != FormWindowState.Minimized */ /* || !Program.mainForm.IsInFocus()*/) return; // Skip if the preview is not visible or the form is not in focus
Image img = IOUtils.GetImage(latestFramePath);