From 1c2162b52a217e2d3b5de5bb5c73dba18461cf66 Mon Sep 17 00:00:00 2001 From: N00MKRAD <61149547+n00mkrad@users.noreply.github.com> Date: Mon, 10 Mar 2025 20:13:08 +0100 Subject: [PATCH] Use single vpy file with args instead of generating scripts on-demand --- CodeLegacy/Os/AiProcess.cs | 9 +- CodeLegacy/Os/VapourSynthUtils.cs | 327 +++++------------------------- Pkgs/rife-ncnn-vs/rife.py | 237 ++++++++++++++++++++++ 3 files changed, 290 insertions(+), 283 deletions(-) create mode 100644 Pkgs/rife-ncnn-vs/rife.py diff --git a/CodeLegacy/Os/AiProcess.cs b/CodeLegacy/Os/AiProcess.cs index 9f3b1df..621a605 100644 --- a/CodeLegacy/Os/AiProcess.cs +++ b/CodeLegacy/Os/AiProcess.cs @@ -8,10 +8,7 @@ using Flowframes.Ui; using Flowframes.Main; using Flowframes.Data; using Flowframes.MiscUtils; -using System.Collections.Generic; -using ImageMagick; using Paths = Flowframes.IO.Paths; -using Flowframes.Media; using System.Drawing; using Flowframes.Utilities; @@ -401,6 +398,7 @@ namespace Flowframes.Os if (rt) { + Logger.ClearLogBox(); Logger.Log($"Starting. Use Space to pause, Left Arrow and Right Arrow to seek, though seeking can be slow."); AiStartedRt(rifeNcnnVs, inPath); } @@ -410,10 +408,11 @@ namespace Flowframes.Os AiStarted(rifeNcnnVs, 1000, inPath); } - string vsPipeArgs = $"{VapourSynthUtils.CreateScript(vsSettings).Wrap()} {VapourSynthUtils.GetVsPipeArgs(vsSettings)} -c y4m -"; + string scriptPath = Path.Combine(Paths.GetPkgPath(), Implementations.rifeNcnnVs.PkgDir, "rife.py"); + string vsPipeArgs = $"{scriptPath} {VapourSynthUtils.GetVsPipeArgs(vsSettings)} -c y4m -"; rifeNcnnVs.StartInfo.Arguments = $"{OsUtils.GetCmdArg()} cd /D {pkgDir.Wrap()} & vspipe {vsPipeArgs} | {pipedTargetArgs}"; - Logger.Log("cmd.exe " + rifeNcnnVs.StartInfo.Arguments, true); + Logger.Log($"cmd.exe {rifeNcnnVs.StartInfo.Arguments}", true); if (!OsUtils.ShowHiddenCmd()) { diff --git a/CodeLegacy/Os/VapourSynthUtils.cs b/CodeLegacy/Os/VapourSynthUtils.cs index 6d236e0..0220df3 100644 --- a/CodeLegacy/Os/VapourSynthUtils.cs +++ b/CodeLegacy/Os/VapourSynthUtils.cs @@ -33,311 +33,82 @@ namespace Flowframes.Os public int PadY { get; set; } = 0; } - public const string VarInputPath = "inputPath"; - public const string VarTempDirPath = "tempDirPath"; - public const string VarLwiPath = "cacheLwiPath"; - public static string GetVsPipeArgs(VsSettings s) { - return $"--arg {VarInputPath}={s.InterpSettings.inPath.Wrap()} --arg {VarTempDirPath}={s.InterpSettings.tempFolder.Wrap()}"; - } - - public static string CreateScript(VsSettings s, bool alwaysPreferFactorOverFps = true) - { - Logger.Log($"Creating RIFE VS script. Model: {s.ModelDir}, Factor: {s.Factor}, Res: {s.Res.Width}x{s.Res.Height}, UHD: {s.Uhd}, SC Sens: {s.SceneDetectSensitivity}, " + + Logger.Log($"Preparing RIFE VS args. Model: {s.ModelDir}, Factor: {s.Factor}, Res: {s.Res.Width}x{s.Res.Height}, UHD: {s.Uhd}, SC Sens: {s.SceneDetectSensitivity}, " + $"GPU ID: {s.GpuId}, GPU Threads: {s.GpuThreads}, TTA: {s.Tta}, Loop: {s.Loop}, Match Duration: {s.MatchDuration}, Dedupe: {s.Dedupe}, RT: {s.Realtime}{(s.Osd ? $", OSD: {s.Osd}" : "")}", true); - s.PadX = s.InterpSettings.InterpResolution.Width - s.InterpSettings.ScaledResolution.Width; - s.PadY = s.InterpSettings.InterpResolution.Height - s.InterpSettings.ScaledResolution.Height; - - int txtScale = (s.InterpSettings.ScaledResolution.Width / 1000f).RoundToInt().Clamp(1, 4); - string mdlPath = Path.Combine(Paths.GetPkgPath(), Implementations.rifeNcnnVs.PkgDir, s.ModelDir).Replace(@"\", "/").Wrap(); - - bool sc = s.SceneDetectSensitivity >= 0.01f; - long frameCount = (long)Interpolate.currentMediaFile.FrameCount; + var args = new List<(string, object)>() + { + ("input", s.InterpSettings.inPath), // Input path + ("tmpDir", s.InterpSettings.tempFolder), // Temp dir path + ("inFps", s.InterpSettings.inFps), // Input FPS + ("outFps", !s.InterpSettings.inputIsFrames ? Interpolate.currentMediaFile.VideoStreams.First().FpsInfo.SpecifiedFps * s.Factor : s.InterpSettings.inFps * s.Factor), // Output FPS + ("outFpsRes", s.InterpSettings.outFpsResampled), + ("resIn", s.InterpSettings.InputResolution.ToStringShort()), + ("resSc", s.InterpSettings.ScaledResolution.ToStringShort()), + ("resOut", s.InterpSettings.OutputResolution.ToStringShort()), + ("pad", $"{s.InterpSettings.InterpResolution.Width - s.InterpSettings.ScaledResolution.Width}x{s.InterpSettings.InterpResolution.Height - s.InterpSettings.ScaledResolution.Height}"), // Padding + ("frames", s.InterpSettings.inputIsFrames), // Input is frames? + ("dedupe", s.InterpSettings.dedupe), // Dedupe? + ("redupe", !s.InterpSettings.noRedupe), // Allow redupe? + ("matchDur", s.MatchDuration), // Match duration? + ("sc", s.SceneDetectSensitivity), // Scene change detection sensitivity + ("loop", s.Loop), // Loop? + ("factor", new Fraction(s.Factor)), + ("mdlPath", Path.Combine(Paths.GetPkgPath(), Implementations.rifeNcnnVs.PkgDir, s.ModelDir).Replace(@"\", "/")), // Model path + ("gpuThreads", s.GpuThreads), // GPU threads + ("uhd", s.Uhd), // UHD? + ("tta", s.Tta), // TTA? + ("gpu", s.GpuId), // GPU ID + ("rt", s.Realtime), // Realtime? + ("osd", s.Osd), // OSD? + ("debugFrNums", Program.Debug), // Show debug overlay with frame nums? + ("debugVars", Program.Debug), // Show debug overlay with variables? + ("txtScale", (s.InterpSettings.ScaledResolution.Width / 1000f).RoundToInt().Clamp(1, 4)), // Text scale + }; + long frameCount = Interpolate.currentMediaFile.FrameCount; bool trim = QuickSettingsTab.trimEnabled; - long srcTrimStartFrame = trim ? (long)(Math.Round(FormatUtils.TimestampToMs(QuickSettingsTab.trimStart) / 1000f * s.InterpSettings.inFps.Float)) : 0; + long srcTrimStartFrame = trim ? (long)Math.Round(FormatUtils.TimestampToMs(QuickSettingsTab.trimStart) / 1000f * s.InterpSettings.inFps.Float) : 0; long srcTrimEndFrame = trim && QuickSettingsTab.doTrimEnd ? (long)(Math.Round(FormatUtils.TimestampToMs(QuickSettingsTab.trimEnd) / 1000f * s.InterpSettings.inFps.Float)) - 1 : frameCount - 1; - - if (trim) - frameCount = srcTrimEndFrame - srcTrimStartFrame; - int endDupeCount = s.Factor.RoundToInt() - 1; int targetFrameCountMatchDuration = (frameCount * s.Factor).RoundToInt(); // Target frame count to match original duration (and for loops) - int targetFrameCountTrue = targetFrameCountMatchDuration - endDupeCount; // Target frame count without dupes at the end (only in-between frames added) - List imports = new List { "sys", "os", "json", "time", "functools", "vapoursynth as vs" }; // Imports - - var l = new List() { "# Generated by Flowframes. Will be overwritten on the next run!", "" }; - l.AddRange(imports.Select(i => $"import {i}")); - l.Add("core = vs.core"); - l.Add($""); - l.Add($"inputPath = globals()[{VarInputPath.Wrap()}]"); - l.Add($"tempDirPath = globals()[{VarTempDirPath.Wrap()}]"); - l.Add($""); - - bool loadFrames = s.InterpSettings.inputIsFrames; - - if (loadFrames) + if (trim) { - l.Add($"# Load frames"); - FileInfo[] frames = IoUtils.GetFileInfosSorted(s.InterpSettings.framesFolder, false, "*.*"); - string ext = frames.FirstOrDefault().Extension; - string first = Path.GetFileNameWithoutExtension(frames.FirstOrDefault().FullName); - l.Add($"clip = core.imwri.Read(r'{Path.Combine(s.InterpSettings.framesFolder, $"%0{first.Length}d{ext}")}', firstnum={first.GetInt()})"); // Load image sequence with imwri - l.Add($"clip = core.std.AssumeFPS(clip, fpsnum={s.InterpSettings.inFps.Numerator}, fpsden={s.InterpSettings.inFps.Denominator})"); // Set frame rate for img seq + frameCount = srcTrimEndFrame - srcTrimStartFrame; + args.Add(("trim", $"{srcTrimStartFrame}/{srcTrimEndFrame}")); } else { - l.Add($"# Load video"); - l.Add("indexFilePath = os.path.join(tempDirPath, 'index.lwi') if os.path.isdir(tempDirPath) else f'{inputPath}.index.lwi'"); - l.Add($"clip = core.lsmas.LWLibavSource(inputPath, cachefile=indexFilePath)"); // Load video with lsmash + args.Add(("trim", "")); } - l.Add($""); - l.Add($"srcFrames = len(clip)"); - l.Add(Debugger.IsAttached ? $"clip = core.text.FrameNum(clip, alignment=7, scale={txtScale}) # Input frame counter" : ""); + string frameIndexesJsonPath = Path.Combine(s.InterpSettings.tempFolder, "frameIndexes.json"); - if(s.InterpSettings.dedupe) - l.Add(GetDedupeLines(s)); - - l.Add($""); - - if (trim) + if (Interpolate.currentMediaFile.IsVfr && Interpolate.currentMediaFile.OutputFrameIndexes != null && Interpolate.currentMediaFile.OutputFrameIndexes.Count > 0 && s.InterpSettings.outFpsResampled.Float > 0.1f) { - l.Add($"# Trim"); - l.Add($"clip = clip.std.Trim({srcTrimStartFrame}, {srcTrimEndFrame}) # Trim"); - l.Add($""); + File.WriteAllText(frameIndexesJsonPath, Interpolate.currentMediaFile.OutputFrameIndexes.ToJson()); } - - if (s.Loop && !s.InterpSettings.inputIsFrames) + else { - l.Add($"# Loop: Copy first frame to end of clip"); - l.Add($"firstFrame = clip[0]"); // Grab first frame - l.Add($"clip = clip + firstFrame"); // Add to end (for seamless loop interpolation) - l.Add($""); + IoUtils.TryDeleteIfExists(frameIndexesJsonPath); } - l.Add($"firstFrameProps = clip.get_frame(0).props"); - l.Add(GetScaleLines(s, loadFrames)); - l.Add($""); + bool use470bg = s.InterpSettings.inputIsFrames && !new[] { "GIF", "EXR" }.Contains(Interpolate.currentMediaFile.Format.Upper()); + args.Add(("cMatrix", use470bg ? "470bg" : "")); + args.Add(("targetMatch", targetFrameCountMatchDuration)); - if (sc) - { - l.Add($"# Scene detection"); - l.Add($"clip = core.misc.SCDetect(clip=clip, threshold={s.SceneDetectSensitivity.ToString("0.0#####")})"); // Scene detection - l.Add($""); - } - - Fraction outFps = s.InterpSettings.inFps * s.Factor; - - if (!loadFrames) - { - outFps = Interpolate.currentMediaFile.VideoStreams.First().FpsInfo.SpecifiedFps * s.Factor; - } - - l.Add($"preInterpFrames = len(clip)"); - - Fraction factor = new Fraction(s.Factor); - string interpStr = alwaysPreferFactorOverFps || Interpolate.currentMediaFile.IsVfr ? $"factor_num={factor.Numerator}, factor_den={factor.Denominator}" : $"fps_num={outFps.Numerator}, fps_den={outFps.Denominator}"; - l.Add($"clip = core.rife.RIFE(clip, {interpStr}, model_path={mdlPath}, gpu_id={s.GpuId.ToString().Replace("-1", "None")}, gpu_thread={s.GpuThreads}, tta={s.Tta}, uhd={s.Uhd}, sc={sc})"); // Interpolate - - if (s.Dedupe && !s.InterpSettings.noRedupe && !s.Realtime) - { - l.Add(GetRedupeLines(s)); - l.Add($""); - } - - bool use470bg = loadFrames && !new[] { "GIF", "EXR" }.Contains(Interpolate.currentMediaFile.Format.Upper()); - l.Add($"clip = vs.core.resize.Bicubic(clip, format=vs.YUV444P16, matrix_s={(use470bg ? "'470bg'" : "cMatrix")})"); // Convert RGB to YUV. Always use 470bg if input is YUV frames - l.Add($"clip = core.std.Crop(clip, right={s.PadX}, bottom={s.PadY})"); - - if (!s.Dedupe) // Ignore trimming code when using deduping that that already handles trimming in the frame order file - { - if (s.Loop) - { - l.Add($"clip = clip.std.Trim(length={targetFrameCountMatchDuration}) # Trim, loop enabled"); - } - else - { - if (!s.MatchDuration) - l.Add($"clip = clip.std.Trim(length={targetFrameCountTrue}) # Trim, loop disabled, duration matching disabled"); - } - } - - if (s.Realtime && s.Loop) - l.AddRange(new List { $"clip = clip.std.Loop(0)", "" }); // Can't loop piped video so we loop it before piping it to ffplay - - if (s.Realtime && s.Osd) - l.Add(GetOsdLines()); - - l.Add(Debugger.IsAttached ? $"clip = core.text.FrameNum(clip, alignment=9, scale={txtScale}) # Output frame counter" : ""); - l.Add(Debugger.IsAttached ? $"clip = core.text.Text(clip, f\"Frames: {{srcFrames}}/{{preInterpFrames}} -> {{len(clip)}} [{factor.GetString()}x]\", alignment=8, scale={txtScale})" : ""); - l.Add(Debugger.IsAttached ? $"clip = core.text.Text(clip, f\"targetMatchDuration: {targetFrameCountMatchDuration} - targetTrue: {targetFrameCountTrue} - endDupeCount: {endDupeCount}\", alignment=2, scale={txtScale})" : ""); - - if(Interpolate.currentMediaFile.IsVfr && Interpolate.currentMediaFile.OutputFrameIndexes != null && Interpolate.currentMediaFile.OutputFrameIndexes.Count > 0 && s.InterpSettings.outFpsResampled.Float > 0.1f) - { - l.Add($"# Frames picked to resample VFR video to {s.InterpSettings.outFpsResampled.Float} FPS"); - l.Add($"frameIndexes = [{string.Join(", ", Interpolate.currentMediaFile.OutputFrameIndexes)}]"); - l.Add($"clip = core.std.Splice([clip[i] for i in frameIndexes if i < len(clip)])"); - l.Add($"clip = core.std.AssumeFPS(clip, fpsnum={s.InterpSettings.outFpsResampled.Numerator}, fpsden={s.InterpSettings.outFpsResampled.Denominator})"); - } - - l.Add($"clip.set_output()"); // Set output - l.Add(""); - - l.Add("if os.path.isfile(r'{inputPath}.index.lwi'):"); - l.Add(" os.remove(r'{inputPath}.index.lwi')"); - - string pkgPath = Path.Combine(Paths.GetPkgPath(), Implementations.rifeNcnnVs.PkgDir); - string vpyPath = Path.Combine(pkgPath, "rife.vpy"); - - File.WriteAllText(vpyPath, string.Join("\n", l)); - - return vpyPath; - } - - static string GetScaleLines(VsSettings settings, bool loadFrames) - { - InterpSettings interp = settings.InterpSettings; - bool resize = !interp.ScaledResolution.IsEmpty && interp.ScaledResolution != interp.InputResolution; - - string s = ""; - - s += $"\n"; - s += $"cMatrix = '709'\n"; - s += $"\n"; - s += $"# Scaled Res: {interp.ScaledResolution.Width}x{interp.ScaledResolution.Height} - Interpolation Res: {interp.InterpResolution.Width}x{interp.InterpResolution.Height} - Output Res: {interp.OutputResolution.Width}x{interp.OutputResolution.Height}\n"; - s += $"\n"; - - if (!loadFrames) - { - s += "try:\n"; - s += " m = firstFrameProps._Matrix\n"; - s += " if m == 0: cMatrix = 'rgb'\n"; - s += " elif m == 4: cMatrix = 'fcc'\n"; - s += " elif m == 5: cMatrix = '470bg'\n"; - s += " elif m == 6: cMatrix = '170m'\n"; - s += " elif m == 7: cMatrix = '240m'\n"; - s += " elif m == 8: cMatrix = 'ycgco'\n"; - s += " elif m == 9: cMatrix = '2020ncl'\n"; - s += " elif m == 10: cMatrix = '2020cl'\n"; - s += " elif m == 12: cMatrix = 'chromancl'\n"; - s += " elif m == 13: cMatrix = 'chromacl'\n"; - s += " elif m == 14: cMatrix = 'ictcp'\n"; - s += $"except:\n"; - s += $" cMatrix = '709'\n"; - s += $"\n"; - s += $"colRange = 'full' if firstFrameProps.get('_ColorRange') == 0 else 'limited'\n"; - s += $"\n"; - } - - s += $"if clip.format.color_family == vs.YUV:\n"; - s += $" clip = core.resize.Bicubic(clip=clip, format=vs.RGBS, matrix_in_s=cMatrix, range_s=colRange{(resize ? $", width={interp.ScaledResolution.Width}, height={interp.ScaledResolution.Height}" : "")})\n"; - s += $"\n"; - s += $"if clip.format.color_family == vs.RGB:\n"; - s += $" clip = core.resize.Bicubic(clip=clip, format=vs.RGBS{(resize ? $", width={interp.ScaledResolution.Width}, height={interp.ScaledResolution.Height}" : "")})\n"; - s += $"\n"; - - if (settings.PadX > 0 || settings.PadY > 0) - { - s += "# Padding to achieve a compatible resolution\n"; - s += $"clip = core.std.AddBorders(clip, right={settings.PadX}, bottom={settings.PadY})\n"; - s += $"\n"; - } - - return s; - } - - static string GetDedupeLines(VsSettings settings) - { - string s = ""; - - string inputJsonPath = Path.Combine(settings.InterpSettings.tempFolder, "input.json"); - - if (!File.Exists(inputJsonPath)) - return s; - - s += "reorderedClip = clip[0]\n"; - s += "\n"; - s += $"with open(r'{inputJsonPath}') as json_file:\n"; - s += " frameList = json.load(json_file)\n"; - s += " \n"; - s += " for i in frameList:\n"; - s += " reorderedClip = reorderedClip + clip[i]\n"; - s += "\n"; - s += "clip = reorderedClip.std.Trim(1, reorderedClip.num_frames - 1) # Dedupe trim\n"; - //s += Debugger.IsAttached ? "clip = core.text.FrameNum(clip, alignment=9)\n" : ""; - - return s; - } - - static string GetRedupeLines(VsSettings settings) - { - string s = ""; - - s += "reorderedClip = clip[0]\n"; - s += "\n"; - s += $"with open(r'{Path.Combine(settings.InterpSettings.tempFolder, "frames.vs.json")}') as json_file:\n"; - s += " frameList = json.load(json_file)\n"; - s += " \n"; - s += " for i in frameList:\n"; - s += " if(i < clip.num_frames):\n"; - s += " reorderedClip = reorderedClip + clip[i]\n"; - s += "\n"; - s += "clip = reorderedClip.std.Trim(1, reorderedClip.num_frames - 1) # Redupe trim\n"; - //s += Debugger.IsAttached ? "clip = core.text.FrameNum(clip, alignment=1)\n" : ""; - - return s; - } - - static string GetOsdLines() - { - string s = ""; - - s += $"framesProducedPrevious = 0 \n"; - s += $"framesProducedCurrent = 0 \n"; - s += $"lastFpsUpdateTime = time.time() \n"; - s += $"startTime = time.time() \n"; - s += $" \n"; - s += $"def onFrame(n, clip): \n"; - s += $" global startTime \n"; - s += $" fpsAvgTime = 1 \n"; - s += $" \n"; - s += $" if time.time() - startTime > fpsAvgTime: \n"; - s += $" global framesProducedPrevious \n"; - s += $" global framesProducedCurrent \n"; - s += $" global lastFpsUpdateTime \n"; - s += $" \n"; - s += $" fpsFloat = (clip.fps.numerator / clip.fps.denominator) \n"; - s += $" videoTimeFloat = (1 / fpsFloat) * n \n"; - s += $" framesProducedCurrent+=1 \n"; - s += $" \n"; - s += $" if time.time() - lastFpsUpdateTime > fpsAvgTime: \n"; - s += $" lastFpsUpdateTime = time.time() \n"; - s += $" framesProducedPrevious = framesProducedCurrent / fpsAvgTime \n"; - s += $" framesProducedCurrent = 0 \n"; - s += $" \n"; - s += $" speed = (framesProducedPrevious / fpsFloat) * 100 \n"; - s += $" osdString = f\"Time: {{time.strftime(\'%H:%M:%S\', time.gmtime(videoTimeFloat))}} - FPS: {{framesProducedPrevious:.2f}}/{{fpsFloat:.2f}} ({{speed:.0f}}%){{\' [!]\' if speed < 95 else \'\'}}\" \n"; - s += $" clip = core.text.Text(clip, text=osdString, alignment=7, scale=1) \n"; - s += $" return clip \n"; - s += $" \n"; - s += $"clip = core.std.FrameEval(clip, functools.partial(onFrame, clip=clip)) \n"; - - return s; + return string.Join(" ", args.Select(a => $"--arg {a.Item1}={a.Item2.ToString().Wrap()}")); } public static int GetSeekSeconds(long videoLengthSeconds) { - int seekStep = 10; - - if (videoLengthSeconds > 2 * 60) seekStep = 20; - if (videoLengthSeconds > 5 * 60) seekStep = 30; - if (videoLengthSeconds > 15 * 60) seekStep = 60; - - return seekStep; + if (videoLengthSeconds > 15 * 60) return 60; + if (videoLengthSeconds > 5 * 60) return 30; + if (videoLengthSeconds > 2 * 60) return 20; + return 10; } } } diff --git a/Pkgs/rife-ncnn-vs/rife.py b/Pkgs/rife-ncnn-vs/rife.py new file mode 100644 index 0000000..af4f462 --- /dev/null +++ b/Pkgs/rife-ncnn-vs/rife.py @@ -0,0 +1,237 @@ +import sys +import os +import json +import time +import functools +import glob +import vapoursynth as vs +core = vs.core + +# Vars from command line (via VSPipe) +input_path = globals()["input"] +temp_dir_path = globals()["tmpDir"] +fps_in = globals()["inFps"] +fps_out = globals()["outFps"] +fps_out_resampled = globals()["outFpsRes"] +res_input = globals()["resIn"] +res_scaled = globals()["resSc"] +pad = globals()["pad"] +frames = globals()["frames"] == 'True' +dedupe = globals()["dedupe"] == 'True' +allow_redupe = globals()["redupe"] == 'True' +match_duration = globals()["matchDur"] == 'True' +sc_sens = float(globals()["sc"]) +loop = globals()["loop"] == 'True' +factor = globals()["factor"] +realtime = globals()["rt"] == 'True' +perf_osd = realtime and globals()["osd"] == 'True' +show_frame_nums = globals()["debugFrNums"] == 'True' +show_vars = globals()["debugVars"] == 'True' +txt_scale = globals()["txtScale"] +trim = globals()["trim"] +override_c_matrix = globals()["cMatrix"] + +# Construct & parse additional variables +frames_dir = os.path.join(temp_dir_path, 'frames') +inframes_json_path = os.path.join(temp_dir_path, 'input.json') +frames_vs_json_path = os.path.join(temp_dir_path, 'frames.vs.json') +vfr_json_path = os.path.join(temp_dir_path, 'frameIndexes.json') +infps_num, infps_den = map(int, fps_in.split('/')) +outfps_num, outfps_den = map(int, fps_out.split('/')) +outfps_res_num, outfps_res_den = map(int, fps_out_resampled.split('/')) +res_in_x, res_in_y = map(int, res_input.split('x')) # Input resolution +res_scaled_x, res_scaled_y = map(int, res_scaled.split('x')) # Scaled resolution +pad_x, pad_y = map(int, pad.split('x')) # Padding right/bottom + +# Load frames or video +if frames: + frames = sorted(glob.glob(os.path.join(frames_dir, "*.*"))) + ext = os.path.splitext(frames[0])[1] + first = os.path.splitext(os.path.basename(frames[0]))[0] + pattern = os.path.join(frames_dir, f"%0{len(first)}d{ext}") # Construct the file pattern with the proper padding + clip = core.imwri.Read(rf"{pattern}", firstnum=int(first)) # Load the image sequence with imwri + clip = core.std.AssumeFPS(clip, fpsnum=infps_num, fpsden=infps_den) # Set the frame rate for the image sequence +else: + index_file_path = os.path.join(temp_dir_path, 'index.lwi') if os.path.isdir(temp_dir_path) else f'{input_path}.index.lwi' + clip = core.lsmas.LWLibavSource(input_path, cachefile=index_file_path) # Load video with lsmash + +src_frames = len(clip) # Amount of source frames + +# if show_frame_nums: clip = core.text.FrameNum(clip, alignment=7, scale=txt_scale) # Input frame counter + +reordered_clip = clip[0] + +# Deduplication +if dedupe: + with open(inframes_json_path) as json_file: + frame_list = json.load(json_file) + for i in frame_list: + reordered_clip = reordered_clip + clip[i] + clip = reordered_clip.std.Trim(1, reordered_clip.num_frames - 1) # Dedupe trim + +# Trim +if trim: + src_trim_start, src_trim_end = map(int, trim.split('/')) # Trim start and end frames + clip = clip.std.Trim(src_trim_start, src_trim_end) + +# Loop: Copy first frame to end of clip +if loop and not frames: + first_frame = clip[0] + clip = clip + first_frame + +# Store properties of the first frame for later use +first_frame_props = clip.get_frame(0).props +c_matrix = '709' + +try: + m = first_frame_props._Matrix + if override_c_matrix: c_matrix = override_c_matrix + elif m == 0: c_matrix = 'rgb' + elif m == 4: c_matrix = 'fcc' + elif m == 5: c_matrix = '470bg' + elif m == 6: c_matrix = '170m' + elif m == 7: c_matrix = '240m' + elif m == 8: c_matrix = 'ycgco' + elif m == 9: c_matrix = '2020ncl' + elif m == 10: c_matrix = '2020cl' + elif m == 12: c_matrix = 'chromancl' + elif m == 13: c_matrix = 'chromacl' + elif m == 14: c_matrix = 'ictcp' +except: + c_matrix = '709' + +# Store color range (same as first frame) +col_range = 'full' if first_frame_props.get('_ColorRange') == 0 else 'limited' + +resize = res_scaled and res_scaled != "0x0" and res_scaled != res_input +res_w = res_scaled_x if resize else res_in_x +res_h = res_scaled_y if resize else res_in_y + +# Convert to RGBS from YUV or RGB +colors = "YUV" if clip.format.color_family == vs.YUV else "RGB" +if colors == "YUV": + clip = core.resize.Bicubic(clip=clip, format=vs.RGBS, matrix_in_s=c_matrix, range_s=col_range, width=res_w, height=res_h) +else: + clip = core.resize.Bicubic(clip=clip, format=vs.RGBS, width=res_w, height=res_h) + +info_str = f"Factor: {factor}\nFPS Inp: {fps_in}\nFPS Out: {fps_out}\nFPS Rsp: {fps_out_resampled}\nRes Inp: {res_input}\nRes Scl: {res_scaled}\nColors: {colors} {col_range}\nDe/Redupe: {dedupe}/{dedupe and allow_redupe}\n" +info_str += f"Loop: {loop}\nScn Detect: {sc_sens}\nMatch Dur: {match_duration}\nVFR: {os.path.isfile(vfr_json_path)}\nTxtScale: {txt_scale}\nTrim: {trim}" + +# Padding to achieve a compatible resolution (some models need a certain modulo) +if pad_x > 0 or pad_y > 0: + clip = core.std.AddBorders(clip, right=pad_x, bottom=pad_y) + +# Scene change detection +if sc_sens > 0.01: + clip = core.misc.SCDetect(clip=clip, threshold=sc_sens) + +pre_interp_frames = len(clip) +frames_processed_total = 0 + +# RIFE Variables +r_mdlpath = globals()["mdlPath"] +r_gpu = int(globals()["gpu"]) +r_threads = int(globals()["gpuThreads"]) +r_uhd = globals()["uhd"] == 'True' +r_tta = globals()["tta"] == 'True' +info_str += f"\nGPU: {r_gpu} ({r_threads} thrds)\nUHD: {r_uhd}\nTTA: {r_tta}\nMatrix: {c_matrix}" + +# OSD (input clip) +def on_frame_in(n, clip): + global frames_processed_total, info_str + frames_processed_total += 1 + if show_frame_nums: + clip = core.text.Text(clip, text=f"{frames_processed_total:06d}", alignment=7, scale=txt_scale) + if show_vars: + clip = core.text.Text(clip, text=f"\n\n\n{info_str}", alignment=7, scale=txt_scale) + return clip + +clip = core.std.FrameEval(clip, functools.partial(on_frame_in, clip=clip)) + +# RIFE Interpolation +r_fac_num, r_fac_den = map(int, factor.split('/')) +clip = core.rife.RIFE(clip, factor_num=r_fac_num, factor_den=r_fac_den, model_path=r_mdlpath, gpu_id=(None if r_gpu < 0 else r_gpu), gpu_thread=r_threads, tta=r_tta, uhd=r_uhd, sc=sc_sens > 0.01) + +# Reduplication +if dedupe and allow_redupe and not realtime: + reordered_clip = clip[0] + with open(frames_vs_json_path) as json_file: + frame_list = json.load(json_file) + for i in frame_list: + if i < clip.num_frames: + reordered_clip = reordered_clip + clip[i] + clip = reordered_clip.std.Trim(1, reordered_clip.num_frames - 1) # Redupe trim + +# Set output format & color matrix +clip = vs.core.resize.Bicubic(clip, format=vs.YUV444P16, matrix_s=c_matrix) + +# Undo compatibility padding by cropping the same area +if pad_x > 0 or pad_y > 0: + clip = core.std.Crop(clip, right=pad_x, bottom=pad_y) + +# Factor rounded to int, minus 1 +end_dupe_count = r_fac_num // r_fac_den - 1 +target_count_match = int(globals()["targetMatch"]) +target_count_true = target_count_match - end_dupe_count + +if not dedupe: + if loop: + clip = clip.std.Trim(length=target_count_match) # Trim, loop enabled + elif match_duration: + clip = clip.std.Trim(length=target_count_true) # Trim, loop disabled, duration matching disabled + +# OSD Variables +frames_produced_prev = 0 +frames_produced_curr = 0 +frames_produced_total = 0 +last_fps_upd_time = time.time() +start_time = last_fps_upd_time + +# OSD etc. +def on_frame_out(n, clip): + global start_time, frames_produced_total + frames_produced_total += 1 + if show_frame_nums: + clip = core.text.Text(clip, text=f"\n{frames_produced_total:06d}", alignment=7, scale=txt_scale) + if not perf_osd: + return clip + fps_avg_time = 2 + now = time.time() + if now - start_time > fps_avg_time: + global frames_produced_prev, frames_produced_curr, last_fps_upd_time + fps_float = (clip.fps.numerator / clip.fps.denominator) + vid_time_float = (1 / fps_float) * n + frames_produced_curr += 1 + if now - last_fps_upd_time > fps_avg_time: + last_fps_upd_time = now + frames_produced_prev = frames_produced_curr / fps_avg_time + frames_produced_curr = 0 + speed = (frames_produced_prev / fps_float) * 100 + osd_str = f"{time.strftime('%H:%M:%S', time.gmtime(vid_time_float))} - {frames_produced_prev:.2f}/{fps_float:.2f} FPS ({speed:.0f}%){' [!]' if speed < 95 else ''}" + clip = core.text.Text(clip, text=osd_str, alignment=1, scale=txt_scale) + return clip + +clip = core.std.FrameEval(clip, functools.partial(on_frame_out, clip=clip)) + +# Frame number debug overlay +if show_frame_nums: + factor_str = f"{r_fac_num / r_fac_den:.2f}" + # clip = core.text.FrameNum(clip, alignment=9, scale=txt_scale) # Output frame counter + clip = core.text.Text(clip, f"Frames: {src_frames}/{pre_interp_frames} -> {len(clip)} [{factor_str}x]", alignment=9, scale=txt_scale) + clip = core.text.Text(clip, f"Target (match): {target_count_match} - Target (true): {target_count_true} - End Dupes: {end_dupe_count}", alignment=3, scale=txt_scale) + +# Frames picked to resample VFR video to fps_out_resampled +if os.path.isfile(vfr_json_path): + with open(vfr_json_path) as json_file: + frame_indexes = json.load(json_file) + clip = core.std.Splice([clip[i] for i in frame_indexes if i < len(clip)]) + clip = core.std.AssumeFPS(clip, fpsnum=outfps_res_den, fpsden=outfps_res_num) + +# Loop video indefinitely for realtime mode +if realtime and loop: + clip = clip.std.Loop(0) + +clip.set_output() + +if os.path.isfile(index_file_path): + os.remove(index_file_path)