mirror of
https://github.com/n00mkrad/flowframes.git
synced 2025-12-24 04:09:29 +01:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
@@ -1,496 +0,0 @@
|
||||
using Flowframes.AudioVideo;
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Main;
|
||||
using Flowframes.MiscUtils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Utils = Flowframes.AudioVideo.FFmpegUtils;
|
||||
|
||||
namespace Flowframes
|
||||
{
|
||||
class FFmpegCommands
|
||||
{
|
||||
static string hdrFilter = @"-vf select=gte(n\,%frNum%),zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p";
|
||||
static string divisionFilter = "\"crop=trunc(iw/2)*2:trunc(ih/2)*2\"";
|
||||
static string pngComprArg = "-compression_level 3";
|
||||
static string mpDecDef = "\"mpdecimate\"";
|
||||
static string mpDecAggr = "\"mpdecimate=hi=64*32:lo=64*32:frac=0.1\"";
|
||||
|
||||
public static async Task ExtractSceneChanges(string inputFile, string frameFolderPath, float rate)
|
||||
{
|
||||
Logger.Log("Extracting scene changes...");
|
||||
await VideoToFrames(inputFile, frameFolderPath, false, rate, false, false, new Size(320, 180), false, true);
|
||||
bool hiddenLog = Interpolate.currentInputFrameCount <= 50;
|
||||
int amount = IOUtils.GetAmountOfFiles(frameFolderPath, false);
|
||||
Logger.Log($"Detected {amount} scene {(amount == 1 ? "change" : "changes")}.".Replace(" 0 ", " no "), false, !hiddenLog);
|
||||
}
|
||||
|
||||
public static async Task VideoToFrames(string inputFile, string framesDir, bool alpha, float rate, bool deDupe, bool delSrc, bool timecodes = true)
|
||||
{
|
||||
await VideoToFrames(inputFile, framesDir, alpha, rate, deDupe, delSrc, new Size(), timecodes);
|
||||
}
|
||||
|
||||
public static async Task VideoToFrames(string inputFile, string framesDir, bool alpha, float rate, bool deDupe, bool delSrc, Size size, bool timecodes, bool sceneDetect = false)
|
||||
{
|
||||
if (!sceneDetect) Logger.Log("Extracting video frames from input video...");
|
||||
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
|
||||
IOUtils.CreateDir(framesDir);
|
||||
string timecodeStr = timecodes ? $"-copyts -r {FrameOrder.timebase} -frame_pts true" : "-copyts -frame_pts true";
|
||||
string scnDetect = sceneDetect ? $"\"select='gt(scene,{Config.GetFloatString("scnDetectValue")})'\"" : "";
|
||||
string mpStr = deDupe ? ((Config.GetInt("mpdecimateMode") == 0) ? mpDecDef : mpDecAggr) : "";
|
||||
string filters = FormatUtils.ConcatStrings(new string[] { scnDetect, mpStr } );
|
||||
string vf = filters.Length > 2 ? $"-vf {filters}" : "";
|
||||
string rateArg = (rate > 0) ? $" -r {rate.ToStringDot()}" : "";
|
||||
string pixFmt = alpha ? "-pix_fmt rgba" : "-pix_fmt rgb24"; // Use RGBA for GIF for alpha support
|
||||
string args = $"{rateArg} -i {inputFile.Wrap()} {pngComprArg} -vsync 0 {pixFmt} {timecodeStr} {vf} {sizeStr} \"{framesDir}/%{Padding.inputFrames}d.png\"";
|
||||
AvProcess.LogMode logMode = Interpolate.currentInputFrameCount > 50 ? AvProcess.LogMode.OnlyLastLine : AvProcess.LogMode.Hidden;
|
||||
await AvProcess.RunFfmpeg(args, logMode, AvProcess.TaskType.ExtractFrames);
|
||||
int amount = IOUtils.GetAmountOfFiles(framesDir, false, "*.png");
|
||||
if (!sceneDetect) Logger.Log($"Extracted {amount} {(amount == 1 ? "frame" : "frames")} from input.", false, true);
|
||||
await Task.Delay(1);
|
||||
if (delSrc)
|
||||
DeleteSource(inputFile);
|
||||
}
|
||||
|
||||
public static async Task ImportImages(string inpath, string outpath, bool alpha, Size size, bool delSrc = false, bool showLog = true)
|
||||
{
|
||||
if (showLog) Logger.Log("Importing images...");
|
||||
Logger.Log($"Importing images from {inpath} to {outpath}.");
|
||||
IOUtils.CreateDir(outpath);
|
||||
string concatFile = Path.Combine(Paths.GetDataPath(), "png-concat-temp.ini");
|
||||
string concatFileContent = "";
|
||||
string[] files = IOUtils.GetFilesSorted(inpath);
|
||||
foreach (string img in files)
|
||||
concatFileContent += $"file '{img.Replace(@"\", "/")}'\n";
|
||||
File.WriteAllText(concatFile, concatFileContent);
|
||||
|
||||
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
|
||||
string pixFmt = alpha ? "-pix_fmt rgba" : "-pix_fmt rgb24"; // Use RGBA for GIF for alpha support
|
||||
string args = $" -loglevel panic -f concat -safe 0 -i {concatFile.Wrap()} {pngComprArg} {sizeStr} {pixFmt} -vsync 0 -vf {divisionFilter} \"{outpath}/%{Padding.inputFrames}d.png\"";
|
||||
AvProcess.LogMode logMode = IOUtils.GetAmountOfFiles(inpath, false) > 50 ? AvProcess.LogMode.OnlyLastLine : AvProcess.LogMode.Hidden;
|
||||
await AvProcess.RunFfmpeg(args, logMode, AvProcess.TaskType.ExtractFrames);
|
||||
if (delSrc)
|
||||
DeleteSource(inpath);
|
||||
}
|
||||
|
||||
public static async Task ImportSingleImage(string inputFile, string outPath, Size size)
|
||||
{
|
||||
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
|
||||
bool isPng = (Path.GetExtension(outPath).ToLower() == ".png");
|
||||
string comprArg = isPng ? pngComprArg : "";
|
||||
string pixFmt = "-pix_fmt " + (isPng ? $"rgb24 {comprArg}" : "yuvj420p");
|
||||
string args = $"-i {inputFile.Wrap()} {comprArg} {sizeStr} {pixFmt} -vf {divisionFilter} {outPath.Wrap()}";
|
||||
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden, AvProcess.TaskType.ExtractFrames);
|
||||
}
|
||||
|
||||
public static async Task ExtractSingleFrame(string inputFile, string outputPath, int frameNum)
|
||||
{
|
||||
bool isPng = (Path.GetExtension(outputPath).ToLower() == ".png");
|
||||
string comprArg = isPng ? pngComprArg : "";
|
||||
string pixFmt = "-pix_fmt " + (isPng ? $"rgb24 {comprArg}" : "yuvj420p");
|
||||
string args = $"-i {inputFile.Wrap()} -vf \"select=eq(n\\,{frameNum})\" -vframes 1 {pixFmt} {outputPath.Wrap()}";
|
||||
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden, AvProcess.TaskType.ExtractFrames);
|
||||
}
|
||||
|
||||
public static async Task ExtractLastFrame(string inputFile, string outputPath, Size size)
|
||||
{
|
||||
if (IOUtils.IsPathDirectory(outputPath))
|
||||
outputPath = Path.Combine(outputPath, "last.png");
|
||||
bool isPng = (Path.GetExtension(outputPath).ToLower() == ".png");
|
||||
string comprArg = isPng ? pngComprArg : "";
|
||||
string pixFmt = "-pix_fmt " + (isPng ? $"rgb24 {comprArg}" : "yuvj420p");
|
||||
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
|
||||
string args = $"-sseof -1 -i {inputFile.Wrap()} -update 1 {pixFmt} {sizeStr} {outputPath.Wrap()}";
|
||||
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden, AvProcess.TaskType.ExtractFrames);
|
||||
}
|
||||
|
||||
public static async Task FramesToVideoConcat(string framesFile, string outPath, Interpolate.OutMode outMode, float fps, AvProcess.LogMode logMode = AvProcess.LogMode.OnlyLastLine, bool isChunk = false)
|
||||
{
|
||||
await FramesToVideoConcat(framesFile, outPath, outMode, fps, 0, logMode, isChunk);
|
||||
}
|
||||
|
||||
public static async Task FramesToVideoConcat(string framesFile, string outPath, Interpolate.OutMode outMode, float fps, float resampleFps, AvProcess.LogMode logMode = AvProcess.LogMode.OnlyLastLine, bool isChunk = false)
|
||||
{
|
||||
if (logMode != AvProcess.LogMode.Hidden)
|
||||
Logger.Log((resampleFps <= 0) ? $"Encoding video..." : $"Encoding video resampled to {resampleFps.ToString().Replace(",", ".")} FPS...");
|
||||
Directory.CreateDirectory(outPath.GetParentDir());
|
||||
string encArgs = Utils.GetEncArgs(Utils.GetCodec(outMode));
|
||||
if (!isChunk) encArgs += $" -movflags +faststart";
|
||||
string vfrFilename = Path.GetFileName(framesFile);
|
||||
string rate = fps.ToString().Replace(",", ".");
|
||||
string vf = (resampleFps <= 0) ? "" : $"-vf fps=fps={resampleFps.ToStringDot()}";
|
||||
string extraArgs = Config.Get("ffEncArgs");
|
||||
string args = $"-loglevel error -vsync 0 -f concat -r {rate} -i {vfrFilename} {encArgs} {vf} {extraArgs} -threads {Config.GetInt("ffEncThreads")} {outPath.Wrap()}";
|
||||
await AvProcess.RunFfmpeg(args, framesFile.GetParentDir(), logMode, AvProcess.TaskType.Encode);
|
||||
}
|
||||
|
||||
public static async Task ConcatVideos(string concatFile, string outPath, int looptimes = -1)
|
||||
{
|
||||
Logger.Log($"Merging videos...", false, Logger.GetLastLine().Contains("frame"));
|
||||
string loopStr = (looptimes > 0) ? $"-stream_loop {looptimes}" : "";
|
||||
string vfrFilename = Path.GetFileName(concatFile);
|
||||
string args = $" {loopStr} -vsync 1 -f concat -i {vfrFilename} -c copy -movflags +faststart {outPath.Wrap()}";
|
||||
await AvProcess.RunFfmpeg(args, concatFile.GetParentDir(), AvProcess.LogMode.Hidden, AvProcess.TaskType.Merge);
|
||||
}
|
||||
|
||||
public static async Task FramesToGifConcat(string framesFile, string outPath, float fps, bool palette, int colors = 64, float resampleFps = -1, AvProcess.LogMode logMode = AvProcess.LogMode.OnlyLastLine)
|
||||
{
|
||||
if (logMode != AvProcess.LogMode.Hidden)
|
||||
Logger.Log((resampleFps <= 0) ? $"Encoding GIF..." : $"Encoding GIF resampled to {resampleFps.ToString().Replace(",", ".")} FPS...");
|
||||
string vfrFilename = Path.GetFileName(framesFile);
|
||||
string paletteFilter = palette ? $"-vf \"split[s0][s1];[s0]palettegen={colors}[p];[s1][p]paletteuse=dither=floyd_steinberg:diff_mode=rectangle\"" : "";
|
||||
string fpsFilter = (resampleFps <= 0) ? "" : $"fps=fps={resampleFps.ToStringDot()}";
|
||||
string vf = FormatUtils.ConcatStrings(new string[] { paletteFilter, fpsFilter });
|
||||
string rate = fps.ToStringDot();
|
||||
string args = $"-loglevel error -f concat -r {rate} -i {vfrFilename.Wrap()} -f gif {vf} {outPath.Wrap()}";
|
||||
await AvProcess.RunFfmpeg(args, framesFile.GetParentDir(), AvProcess.LogMode.OnlyLastLine, AvProcess.TaskType.Encode);
|
||||
}
|
||||
|
||||
public static async Task MergeAlphaIntoRgb (string rgbDir, int rgbPad, string alphaDir, int aPad)
|
||||
{
|
||||
string filter = "-filter_complex [0:v:0][1:v:0]alphamerge[out] -map [out]";
|
||||
string args = $"-i \"{rgbDir}/%{rgbPad}d.png\" -i \"{alphaDir}/%{aPad}d.png\" {filter} \"{rgbDir}/%{rgbPad}d.png\"";
|
||||
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
|
||||
}
|
||||
|
||||
public static async Task LoopVideo(string inputFile, int times, bool delSrc = false)
|
||||
{
|
||||
string pathNoExt = Path.ChangeExtension(inputFile, null);
|
||||
string ext = Path.GetExtension(inputFile);
|
||||
string args = $" -stream_loop {times} -i {inputFile.Wrap()} -c copy \"{pathNoExt}-Loop{times}{ext}\"";
|
||||
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
|
||||
if (delSrc)
|
||||
DeleteSource(inputFile);
|
||||
}
|
||||
|
||||
public static async Task ChangeSpeed(string inputFile, float newSpeedPercent, bool delSrc = false)
|
||||
{
|
||||
string pathNoExt = Path.ChangeExtension(inputFile, null);
|
||||
string ext = Path.GetExtension(inputFile);
|
||||
float val = newSpeedPercent / 100f;
|
||||
string speedVal = (1f / val).ToString("0.0000").Replace(",", ".");
|
||||
string args = " -itsscale " + speedVal + " -i \"" + inputFile + "\" -c copy \"" + pathNoExt + "-" + newSpeedPercent + "pcSpeed" + ext + "\"";
|
||||
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.OnlyLastLine);
|
||||
if (delSrc)
|
||||
DeleteSource(inputFile);
|
||||
}
|
||||
|
||||
public static async Task Encode(string inputFile, string vcodec, string acodec, int crf, int audioKbps = 0, bool delSrc = false)
|
||||
{
|
||||
string outPath = Path.ChangeExtension(inputFile, null) + "-convert.mp4";
|
||||
string args = $" -i {inputFile.Wrap()} -c:v {vcodec} -crf {crf} -pix_fmt yuv420p -c:a {acodec} -b:a {audioKbps}k -vf {divisionFilter} {outPath.Wrap()}";
|
||||
if (string.IsNullOrWhiteSpace(acodec))
|
||||
args = args.Replace("-c:a", "-an");
|
||||
if (audioKbps < 0)
|
||||
args = args.Replace($" -b:a {audioKbps}", "");
|
||||
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.OnlyLastLine, AvProcess.TaskType.Encode);
|
||||
if (delSrc)
|
||||
DeleteSource(inputFile);
|
||||
}
|
||||
|
||||
public static async Task ExtractAudio(string inputFile, string outFile) // https://stackoverflow.com/a/27413824/14274419
|
||||
{
|
||||
string audioExt = Utils.GetAudioExt(inputFile);
|
||||
outFile = Path.ChangeExtension(outFile, audioExt);
|
||||
Logger.Log($"[FFCmds] Extracting audio from {inputFile} to {outFile}", true);
|
||||
string args = $" -loglevel panic -i {inputFile.Wrap()} -vn -c:a copy {outFile.Wrap()}";
|
||||
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
|
||||
if (File.Exists(outFile) && IOUtils.GetFilesize(outFile) < 512)
|
||||
{
|
||||
Logger.Log("Failed to extract audio losslessly! Trying to re-encode.");
|
||||
File.Delete(outFile);
|
||||
|
||||
outFile = Path.ChangeExtension(outFile, Utils.GetAudioExtForContainer(Path.GetExtension(inputFile)));
|
||||
args = $" -loglevel panic -i {inputFile.Wrap()} -vn {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} {outFile.Wrap()}";
|
||||
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
|
||||
|
||||
if ((File.Exists(outFile) && IOUtils.GetFilesize(outFile) < 512) || AvProcess.lastOutputFfmpeg.Contains("Invalid data"))
|
||||
{
|
||||
Logger.Log("Failed to extract audio, even with re-encoding. Output will not have audio.");
|
||||
IOUtils.TryDeleteIfExists(outFile);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Log($"Source audio has been re-encoded as it can't be extracted losslessly. This may decrease the quality slightly.", false, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ExtractSubtitles (string inputFile, string outFolder, Interpolate.OutMode outMode)
|
||||
{
|
||||
Dictionary<int, string> subDict = await GetSubtitleTracks(inputFile);
|
||||
foreach (KeyValuePair<int, string> subTrack in subDict)
|
||||
{
|
||||
string trackName = subTrack.Value.Length > 4 ? CultureInfo.CurrentCulture.TextInfo.ToTitleCase(subTrack.Value.ToLower()) : subTrack.Value.ToUpper();
|
||||
string outPath = Path.Combine(outFolder, $"{subTrack.Key}-{trackName}.srt");
|
||||
string args = $" -loglevel error -i {inputFile.Wrap()} -map 0:s:{subTrack.Key} {outPath.Wrap()}";
|
||||
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
|
||||
if (AvProcess.lastOutputFfmpeg.Contains("matches no streams")) // Break if there are no more subtitle tracks
|
||||
break;
|
||||
Logger.Log($"[FFCmds] Extracted subtitle track {subTrack.Key} to {outPath} ({FormatUtils.Bytes(IOUtils.GetFilesize(outPath))})", true, false, "ffmpeg");
|
||||
}
|
||||
if(subDict.Count > 0)
|
||||
{
|
||||
Logger.Log($"Extracted {subDict.Count} subtitle tracks from the input video.");
|
||||
Utils.ContainerSupportsSubs(Utils.GetExt(outMode), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Dictionary<int, string>> GetSubtitleTracks (string inputFile)
|
||||
{
|
||||
Dictionary<int, string> subDict = new Dictionary<int, string>();
|
||||
string args = $"-i {inputFile.Wrap()}";
|
||||
string[] outputLines = (await AvProcess.GetFfmpegOutputAsync(args)).SplitIntoLines();
|
||||
string[] filteredLines = outputLines.Where(l => l.Contains(" Subtitle: ")).ToArray();
|
||||
int idx = 0;
|
||||
foreach(string line in filteredLines)
|
||||
{
|
||||
string lang = "unknown";
|
||||
bool hasLangInfo = line.Contains("(") && line.Contains("): Subtitle: ");
|
||||
if (hasLangInfo)
|
||||
lang = line.Split('(')[1].Split(')')[0];
|
||||
subDict.Add(idx, lang);
|
||||
idx++;
|
||||
}
|
||||
return subDict;
|
||||
}
|
||||
|
||||
public static async Task MergeAudioAndSubs(string inputFile, string audioPath, string tempFolder, int looptimes = -1) // https://superuser.com/a/277667
|
||||
{
|
||||
Logger.Log($"[FFCmds] Merging audio from {audioPath} into {inputFile}", true);
|
||||
string containerExt = Path.GetExtension(inputFile);
|
||||
string tempPath = Path.Combine(tempFolder, $"vid{containerExt}"); // inputFile + "-temp" + Path.GetExtension(inputFile);
|
||||
string outPath = Path.Combine(tempFolder, $"muxed{containerExt}"); // inputFile + "-temp" + Path.GetExtension(inputFile);
|
||||
File.Move(inputFile, tempPath);
|
||||
string inName = Path.GetFileName(tempPath);
|
||||
string audioName = Path.GetFileName(audioPath);
|
||||
string outName = Path.GetFileName(outPath);
|
||||
|
||||
bool subs = Utils.ContainerSupportsSubs(containerExt, false) && Config.GetBool("keepSubs");
|
||||
string subInputArgs = "";
|
||||
string subMapArgs = "";
|
||||
string subMetaArgs = "";
|
||||
string[] subTracks = subs ? IOUtils.GetFilesSorted(tempFolder, false, "*.srt") : new string[0];
|
||||
for (int subTrack = 0; subTrack < subTracks.Length; subTrack++)
|
||||
{
|
||||
subInputArgs += $" -i {Path.GetFileName(subTracks[subTrack])}";
|
||||
subMapArgs += $" -map {subTrack+2}";
|
||||
subMetaArgs += $" -metadata:s:s:{subTrack} language={Path.GetFileNameWithoutExtension(subTracks[subTrack]).Split('-').Last()}";
|
||||
}
|
||||
|
||||
string subCodec = Utils.GetSubCodecForContainer(containerExt);
|
||||
string args = $" -i {inName} -stream_loop {looptimes} -i {audioName.Wrap()}" +
|
||||
$"{subInputArgs} -map 0:v -map 1:a {subMapArgs} -c:v copy -c:a copy -c:s {subCodec} {subMetaArgs} -shortest {outName}";
|
||||
|
||||
await AvProcess.RunFfmpeg(args, tempFolder, AvProcess.LogMode.Hidden);
|
||||
|
||||
if ((File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024) || AvProcess.lastOutputFfmpeg.Contains("Invalid data") || AvProcess.lastOutputFfmpeg.Contains("Error initializing output stream"))
|
||||
{
|
||||
Logger.Log("Failed to merge audio losslessly! Trying to re-encode.", false, false, "ffmpeg");
|
||||
|
||||
args = $" -i {inName} -stream_loop {looptimes} -i {audioName.Wrap()}" +
|
||||
$"{subInputArgs} -map 0:v -map 1:a {subMapArgs} -c:v copy {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} -c:s {subCodec} {subMetaArgs} -shortest {outName}";
|
||||
|
||||
await AvProcess.RunFfmpeg(args, tempFolder, AvProcess.LogMode.Hidden);
|
||||
|
||||
if ((File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024) || AvProcess.lastOutputFfmpeg.Contains("Invalid data") || AvProcess.lastOutputFfmpeg.Contains("Error initializing output stream"))
|
||||
{
|
||||
Logger.Log("Failed to merge audio, even with re-encoding. Output will not have audio.", false, false, "ffmpeg");
|
||||
IOUtils.TryDeleteIfExists(tempPath);
|
||||
return;
|
||||
}
|
||||
|
||||
string audioExt = Path.GetExtension(audioPath).Remove(".").ToUpper();
|
||||
Logger.Log($"Source audio ({audioExt}) has been re-encoded to fit into the target container ({containerExt.Remove(".").ToUpper()}). This may decrease the quality slightly.", false, true, "ffmpeg");
|
||||
}
|
||||
|
||||
//string movePath = Path.ChangeExtension(inputFile, Path.GetExtension(tempPath));
|
||||
//File.Delete(movePath);
|
||||
if(File.Exists(outPath) && IOUtils.GetFilesize(outPath) > 512)
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
File.Move(outPath, inputFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Move(tempPath, inputFile);
|
||||
}
|
||||
}
|
||||
|
||||
public static float GetFramerate(string inputFile)
|
||||
{
|
||||
Logger.Log("Reading FPS using ffmpeg.", true, false, "ffmpeg");
|
||||
string args = $" -i {inputFile.Wrap()}";
|
||||
string output = AvProcess.GetFfmpegOutput(args);
|
||||
string[] entries = output.Split(',');
|
||||
foreach (string entry in entries)
|
||||
{
|
||||
if (entry.Contains(" fps") && !entry.Contains("Input ")) // Avoid reading FPS from the filename, in case filename contains "fps"
|
||||
{
|
||||
Logger.Log("[FFCmds] FPS Entry: " + entry, true);
|
||||
string num = entry.Replace(" fps", "").Trim().Replace(",", ".");
|
||||
float value;
|
||||
float.TryParse(num, NumberStyles.Any, CultureInfo.InvariantCulture, out value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public static Size GetSize(string inputFile)
|
||||
{
|
||||
string args = $" -v panic -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 {inputFile.Wrap()}";
|
||||
string output = AvProcess.GetFfprobeOutput(args);
|
||||
|
||||
if (output.Length > 4 && output.Contains("x"))
|
||||
{
|
||||
string[] numbers = output.Split('x');
|
||||
return new Size(numbers[0].GetInt(), numbers[1].GetInt());
|
||||
}
|
||||
return new Size(0, 0);
|
||||
}
|
||||
|
||||
public static int GetFrameCount(string inputFile)
|
||||
{
|
||||
int frames = 0;
|
||||
|
||||
Logger.Log("Reading frame count using ffprobe.", true, false, "ffmpeg");
|
||||
frames = ReadFrameCountFfprobe(inputFile, Config.GetBool("ffprobeCountFrames")); // Try reading frame count with ffprobe
|
||||
if (frames > 0)
|
||||
return frames;
|
||||
|
||||
Logger.Log($"Failed to get frame count using ffprobe (frames = {frames}). Reading frame count using ffmpeg.", true, false, "ffmpeg");
|
||||
frames = ReadFrameCountFfmpeg(inputFile); // Try reading frame count with ffmpeg
|
||||
if (frames > 0)
|
||||
return frames;
|
||||
|
||||
Logger.Log("Failed to get total frame count of video.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static async Task<int> GetFrameCountAsync(string inputFile)
|
||||
{
|
||||
int frames = 0;
|
||||
|
||||
Logger.Log("Reading frame count using ffprobe.", true, false, "ffmpeg");
|
||||
frames = await ReadFrameCountFfprobeAsync(inputFile, Config.GetBool("ffprobeCountFrames")); // Try reading frame count with ffprobe
|
||||
if (frames > 0)
|
||||
return frames;
|
||||
|
||||
Logger.Log($"Failed to get frame count using ffprobe (frames = {frames}). Reading frame count using ffmpeg.", true, false, "ffmpeg");
|
||||
frames = await ReadFrameCountFfmpegAsync(inputFile); // Try reading frame count with ffmpeg
|
||||
if (frames > 0)
|
||||
return frames;
|
||||
|
||||
Logger.Log("Failed to get total frame count of video.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ReadFrameCountFfprobe(string inputFile, bool readFramesSlow)
|
||||
{
|
||||
string args = $" -v panic -select_streams v:0 -show_entries stream=nb_frames -of default=noprint_wrappers=1 {inputFile.Wrap()}";
|
||||
if (readFramesSlow)
|
||||
{
|
||||
Logger.Log("Counting total frames using FFprobe. This can take a moment...");
|
||||
args = $" -v panic -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 {inputFile.Wrap()}";
|
||||
}
|
||||
string info = AvProcess.GetFfprobeOutput(args);
|
||||
string[] entries = info.SplitIntoLines();
|
||||
try
|
||||
{
|
||||
if (readFramesSlow)
|
||||
return info.GetInt();
|
||||
foreach (string entry in entries)
|
||||
{
|
||||
if (entry.Contains("nb_frames="))
|
||||
return entry.GetInt();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return -1;
|
||||
}
|
||||
|
||||
static async Task<int> ReadFrameCountFfprobeAsync(string inputFile, bool readFramesSlow)
|
||||
{
|
||||
string args = $" -v panic -select_streams v:0 -show_entries stream=nb_frames -of default=noprint_wrappers=1 {inputFile.Wrap()}";
|
||||
if (readFramesSlow)
|
||||
{
|
||||
Logger.Log("Counting total frames using FFprobe. This can take a moment...");
|
||||
await Task.Delay(10);
|
||||
args = $" -v panic -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 {inputFile.Wrap()}";
|
||||
}
|
||||
string info = AvProcess.GetFfprobeOutput(args);
|
||||
string[] entries = info.SplitIntoLines();
|
||||
try
|
||||
{
|
||||
if (readFramesSlow)
|
||||
return info.GetInt();
|
||||
foreach (string entry in entries)
|
||||
{
|
||||
if (entry.Contains("nb_frames="))
|
||||
return entry.GetInt();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ReadFrameCountFfmpeg(string inputFile)
|
||||
{
|
||||
string args = $" -loglevel panic -i {inputFile.Wrap()} -map 0:v:0 -c copy -f null - ";
|
||||
string info = AvProcess.GetFfmpegOutput(args);
|
||||
string[] entries = info.SplitIntoLines();
|
||||
foreach (string entry in entries)
|
||||
{
|
||||
if (entry.Contains("frame="))
|
||||
return entry.Substring(0, entry.IndexOf("fps")).GetInt();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static async Task<int> ReadFrameCountFfmpegAsync (string inputFile)
|
||||
{
|
||||
string args = $" -loglevel panic -i {inputFile.Wrap()} -map 0:v:0 -c copy -f null - ";
|
||||
string info = await AvProcess.GetFfmpegOutputAsync(args, true);
|
||||
try
|
||||
{
|
||||
string[] lines = info.SplitIntoLines();
|
||||
string lastLine = lines.Last();
|
||||
return lastLine.Substring(0, lastLine.IndexOf("fps")).GetInt();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetAudioCodec(string path)
|
||||
{
|
||||
string args = $" -v panic -show_streams -select_streams a -show_entries stream=codec_name {path.Wrap()}";
|
||||
string info = AvProcess.GetFfprobeOutput(args);
|
||||
string[] entries = info.SplitIntoLines();
|
||||
foreach (string entry in entries)
|
||||
{
|
||||
if (entry.Contains("codec_name="))
|
||||
return entry.Split('=')[1];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static void DeleteSource(string path)
|
||||
{
|
||||
Logger.Log("[FFCmds] Deleting input file/dir: " + path, true);
|
||||
|
||||
if (IOUtils.IsPathDirectory(path) && Directory.Exists(path))
|
||||
Directory.Delete(path, true);
|
||||
|
||||
if (!IOUtils.IsPathDirectory(path) && File.Exists(path))
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,7 @@ namespace Flowframes.Data
|
||||
public AI(string aiNameArg, string friendlyNameArg, string descArg, FlowPackage pkgArg, bool supportsAnyExpArg)
|
||||
{
|
||||
aiName = aiNameArg;
|
||||
aiNameShort = aiNameArg.Split(' ')[0];
|
||||
aiNameShort = aiNameArg.Split('_')[0];
|
||||
aiNameShort = aiNameArg.Split(' ')[0].Split('_')[0];
|
||||
friendlyName = friendlyNameArg;
|
||||
description = descArg;
|
||||
pkg = pkgArg;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
|
||||
namespace Flowframes.IO
|
||||
{
|
||||
class Formats
|
||||
{
|
||||
public static string[] supported = { ".mp4", ".m4v", ".gif", ".mkv", ".mpg", ".webm", ".avi", ".wmv", ".ts", ".bik" }; // Supported formats
|
||||
public static string[] noEncodeSupport = { ".bik" }; // Files that have no encode support, but decode
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
using Flowframes.AudioVideo;
|
||||
using Flowframes.Media;
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Main;
|
||||
using Flowframes.MiscUtils;
|
||||
using Flowframes.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flowframes
|
||||
@@ -18,7 +20,7 @@ namespace Flowframes
|
||||
public AI ai;
|
||||
public float inFps;
|
||||
public float outFps;
|
||||
public int interpFactor;
|
||||
public float interpFactor;
|
||||
public Interpolate.OutMode outMode;
|
||||
public string model;
|
||||
|
||||
@@ -31,6 +33,7 @@ namespace Flowframes
|
||||
public Size scaledResolution;
|
||||
|
||||
public bool alpha;
|
||||
public bool stepByStep;
|
||||
|
||||
public InterpSettings(string inPathArg, string outPathArg, AI aiArg, float inFpsArg, int interpFactorArg, Interpolate.OutMode outModeArg, string modelArg)
|
||||
{
|
||||
@@ -44,6 +47,7 @@ namespace Flowframes
|
||||
model = modelArg;
|
||||
|
||||
alpha = false;
|
||||
stepByStep = false;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -67,6 +71,68 @@ namespace Flowframes
|
||||
scaledResolution = new Size(0, 0);
|
||||
}
|
||||
|
||||
public InterpSettings (string serializedData)
|
||||
{
|
||||
inPath = "";
|
||||
outPath = "";
|
||||
ai = Networks.networks[0];
|
||||
inFps = 0;
|
||||
interpFactor = 0;
|
||||
outFps = 0;
|
||||
outMode = Interpolate.OutMode.VidMp4;
|
||||
model = "";
|
||||
alpha = false;
|
||||
stepByStep = false;
|
||||
inputResolution = new Size(0, 0);
|
||||
scaledResolution = new Size(0, 0);
|
||||
|
||||
Dictionary<string, string> entries = new Dictionary<string, string>();
|
||||
|
||||
foreach(string line in serializedData.SplitIntoLines())
|
||||
{
|
||||
if (line.Length < 3) continue;
|
||||
string[] keyValuePair = line.Split('|');
|
||||
entries.Add(keyValuePair[0], keyValuePair[1]);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, string> entry in entries)
|
||||
{
|
||||
switch (entry.Key)
|
||||
{
|
||||
case "INPATH": inPath = entry.Value; break;
|
||||
case "OUTPATH": outPath = entry.Value; break;
|
||||
case "AI": ai = Networks.GetAi(entry.Value); break;
|
||||
case "INFPS": inFps = float.Parse(entry.Value); break;
|
||||
case "OUTFPS": outFps = float.Parse(entry.Value); break;
|
||||
case "INTERPFACTOR": interpFactor = float.Parse(entry.Value); break;
|
||||
case "OUTMODE": outMode = (Interpolate.OutMode)Enum.Parse(typeof(Interpolate.OutMode), entry.Value); break;
|
||||
case "MODEL": model = entry.Value; break;
|
||||
case "INPUTRES": inputResolution = FormatUtils.ParseSize(entry.Value); break;
|
||||
case "OUTPUTRES": scaledResolution = FormatUtils.ParseSize(entry.Value); break;
|
||||
case "ALPHA": alpha = bool.Parse(entry.Value); break;
|
||||
case "STEPBYSTEP": stepByStep = bool.Parse(entry.Value); break;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
tempFolder = InterpolateUtils.GetTempFolderLoc(inPath, outPath);
|
||||
framesFolder = Path.Combine(tempFolder, Paths.framesDir);
|
||||
interpFolder = Path.Combine(tempFolder, Paths.interpDir);
|
||||
inputIsFrames = IOUtils.IsPathDirectory(inPath);
|
||||
outFilename = Path.Combine(outPath, Path.GetFileNameWithoutExtension(inPath) + IOUtils.GetExportSuffix(interpFactor, ai, model) + FFmpegUtils.GetExt(outMode));
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Log("Tried to create InterpSettings struct without an inpath. Can't set tempFolder, framesFolder and interpFolder.", true);
|
||||
tempFolder = "";
|
||||
framesFolder = "";
|
||||
interpFolder = "";
|
||||
inputIsFrames = false;
|
||||
outFilename = "";
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePaths (string inPathArg, string outPathArg)
|
||||
{
|
||||
inPath = inPathArg;
|
||||
@@ -99,16 +165,50 @@ namespace Flowframes
|
||||
}
|
||||
}
|
||||
|
||||
public int GetTargetFrameCount(string overrideInputDir = "", int overrideFactor = -1)
|
||||
public int GetTargetFrameCount(string overrideInputDir = "", float overrideFactor = -1)
|
||||
{
|
||||
if (framesFolder == null || !Directory.Exists(framesFolder))
|
||||
return 0;
|
||||
|
||||
string framesDir = (!string.IsNullOrWhiteSpace(overrideInputDir)) ? overrideInputDir : framesFolder;
|
||||
int frames = IOUtils.GetAmountOfFiles(framesDir, false, "*.png");
|
||||
int factor = (overrideFactor > 0) ? overrideFactor : interpFactor;
|
||||
int targetFrameCount = (frames * factor) - (interpFactor - 1);
|
||||
float factor = (overrideFactor > 0) ? overrideFactor : interpFactor;
|
||||
int targetFrameCount = ((frames * factor) - (interpFactor - 1)).RoundToInt();
|
||||
return targetFrameCount;
|
||||
}
|
||||
|
||||
public void RefreshAlpha ()
|
||||
{
|
||||
try
|
||||
{
|
||||
bool alphaEnabled = Config.GetBool("enableAlpha", false);
|
||||
bool outputSupportsAlpha = (outMode == Interpolate.OutMode.ImgPng || outMode == Interpolate.OutMode.VidGif);
|
||||
string ext = inputIsFrames ? Path.GetExtension(IOUtils.GetFilesSorted(inPath).First()).ToLower() : Path.GetExtension(inPath).ToLower();
|
||||
alpha = (alphaEnabled && outputSupportsAlpha && (ext == ".gif" || ext == ".png" || ext == ".apng"));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log("RefreshAlpha Error: " + e.Message, true);
|
||||
alpha = false;
|
||||
}
|
||||
}
|
||||
|
||||
public string Serialize ()
|
||||
{
|
||||
string s = $"INPATH|{inPath}\n";
|
||||
s += $"OUTPATH|{outPath}\n";
|
||||
s += $"AI|{ai.aiName}\n";
|
||||
s += $"INFPS|{inFps.ToStringDot()}\n";
|
||||
s += $"OUTFPS|{outFps.ToStringDot()}\n";
|
||||
s += $"INTERPFACTOR|{interpFactor}\n";
|
||||
s += $"OUTMODE|{outMode}\n";
|
||||
s += $"MODEL|{model}\n";
|
||||
s += $"INPUTRES|{inputResolution.Width}x{inputResolution.Height}\n";
|
||||
s += $"OUTPUTRES|{scaledResolution.Width}x{scaledResolution.Height}\n";
|
||||
s += $"ALPHA|{alpha}\n";
|
||||
s += $"STEPBYSTEP|{stepByStep}\n";
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,16 @@ namespace Flowframes.Data
|
||||
networks.Add(rifeNcnn);
|
||||
networks.Add(dainNcnn);
|
||||
}
|
||||
|
||||
public static AI GetAi (string aiName)
|
||||
{
|
||||
foreach(AI ai in networks)
|
||||
{
|
||||
if (ai.aiName == aiName)
|
||||
return ai;
|
||||
}
|
||||
|
||||
return networks[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ namespace Flowframes.Data
|
||||
class Padding
|
||||
{
|
||||
public const int inputFrames = 9;
|
||||
public const int inputFramesRenamed = 8;
|
||||
public const int interpFrames = 8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flowframes.IO
|
||||
{
|
||||
@@ -12,11 +9,22 @@ namespace Flowframes.IO
|
||||
public const string framesDir = "frames";
|
||||
public const string interpDir = "interp";
|
||||
public const string chunksDir = "vchunks";
|
||||
public const string resumeDir = "resumedata";
|
||||
public const string scenesDir = "scenes";
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
public const string alphaSuffix = "-a";
|
||||
public const string prevSuffix = "-previous";
|
||||
|
||||
public const string frameOrderPrefix = "frames";
|
||||
|
||||
public static string GetFrameOrderFilename(float factor)
|
||||
{
|
||||
return $"{frameOrderPrefix}-{factor.ToStringDot()}x.ini";
|
||||
}
|
||||
|
||||
public static string GetFrameOrderFilenameChunk (int from, int to)
|
||||
{
|
||||
return $"{frameOrderPrefix}-chunk-{from}-{to}.ini";
|
||||
}
|
||||
|
||||
public static string GetVerPath()
|
||||
56
Code/Data/ResumeState.cs
Normal file
56
Code/Data/ResumeState.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flowframes.Data
|
||||
{
|
||||
public struct ResumeState
|
||||
{
|
||||
public bool autoEncode;
|
||||
public int interpolatedInputFrames;
|
||||
|
||||
public ResumeState (bool autoEncArg, int lastInterpInFrameArg)
|
||||
{
|
||||
autoEncode = autoEncArg;
|
||||
interpolatedInputFrames = lastInterpInFrameArg;
|
||||
}
|
||||
|
||||
public ResumeState(string serializedData)
|
||||
{
|
||||
autoEncode = false;
|
||||
interpolatedInputFrames = 0;
|
||||
|
||||
Dictionary<string, string> entries = new Dictionary<string, string>();
|
||||
|
||||
foreach (string line in serializedData.SplitIntoLines())
|
||||
{
|
||||
if (line.Length < 3) continue;
|
||||
string[] keyValuePair = line.Split('|');
|
||||
entries.Add(keyValuePair[0], keyValuePair[1]);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, string> entry in entries)
|
||||
{
|
||||
switch (entry.Key)
|
||||
{
|
||||
case "AUTOENC": autoEncode = bool.Parse(entry.Value); break;
|
||||
case "INTERPOLATEDINPUTFRAMES": interpolatedInputFrames = entry.Value.GetInt(); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
string s = $"AUTOENC|{autoEncode}\n";
|
||||
|
||||
if (!autoEncode)
|
||||
{
|
||||
s += $"INTERPOLATEDINPUTFRAMES|{interpolatedInputFrames}";
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Flowframes.Data
|
||||
{
|
||||
public struct SemVer
|
||||
{
|
||||
public int major;
|
||||
public int minor;
|
||||
public int patch;
|
||||
|
||||
public SemVer(int majorNum, int minorNum, int patchNum)
|
||||
{
|
||||
major = majorNum;
|
||||
minor = minorNum;
|
||||
patch = patchNum;
|
||||
}
|
||||
|
||||
public SemVer(string versionStr)
|
||||
{
|
||||
string[] nums = versionStr.Trim().Split('.');
|
||||
major = nums[0].GetInt();
|
||||
minor = nums[1].GetInt();
|
||||
patch = nums[2].GetInt();
|
||||
}
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"{major}.{minor}.{patch}";
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Code/Data/SubtitleTrack.cs
Normal file
23
Code/Data/SubtitleTrack.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Flowframes.Data
|
||||
{
|
||||
class SubtitleTrack
|
||||
{
|
||||
public int streamIndex;
|
||||
public string lang;
|
||||
public string langFriendly;
|
||||
public string encoding;
|
||||
|
||||
public SubtitleTrack (int streamNum, string langStr, string encodingStr)
|
||||
{
|
||||
streamIndex = streamNum;
|
||||
lang = langStr;
|
||||
langFriendly = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(langStr.ToLower());
|
||||
encoding = encodingStr.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ namespace Flowframes
|
||||
try { return int.Parse(str.TrimNumbers()); }
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log("Failed to parse \"" + str + "\" to int: " + e, true);
|
||||
Logger.Log("Failed to parse \"" + str + "\" to int: " + e.Message, true);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,7 @@ namespace Flowframes
|
||||
public static string StripBadChars(this string str)
|
||||
{
|
||||
string outStr = Regex.Replace(str, @"[^\u0020-\u007E]", string.Empty);
|
||||
outStr = outStr.Remove("(").Remove(")").Remove("[").Remove("]").Remove("{").Remove("}").Remove("%").Remove("'");
|
||||
outStr = outStr.Remove("(").Remove(")").Remove("[").Remove("]").Remove("{").Remove("}").Remove("%").Remove("'").Remove("~");
|
||||
return outStr;
|
||||
}
|
||||
|
||||
|
||||
@@ -200,12 +200,12 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AudioVideo\FFmpegUtils.cs" />
|
||||
<Compile Include="Data\AI.cs" />
|
||||
<Compile Include="Data\InterpSettings.cs" />
|
||||
<Compile Include="Data\Networks.cs" />
|
||||
<Compile Include="Data\Padding.cs" />
|
||||
<Compile Include="Data\SemVer.cs" />
|
||||
<Compile Include="Data\ResumeState.cs" />
|
||||
<Compile Include="Data\SubtitleTrack.cs" />
|
||||
<Compile Include="Forms\BatchForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -241,11 +241,17 @@
|
||||
<Compile Include="Main\InterpolateSteps.cs" />
|
||||
<Compile Include="Main\InterpolateUtils.cs" />
|
||||
<Compile Include="Main\FrameOrder.cs" />
|
||||
<Compile Include="Main\ResumeUtils.cs" />
|
||||
<Compile Include="Media\AvProcess.cs" />
|
||||
<Compile Include="Media\FfmpegAlpha.cs" />
|
||||
<Compile Include="Media\FfmpegAudioAndMetadata.cs" />
|
||||
<Compile Include="Media\FfmpegCommands.cs" />
|
||||
<Compile Include="Media\FfmpegEncode.cs" />
|
||||
<Compile Include="Media\FfmpegExtract.cs" />
|
||||
<Compile Include="Media\FFmpegUtils.cs" />
|
||||
<Compile Include="MiscUtils\Benchmarker.cs" />
|
||||
<Compile Include="OS\AiProcess.cs" />
|
||||
<Compile Include="ExtensionMethods.cs" />
|
||||
<Compile Include="AudioVideo\AvProcess.cs" />
|
||||
<Compile Include="AudioVideo\FFmpegCommands.cs" />
|
||||
<Compile Include="Form1.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -255,9 +261,8 @@
|
||||
<Compile Include="Main\Interpolate.cs" />
|
||||
<Compile Include="IO\CfgStrings.cs" />
|
||||
<Compile Include="IO\Config.cs" />
|
||||
<Compile Include="Data\Formats.cs" />
|
||||
<Compile Include="IO\IOUtils.cs" />
|
||||
<Compile Include="IO\Paths.cs" />
|
||||
<Compile Include="Data\Paths.cs" />
|
||||
<Compile Include="IO\Logger.cs" />
|
||||
<Compile Include="Magick\Converter.cs" />
|
||||
<Compile Include="Magick\Dedupe.cs" />
|
||||
|
||||
3
Code/Form1.Designer.cs
generated
3
Code/Form1.Designer.cs
generated
@@ -541,8 +541,7 @@
|
||||
this.label14.Name = "label14";
|
||||
this.label14.Size = new System.Drawing.Size(884, 142);
|
||||
this.label14.TabIndex = 2;
|
||||
this.label14.Text = "Flowframes - Created by n00mkrad aka nmkd\r\n\r\nBased on:\r\n- RIFE by hzwer\r\n- rife-n" +
|
||||
"cnn-vulkan by nihui";
|
||||
this.label14.Text = resources.GetString("label14.Text");
|
||||
//
|
||||
// runBtn
|
||||
//
|
||||
|
||||
@@ -15,6 +15,7 @@ using HTAlt.WinForms;
|
||||
using Flowframes.Data;
|
||||
using Microsoft.WindowsAPICodePack.Taskbar;
|
||||
using System.Threading.Tasks;
|
||||
using Flowframes.MiscUtils;
|
||||
|
||||
namespace Flowframes
|
||||
{
|
||||
@@ -48,8 +49,6 @@ namespace Flowframes
|
||||
InitAis();
|
||||
InterpolateUtils.preview = previewPicturebox;
|
||||
|
||||
ConfigParser.LoadComboxIndex(aiCombox);
|
||||
|
||||
UpdateStepByStepControls(true);
|
||||
|
||||
Initialized();
|
||||
@@ -110,26 +109,39 @@ namespace Flowframes
|
||||
|
||||
public void SetProgress(int percent)
|
||||
{
|
||||
longProgBar.Value = percent.Clamp(0, 100);
|
||||
percent = percent.Clamp(0, 100);
|
||||
TaskbarManager.Instance.SetProgressValue(percent, 100);
|
||||
longProgBar.Value = percent;
|
||||
longProgBar.Refresh();
|
||||
}
|
||||
|
||||
public Size currInRes;
|
||||
public float currInFps;
|
||||
public int currInFrames;
|
||||
public long currInDuration;
|
||||
public void UpdateInputInfo ()
|
||||
{
|
||||
string str = $"Resolution: {(!currInRes.IsEmpty ? $"{currInRes.Width}x{currInRes.Height}" : "Unknown")} - ";
|
||||
str += $"Framerate: {(currInFps > 0f ? $"{currInFps.ToStringDot()} FPS" : "Unknown")} - ";
|
||||
str += $"Frame Count: {(currInFrames > 0 ? $"{currInFrames} Frames" : "Unknown")}";
|
||||
str += $"Frame Count: {(currInFrames > 0 ? $"{currInFrames}" : "Unknown")} - ";
|
||||
str += $"Duration: {(currInDuration > 0 ? $"{FormatUtils.MsToTimestamp(currInDuration)}" : "Unknown")}";
|
||||
inputInfo.Text = str;
|
||||
}
|
||||
|
||||
public void ResetInputInfo ()
|
||||
{
|
||||
currInRes = new Size();
|
||||
currInFps = 0;
|
||||
currInFrames = 0;
|
||||
currInDuration = 0;
|
||||
UpdateInputInfo();
|
||||
}
|
||||
|
||||
void InitAis()
|
||||
{
|
||||
foreach (AI ai in Networks.networks)
|
||||
aiCombox.Items.Add(ai.friendlyName + " - " + ai.description);
|
||||
aiCombox.SelectedIndex = 0;
|
||||
ConfigParser.LoadComboxIndex(aiCombox);
|
||||
}
|
||||
|
||||
public void Initialized()
|
||||
@@ -230,7 +242,6 @@ namespace Flowframes
|
||||
private void fpsInTbox_TextChanged(object sender, EventArgs e)
|
||||
{
|
||||
fpsInTbox.Text = fpsInTbox.Text.TrimNumbers(true);
|
||||
//Interpolate.SetFps(fpsInTbox.GetFloat());
|
||||
UpdateOutputFPS();
|
||||
}
|
||||
|
||||
@@ -238,7 +249,6 @@ namespace Flowframes
|
||||
{
|
||||
float fpsOut = fpsInTbox.GetFloat() * interpFactorCombox.GetFloat();
|
||||
fpsOutTbox.Text = fpsOut.ToString();
|
||||
//Interpolate.interpFactor = interpFactorCombox.GetInt();
|
||||
}
|
||||
|
||||
private void interpFactorCombox_SelectedIndexChanged(object sender, EventArgs e)
|
||||
@@ -275,12 +285,14 @@ namespace Flowframes
|
||||
if (string.IsNullOrWhiteSpace(aiCombox.Text) || aiCombox.Text == lastAiComboxStr) return;
|
||||
lastAiComboxStr = aiCombox.Text;
|
||||
aiModel = UIUtils.FillAiModelsCombox(aiModel, GetAi());
|
||||
if(initialized)
|
||||
ConfigParser.SaveComboxIndex(aiCombox);
|
||||
interpFactorCombox_SelectedIndexChanged(null, null);
|
||||
}
|
||||
|
||||
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
ConfigParser.SaveComboxIndex(aiCombox);
|
||||
Logger.Log("Closing main form.", true);
|
||||
}
|
||||
|
||||
private async void debugExtractFramesBtn_Click(object sender, EventArgs e)
|
||||
@@ -325,6 +337,13 @@ namespace Flowframes
|
||||
SetTab("interpolation");
|
||||
Logger.Log("Selected video/directory: " + Path.GetFileName(files[0]));
|
||||
inputTbox.Text = files[0];
|
||||
|
||||
bool resume = (IOUtils.GetAmountOfFiles(Path.Combine(files[0], Paths.resumeDir), true) > 0);
|
||||
ResumeUtils.resumeNextRun = resume;
|
||||
|
||||
if (resume)
|
||||
ResumeUtils.LoadTempFolder(files[0]);
|
||||
|
||||
MainUiFunctions.InitInput(outputTbox, inputTbox, fpsInTbox);
|
||||
}
|
||||
}
|
||||
@@ -414,6 +433,7 @@ namespace Flowframes
|
||||
stepSelector.Items.AddRange(new string[] { "1) Import/Extract Frames", "2) Run Interpolation", "3) Export", "4) Cleanup & Reset" });
|
||||
stepSelector.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
bool stepByStep = Config.GetInt("processingMode") == 1;
|
||||
runBtn.Visible = !stepByStep && !Program.busy;
|
||||
}
|
||||
|
||||
@@ -117,6 +117,14 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="label14.Text" xml:space="preserve">
|
||||
<value>Flowframes - Created by n00mkrad aka nmkd
|
||||
|
||||
Based on:
|
||||
- RIFE by hzwer
|
||||
- rife-ncnn-vulkan by nihui
|
||||
- dain-ncnn-vulkan by nihui, originally based on DAIN (Depth-Aware Video Frame Interpolation) by baowenbo </value>
|
||||
</data>
|
||||
<metadata name="inFolderDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace Flowframes.Forms
|
||||
|
||||
InterpSettings current = Program.mainForm.GetCurrentSettings();
|
||||
current.UpdatePaths(path, path.GetParentDir());
|
||||
current.inFps = GetFramerate(path);
|
||||
current.inFps = await GetFramerate(path);
|
||||
current.outFps = current.inFps * current.interpFactor;
|
||||
Program.batchQueue.Enqueue(current);
|
||||
RefreshGui();
|
||||
@@ -137,10 +137,10 @@ namespace Flowframes.Forms
|
||||
}
|
||||
}
|
||||
|
||||
float GetFramerate (string path)
|
||||
async Task<float> GetFramerate (string path)
|
||||
{
|
||||
float fps = Interpolate.current.inFps;
|
||||
float fpsFromFile = IOUtils.GetFpsFolderOrVideo(path);
|
||||
float fpsFromFile = await IOUtils.GetFpsFolderOrVideo(path);
|
||||
if (fpsFromFile > 0)
|
||||
return fpsFromFile;
|
||||
|
||||
|
||||
93
Code/Forms/SettingsForm.Designer.cs
generated
93
Code/Forms/SettingsForm.Designer.cs
generated
@@ -140,6 +140,9 @@
|
||||
this.label9 = new System.Windows.Forms.Label();
|
||||
this.label8 = new System.Windows.Forms.Label();
|
||||
this.debugTab = new Cyotek.Windows.Forms.TabListPage();
|
||||
this.panel13 = new System.Windows.Forms.Panel();
|
||||
this.modelsBaseUrl = new System.Windows.Forms.ComboBox();
|
||||
this.label42 = new System.Windows.Forms.Label();
|
||||
this.ffEncArgs = new System.Windows.Forms.TextBox();
|
||||
this.label56 = new System.Windows.Forms.Label();
|
||||
this.label48 = new System.Windows.Forms.Label();
|
||||
@@ -161,7 +164,6 @@
|
||||
this.cmdDebugMode = new System.Windows.Forms.ComboBox();
|
||||
this.titleLabel = new System.Windows.Forms.Label();
|
||||
this.toolTip1 = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.label42 = new System.Windows.Forms.Label();
|
||||
this.settingsTabList.SuspendLayout();
|
||||
this.generalTab.SuspendLayout();
|
||||
this.tabListPage2.SuspendLayout();
|
||||
@@ -460,7 +462,6 @@
|
||||
// tabListPage2
|
||||
//
|
||||
this.tabListPage2.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
|
||||
this.tabListPage2.Controls.Add(this.label42);
|
||||
this.tabListPage2.Controls.Add(this.label4);
|
||||
this.tabListPage2.Controls.Add(this.enableAlpha);
|
||||
this.tabListPage2.Controls.Add(this.label25);
|
||||
@@ -515,9 +516,9 @@
|
||||
this.label25.Location = new System.Drawing.Point(10, 70);
|
||||
this.label25.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
|
||||
this.label25.Name = "label25";
|
||||
this.label25.Size = new System.Drawing.Size(108, 13);
|
||||
this.label25.Size = new System.Drawing.Size(177, 13);
|
||||
this.label25.TabIndex = 76;
|
||||
this.label25.Text = "Enable Transparency";
|
||||
this.label25.Text = "Enable Transparency (Experimental)";
|
||||
//
|
||||
// keepSubs
|
||||
//
|
||||
@@ -1243,9 +1244,9 @@
|
||||
this.label58.Location = new System.Drawing.Point(420, 301);
|
||||
this.label58.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
|
||||
this.label58.Name = "label58";
|
||||
this.label58.Size = new System.Drawing.Size(255, 13);
|
||||
this.label58.Size = new System.Drawing.Size(221, 13);
|
||||
this.label58.TabIndex = 65;
|
||||
this.label58.Text = "Lower is better. Use slightly higher values than h265!";
|
||||
this.label58.Text = "Lower is better. Use higher values than h265!";
|
||||
//
|
||||
// vp9Crf
|
||||
//
|
||||
@@ -1582,13 +1583,16 @@
|
||||
this.label8.Location = new System.Drawing.Point(10, 40);
|
||||
this.label8.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
|
||||
this.label8.Name = "label8";
|
||||
this.label8.Size = new System.Drawing.Size(165, 13);
|
||||
this.label8.Size = new System.Drawing.Size(192, 13);
|
||||
this.label8.TabIndex = 30;
|
||||
this.label8.Text = "Minimum Video Length (Seconds)";
|
||||
this.label8.Text = "Minimum Loop Video Length (Seconds)";
|
||||
//
|
||||
// debugTab
|
||||
//
|
||||
this.debugTab.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(48)))), ((int)(((byte)(48)))), ((int)(((byte)(48)))));
|
||||
this.debugTab.Controls.Add(this.panel13);
|
||||
this.debugTab.Controls.Add(this.modelsBaseUrl);
|
||||
this.debugTab.Controls.Add(this.label42);
|
||||
this.debugTab.Controls.Add(this.ffEncArgs);
|
||||
this.debugTab.Controls.Add(this.label56);
|
||||
this.debugTab.Controls.Add(this.label48);
|
||||
@@ -1612,11 +1616,46 @@
|
||||
this.debugTab.Size = new System.Drawing.Size(762, 419);
|
||||
this.debugTab.Text = "Debugging / Experimental";
|
||||
//
|
||||
// panel13
|
||||
//
|
||||
this.panel13.BackgroundImage = global::Flowframes.Properties.Resources.baseline_create_white_18dp_semiTransparent;
|
||||
this.panel13.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom;
|
||||
this.panel13.Location = new System.Drawing.Point(536, 97);
|
||||
this.panel13.Name = "panel13";
|
||||
this.panel13.Size = new System.Drawing.Size(21, 21);
|
||||
this.panel13.TabIndex = 61;
|
||||
this.toolTip1.SetToolTip(this.panel13, "Allows custom input.");
|
||||
//
|
||||
// modelsBaseUrl
|
||||
//
|
||||
this.modelsBaseUrl.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.modelsBaseUrl.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.modelsBaseUrl.ForeColor = System.Drawing.Color.White;
|
||||
this.modelsBaseUrl.FormattingEnabled = true;
|
||||
this.modelsBaseUrl.Items.AddRange(new object[] {
|
||||
"https://dl.nmkd.de/flowframes/mdl/",
|
||||
"http://45.132.246.240/dl/flowframes/mdl/",
|
||||
"http://104.225.145.33/models/"});
|
||||
this.modelsBaseUrl.Location = new System.Drawing.Point(280, 97);
|
||||
this.modelsBaseUrl.Name = "modelsBaseUrl";
|
||||
this.modelsBaseUrl.Size = new System.Drawing.Size(250, 21);
|
||||
this.modelsBaseUrl.TabIndex = 87;
|
||||
//
|
||||
// label42
|
||||
//
|
||||
this.label42.AutoSize = true;
|
||||
this.label42.Location = new System.Drawing.Point(10, 100);
|
||||
this.label42.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
|
||||
this.label42.Name = "label42";
|
||||
this.label42.Size = new System.Drawing.Size(174, 13);
|
||||
this.label42.TabIndex = 86;
|
||||
this.label42.Text = "Model Download Mirror (Base URL)";
|
||||
//
|
||||
// ffEncArgs
|
||||
//
|
||||
this.ffEncArgs.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.ffEncArgs.ForeColor = System.Drawing.Color.White;
|
||||
this.ffEncArgs.Location = new System.Drawing.Point(280, 210);
|
||||
this.ffEncArgs.Location = new System.Drawing.Point(280, 240);
|
||||
this.ffEncArgs.MinimumSize = new System.Drawing.Size(4, 21);
|
||||
this.ffEncArgs.Name = "ffEncArgs";
|
||||
this.ffEncArgs.Size = new System.Drawing.Size(250, 20);
|
||||
@@ -1625,7 +1664,7 @@
|
||||
// label56
|
||||
//
|
||||
this.label56.AutoSize = true;
|
||||
this.label56.Location = new System.Drawing.Point(10, 213);
|
||||
this.label56.Location = new System.Drawing.Point(10, 243);
|
||||
this.label56.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
|
||||
this.label56.Name = "label56";
|
||||
this.label56.Size = new System.Drawing.Size(154, 13);
|
||||
@@ -1647,7 +1686,7 @@
|
||||
//
|
||||
this.label54.AutoSize = true;
|
||||
this.label54.ForeColor = System.Drawing.Color.Silver;
|
||||
this.label54.Location = new System.Drawing.Point(543, 184);
|
||||
this.label54.Location = new System.Drawing.Point(543, 214);
|
||||
this.label54.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
|
||||
this.label54.Name = "label54";
|
||||
this.label54.Size = new System.Drawing.Size(118, 13);
|
||||
@@ -1669,7 +1708,7 @@
|
||||
"slow",
|
||||
"slower",
|
||||
"veryslow"});
|
||||
this.ffEncPreset.Location = new System.Drawing.Point(280, 180);
|
||||
this.ffEncPreset.Location = new System.Drawing.Point(280, 210);
|
||||
this.ffEncPreset.Name = "ffEncPreset";
|
||||
this.ffEncPreset.Size = new System.Drawing.Size(250, 21);
|
||||
this.ffEncPreset.TabIndex = 78;
|
||||
@@ -1677,7 +1716,7 @@
|
||||
// label47
|
||||
//
|
||||
this.label47.AutoSize = true;
|
||||
this.label47.Location = new System.Drawing.Point(10, 183);
|
||||
this.label47.Location = new System.Drawing.Point(10, 213);
|
||||
this.label47.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
|
||||
this.label47.Name = "label47";
|
||||
this.label47.Size = new System.Drawing.Size(145, 13);
|
||||
@@ -1688,7 +1727,7 @@
|
||||
//
|
||||
this.label46.AutoSize = true;
|
||||
this.label46.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.label46.Location = new System.Drawing.Point(10, 120);
|
||||
this.label46.Location = new System.Drawing.Point(10, 150);
|
||||
this.label46.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
|
||||
this.label46.Name = "label46";
|
||||
this.label46.Size = new System.Drawing.Size(65, 16);
|
||||
@@ -1710,7 +1749,7 @@
|
||||
//
|
||||
this.label41.AutoSize = true;
|
||||
this.label41.ForeColor = System.Drawing.Color.Silver;
|
||||
this.label41.Location = new System.Drawing.Point(308, 243);
|
||||
this.label41.Location = new System.Drawing.Point(308, 273);
|
||||
this.label41.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
|
||||
this.label41.Name = "label41";
|
||||
this.label41.Size = new System.Drawing.Size(395, 13);
|
||||
@@ -1721,7 +1760,7 @@
|
||||
// ffprobeCountFrames
|
||||
//
|
||||
this.ffprobeCountFrames.AutoSize = true;
|
||||
this.ffprobeCountFrames.Location = new System.Drawing.Point(280, 243);
|
||||
this.ffprobeCountFrames.Location = new System.Drawing.Point(280, 273);
|
||||
this.ffprobeCountFrames.Name = "ffprobeCountFrames";
|
||||
this.ffprobeCountFrames.Size = new System.Drawing.Size(15, 14);
|
||||
this.ffprobeCountFrames.TabIndex = 73;
|
||||
@@ -1730,7 +1769,7 @@
|
||||
// label40
|
||||
//
|
||||
this.label40.AutoSize = true;
|
||||
this.label40.Location = new System.Drawing.Point(10, 243);
|
||||
this.label40.Location = new System.Drawing.Point(10, 273);
|
||||
this.label40.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
|
||||
this.label40.Name = "label40";
|
||||
this.label40.Size = new System.Drawing.Size(162, 13);
|
||||
@@ -1741,7 +1780,7 @@
|
||||
//
|
||||
this.label38.AutoSize = true;
|
||||
this.label38.ForeColor = System.Drawing.Color.Silver;
|
||||
this.label38.Location = new System.Drawing.Point(543, 156);
|
||||
this.label38.Location = new System.Drawing.Point(543, 186);
|
||||
this.label38.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
|
||||
this.label38.Name = "label38";
|
||||
this.label38.Size = new System.Drawing.Size(131, 13);
|
||||
@@ -1752,7 +1791,7 @@
|
||||
//
|
||||
this.ffEncThreads.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.ffEncThreads.ForeColor = System.Drawing.Color.White;
|
||||
this.ffEncThreads.Location = new System.Drawing.Point(280, 153);
|
||||
this.ffEncThreads.Location = new System.Drawing.Point(280, 183);
|
||||
this.ffEncThreads.MinimumSize = new System.Drawing.Size(4, 21);
|
||||
this.ffEncThreads.Name = "ffEncThreads";
|
||||
this.ffEncThreads.Size = new System.Drawing.Size(250, 20);
|
||||
@@ -1761,7 +1800,7 @@
|
||||
// label37
|
||||
//
|
||||
this.label37.AutoSize = true;
|
||||
this.label37.Location = new System.Drawing.Point(10, 153);
|
||||
this.label37.Location = new System.Drawing.Point(10, 183);
|
||||
this.label37.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
|
||||
this.label37.Name = "label37";
|
||||
this.label37.Size = new System.Drawing.Size(204, 13);
|
||||
@@ -1844,18 +1883,6 @@
|
||||
this.titleLabel.Size = new System.Drawing.Size(119, 40);
|
||||
this.titleLabel.TabIndex = 1;
|
||||
this.titleLabel.Text = "Settings";
|
||||
//
|
||||
// label42
|
||||
//
|
||||
this.label42.AutoSize = true;
|
||||
this.label42.ForeColor = System.Drawing.Color.Silver;
|
||||
this.label42.Location = new System.Drawing.Point(277, 70);
|
||||
this.label42.Margin = new System.Windows.Forms.Padding(10, 10, 10, 7);
|
||||
this.label42.Name = "label42";
|
||||
this.label42.Size = new System.Drawing.Size(346, 13);
|
||||
this.label42.TabIndex = 79;
|
||||
this.label42.Text = "Coming Soon " +
|
||||
" ";
|
||||
//
|
||||
// SettingsForm
|
||||
//
|
||||
@@ -2028,6 +2055,8 @@
|
||||
private System.Windows.Forms.ComboBox dainNcnnTilesize;
|
||||
private System.Windows.Forms.Label label27;
|
||||
private System.Windows.Forms.Label label26;
|
||||
private System.Windows.Forms.ComboBox modelsBaseUrl;
|
||||
private System.Windows.Forms.Label label42;
|
||||
private System.Windows.Forms.Panel panel13;
|
||||
}
|
||||
}
|
||||
@@ -101,6 +101,7 @@ namespace Flowframes.Forms
|
||||
// Debugging
|
||||
ConfigParser.SaveComboxIndex(cmdDebugMode);
|
||||
ConfigParser.SaveGuiElement(autoDedupFrames);
|
||||
ConfigParser.SaveGuiElement(modelsBaseUrl);
|
||||
ConfigParser.SaveGuiElement(ffEncThreads, ConfigParser.StringMode.Int);
|
||||
ConfigParser.SaveGuiElement(ffEncPreset);
|
||||
ConfigParser.SaveGuiElement(ffEncArgs);
|
||||
@@ -151,6 +152,7 @@ namespace Flowframes.Forms
|
||||
// Debugging
|
||||
ConfigParser.LoadComboxIndex(cmdDebugMode);
|
||||
ConfigParser.LoadGuiElement(autoDedupFrames);
|
||||
ConfigParser.LoadGuiElement(modelsBaseUrl);
|
||||
ConfigParser.LoadGuiElement(ffEncThreads);
|
||||
ConfigParser.LoadGuiElement(ffEncPreset);
|
||||
ConfigParser.LoadGuiElement(ffEncArgs);
|
||||
|
||||
6
Code/Forms/UpdaterForm.Designer.cs
generated
6
Code/Forms/UpdaterForm.Designer.cs
generated
@@ -108,7 +108,7 @@
|
||||
this.installedLabel.AutoSize = true;
|
||||
this.installedLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.installedLabel.ForeColor = System.Drawing.Color.White;
|
||||
this.installedLabel.Location = new System.Drawing.Point(200, 67);
|
||||
this.installedLabel.Location = new System.Drawing.Point(170, 67);
|
||||
this.installedLabel.Margin = new System.Windows.Forms.Padding(8, 8, 3, 0);
|
||||
this.installedLabel.Name = "installedLabel";
|
||||
this.installedLabel.Size = new System.Drawing.Size(76, 16);
|
||||
@@ -120,7 +120,7 @@
|
||||
this.latestLabel.AutoSize = true;
|
||||
this.latestLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.latestLabel.ForeColor = System.Drawing.Color.White;
|
||||
this.latestLabel.Location = new System.Drawing.Point(200, 93);
|
||||
this.latestLabel.Location = new System.Drawing.Point(170, 93);
|
||||
this.latestLabel.Margin = new System.Windows.Forms.Padding(8, 8, 3, 0);
|
||||
this.latestLabel.Name = "latestLabel";
|
||||
this.latestLabel.Size = new System.Drawing.Size(76, 16);
|
||||
@@ -132,7 +132,7 @@
|
||||
this.statusLabel.AutoSize = true;
|
||||
this.statusLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.statusLabel.ForeColor = System.Drawing.Color.White;
|
||||
this.statusLabel.Location = new System.Drawing.Point(200, 119);
|
||||
this.statusLabel.Location = new System.Drawing.Point(170, 119);
|
||||
this.statusLabel.Margin = new System.Windows.Forms.Padding(8, 8, 3, 0);
|
||||
this.statusLabel.Name = "statusLabel";
|
||||
this.statusLabel.Size = new System.Drawing.Size(76, 16);
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace Flowframes.Forms
|
||||
{
|
||||
public partial class UpdaterForm : Form
|
||||
{
|
||||
SemVer installed;
|
||||
SemVer latestPat;
|
||||
SemVer latestFree;
|
||||
Version installed;
|
||||
Version latestPat;
|
||||
Version latestFree;
|
||||
|
||||
public UpdaterForm()
|
||||
{
|
||||
@@ -30,28 +30,28 @@ namespace Flowframes.Forms
|
||||
await Task.Delay(100);
|
||||
latestLabel.Text = $"{latestPat} (Patreon/Beta) - {latestFree} (Free/Stable)";
|
||||
|
||||
if (Updater.VersionMatches(installed, latestFree))
|
||||
if (Updater.CompareVersions(installed, latestFree) == Updater.VersionCompareResult.Equal)
|
||||
{
|
||||
statusLabel.Text = "Latest Free Version Is Installed.";
|
||||
|
||||
if (Updater.IsVersionNewer(installed, latestPat))
|
||||
if (Updater.CompareVersions(installed, latestPat) == Updater.VersionCompareResult.Newer)
|
||||
statusLabel.Text += "\nBeta Update Available On Patreon.";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (Updater.VersionMatches(installed, latestPat))
|
||||
if (Updater.CompareVersions(installed, latestPat) == Updater.VersionCompareResult.Equal)
|
||||
{
|
||||
statusLabel.Text = "Latest Patreon/Beta Version Is Installed.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (Updater.IsVersionNewer(installed, latestPat))
|
||||
if (Updater.CompareVersions(installed, latestPat) == Updater.VersionCompareResult.Newer)
|
||||
{
|
||||
statusLabel.Text = "Update available on Patreon!";
|
||||
|
||||
if (Updater.IsVersionNewer(installed, latestFree))
|
||||
statusLabel.Text += " - Beta Updates Available On Patreon and Itch.io.";
|
||||
if (Updater.CompareVersions(installed, latestFree) == Updater.VersionCompareResult.Newer)
|
||||
statusLabel.Text = $"Beta Updates Available On Patreon and Itch.io.";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,12 @@ namespace Flowframes.IO
|
||||
cachedLines = list.ToArray();
|
||||
}
|
||||
|
||||
public static string Get(string key, string defaultVal)
|
||||
{
|
||||
WriteIfDoesntExist(key, defaultVal);
|
||||
return Get(key);
|
||||
}
|
||||
|
||||
public static string Get(string key, Type type = Type.String)
|
||||
{
|
||||
try
|
||||
@@ -129,10 +135,10 @@ namespace Flowframes.IO
|
||||
if (key == "keepAudio") return WriteDefault(key, "True");
|
||||
if (key == "keepSubs") return WriteDefault(key, "True");
|
||||
if (key == "autoDedupFrames") return WriteDefault(key, "100");
|
||||
if (key == "vfrDedupe") return WriteDefault(key, "True");
|
||||
if (key == "scnDetectValue") return WriteDefault(key, "0.2");
|
||||
if (key == "autoEncMode") return WriteDefault(key, "2");
|
||||
// Video Export
|
||||
if (key == "minOutVidLength") return WriteDefault(key, "5");
|
||||
if (key == "h264Crf") return WriteDefault(key, "20");
|
||||
if (key == "h265Crf") return WriteDefault(key, "24");
|
||||
if (key == "vp9Crf") return WriteDefault(key, "32");
|
||||
@@ -146,12 +152,9 @@ namespace Flowframes.IO
|
||||
if (key == "ncnnThreads") return WriteDefault(key, "1");
|
||||
if (key == "dainNcnnTilesize") return WriteDefault(key, "768");
|
||||
// Debug / Other / Experimental
|
||||
if (key == "modelsBaseUrl") return WriteDefault(key, "https://dl.nmkd.de/flowframes/mdl/");
|
||||
if (key == "ffEncPreset") return WriteDefault(key, "medium");
|
||||
if (key == "ffEncArgs") return WriteDefault(key, "");
|
||||
// Tile Sizes
|
||||
if (key == "tilesize_RIFE_NCNN") return WriteDefault(key, "2048");
|
||||
if (key == "tilesize_DAIN_NCNN") return WriteDefault(key, "512");
|
||||
if (key == "tilesize_CAIN_NCNN") return WriteDefault(key, "2048");
|
||||
|
||||
if (type == Type.Int || type == Type.Float) return WriteDefault(key, "0"); // Write default int/float (0)
|
||||
if (type == Type.Bool) return WriteDefault(key, "False"); // Write default bool (False)
|
||||
|
||||
@@ -168,20 +168,6 @@ namespace Flowframes.IO
|
||||
File.Move(path, targetPath);
|
||||
}
|
||||
|
||||
public static bool TryCopy(string source, string dest, bool overwrite = true) // Copy with error handling. Returns false if failed
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Copy(source, dest, overwrite);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MessageBox.Show("Copy from \"" + source + "\" to \"" + dest + " (Overwrite: " + overwrite + ") failed: \n\n" + e.Message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int GetFilenameCounterLength(string file, string prefixToRemove = "")
|
||||
{
|
||||
string filenameNoExt = Path.GetFileNameWithoutExtension(file);
|
||||
@@ -209,31 +195,36 @@ namespace Flowframes.IO
|
||||
}
|
||||
}
|
||||
|
||||
static bool TryCopy(string source, string target)
|
||||
static bool TryCopy(string source, string target, bool overwrite = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Copy(source, target);
|
||||
File.Copy(source, target, overwrite);
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"Failed to move '{source}' to '{target}' (Overwrite: {overwrite}): {e.Message}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TryMove(string source, string target, bool deleteIfExists = true)
|
||||
public static bool TryMove(string source, string target, bool overwrite = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (deleteIfExists && File.Exists(target))
|
||||
if (overwrite && File.Exists(target))
|
||||
File.Delete(target);
|
||||
|
||||
File.Move(source, target);
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"Failed to move '{source}' to '{target}' (Overwrite: {overwrite}): {e.Message}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -255,8 +246,10 @@ namespace Flowframes.IO
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<string, string> RenameCounterDirReversible(string path, string ext, int startAt, int padding = 0)
|
||||
public static async Task<Dictionary<string, string>> RenameCounterDirReversibleAsync(string path, string ext, int startAt, int padding = 0)
|
||||
{
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Restart();
|
||||
Dictionary<string, string> oldNewNamesMap = new Dictionary<string, string>();
|
||||
|
||||
int counter = startAt;
|
||||
@@ -267,29 +260,52 @@ namespace Flowframes.IO
|
||||
{
|
||||
string dir = new DirectoryInfo(file.FullName).Parent.FullName;
|
||||
int filesDigits = (int)Math.Floor(Math.Log10((double)files.Length) + 1);
|
||||
string outpath = "";
|
||||
if (padding > 0)
|
||||
outpath = Path.Combine(dir, counter.ToString().PadLeft(padding, '0') + Path.GetExtension(file.FullName));
|
||||
else
|
||||
outpath = Path.Combine(dir, counter.ToString() + Path.GetExtension(file.FullName));
|
||||
string newFilename = (padding > 0) ? counter.ToString().PadLeft(padding, '0') : counter.ToString();
|
||||
string outpath = outpath = Path.Combine(dir, newFilename + Path.GetExtension(file.FullName));
|
||||
File.Move(file.FullName, outpath);
|
||||
oldNewNamesMap.Add(file.FullName, outpath);
|
||||
counter++;
|
||||
|
||||
if(sw.ElapsedMilliseconds > 100)
|
||||
{
|
||||
await Task.Delay(1);
|
||||
sw.Restart();
|
||||
}
|
||||
}
|
||||
|
||||
return oldNewNamesMap;
|
||||
}
|
||||
|
||||
public static void ReverseRenaming(Dictionary<string, string> oldNewMap, bool clearDict)
|
||||
public static async Task ReverseRenaming(string basePath, Dictionary<string, string> oldNewMap) // Relative -> absolute paths
|
||||
{
|
||||
if (oldNewMap == null || oldNewMap.Count < 1) return;
|
||||
foreach (KeyValuePair<string, string> pair in oldNewMap)
|
||||
TryMove(pair.Value, pair.Key);
|
||||
if (clearDict)
|
||||
oldNewMap.Clear();
|
||||
Dictionary<string, string> absPaths = oldNewMap.ToDictionary(x => Path.Combine(basePath, x.Key), x => Path.Combine(basePath, x.Value));
|
||||
await ReverseRenaming(absPaths);
|
||||
}
|
||||
|
||||
public static float GetVideoFramerate (string path)
|
||||
public static async Task ReverseRenaming(Dictionary<string, string> oldNewMap) // Takes absolute paths only
|
||||
{
|
||||
if (oldNewMap == null || oldNewMap.Count < 1) return;
|
||||
int counter = 0;
|
||||
int failCount = 0;
|
||||
|
||||
foreach (KeyValuePair<string, string> pair in oldNewMap)
|
||||
{
|
||||
bool success = TryMove(pair.Value, pair.Key);
|
||||
|
||||
if (!success)
|
||||
failCount++;
|
||||
|
||||
if (failCount >= 100)
|
||||
break;
|
||||
|
||||
counter++;
|
||||
|
||||
if (counter % 1000 == 0)
|
||||
await Task.Delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<float> GetVideoFramerate (string path)
|
||||
{
|
||||
float fps = 0;
|
||||
try
|
||||
@@ -305,7 +321,7 @@ namespace Flowframes.IO
|
||||
Logger.Log("Failed to read FPS - Trying alternative method...", true);
|
||||
try
|
||||
{
|
||||
fps = FFmpegCommands.GetFramerate(path);
|
||||
fps = await FfmpegCommands.GetFramerate(path);
|
||||
Logger.Log("Detected FPS of " + Path.GetFileName(path) + " as " + fps + " FPS", true);
|
||||
}
|
||||
catch
|
||||
@@ -369,7 +385,7 @@ namespace Flowframes.IO
|
||||
Logger.Log($"Failed to read video size ({e.Message}) - Trying alternative method...", true);
|
||||
try
|
||||
{
|
||||
size = FFmpegCommands.GetSize(path);
|
||||
size = FfmpegCommands.GetSize(path);
|
||||
Logger.Log($"Detected video size of {Path.GetFileName(path)} as {size.Width}x{size.Height}", true);
|
||||
}
|
||||
catch
|
||||
@@ -416,9 +432,9 @@ namespace Flowframes.IO
|
||||
return GetExportSuffix(Interpolate.current.interpFactor, Interpolate.current.ai, Interpolate.current.model);
|
||||
}
|
||||
|
||||
public static string GetExportSuffix(int factor, AI ai, string mdl)
|
||||
public static string GetExportSuffix(float factor, AI ai, string mdl)
|
||||
{
|
||||
string suffix = $"-{factor}x-{ai.aiNameShort.ToUpper()}";
|
||||
string suffix = $"-{factor.ToStringDot()}x-{ai.aiNameShort.ToUpper()}";
|
||||
if (Config.GetBool("modelSuffix"))
|
||||
suffix += $"-{mdl}";
|
||||
return suffix;
|
||||
@@ -467,19 +483,21 @@ namespace Flowframes.IO
|
||||
return null;
|
||||
}
|
||||
|
||||
public static float GetFpsFolderOrVideo(string path)
|
||||
public static async Task<float> GetFpsFolderOrVideo(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsPathDirectory(path))
|
||||
{
|
||||
float dirFps = GetVideoFramerateForDir(path);
|
||||
|
||||
if (dirFps > 0)
|
||||
return dirFps;
|
||||
}
|
||||
else
|
||||
{
|
||||
float vidFps = GetVideoFramerate(path);
|
||||
float vidFps = await GetVideoFramerate(path);
|
||||
|
||||
if (vidFps > 0)
|
||||
return vidFps;
|
||||
}
|
||||
@@ -488,6 +506,7 @@ namespace Flowframes.IO
|
||||
{
|
||||
Logger.Log("GetFpsFolderOrVideo() Error: " + e.Message);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -722,14 +741,28 @@ namespace Flowframes.IO
|
||||
}
|
||||
|
||||
public static long GetFilesize(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new FileInfo(path).Length;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetFilesizeStr (string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return FormatUtils.Bytes(GetFilesize(path));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] GetLastBytes (string path, int startAt, int bytesAmount)
|
||||
{
|
||||
@@ -741,5 +774,22 @@ namespace Flowframes.IO
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static bool HasBadChars(string str)
|
||||
{
|
||||
return str != str.StripBadChars();
|
||||
}
|
||||
|
||||
public static void OverwriteFileWithText (string path, string text = "THIS IS A DUMMY FILE - DO NOT DELETE ME")
|
||||
{
|
||||
try
|
||||
{
|
||||
File.WriteAllText(path, text);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"OverwriteWithText failed for '{path}': {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,12 @@ namespace Flowframes
|
||||
}
|
||||
}
|
||||
|
||||
public static void LogIfLastLineDoesNotContainMsg (string s, bool hidden = false, bool replaceLastLine = false, string filename = "")
|
||||
{
|
||||
if (!GetLastLine().Contains(s))
|
||||
Log(s, hidden, replaceLastLine, filename);
|
||||
}
|
||||
|
||||
public static void WriteToFile (string content, bool append, string filename)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filename))
|
||||
|
||||
@@ -13,8 +13,6 @@ namespace Flowframes.IO
|
||||
{
|
||||
class ModelDownloader
|
||||
{
|
||||
static string baseUrl = "https://dl.nmkd.de/flowframes/mdl/";
|
||||
|
||||
public static async Task<Dictionary<string, string>> GetFilelist (string ai, string model)
|
||||
{
|
||||
var client = new WebClient();
|
||||
@@ -25,6 +23,7 @@ namespace Flowframes.IO
|
||||
|
||||
static string GetMdlUrl (string ai, string model)
|
||||
{
|
||||
string baseUrl = Config.Get("modelsBaseUrl");
|
||||
return Path.Combine(baseUrl, ai.ToLower(), model);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,11 +36,45 @@ namespace Flowframes.Magick
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ExtractAlpha (string inputDir, string outputDir, bool print = true, bool setProgress = true)
|
||||
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);
|
||||
@@ -49,16 +83,28 @@ namespace Flowframes.Magick
|
||||
int counter = 0;
|
||||
foreach (string file in files)
|
||||
{
|
||||
MagickImage img = new MagickImage(file);
|
||||
img.Format = MagickFormat.Png32;
|
||||
img.Quality = 10;
|
||||
MagickImage alphaImg = new MagickImage(file);
|
||||
|
||||
img.FloodFill(MagickColors.None, 0, 0); // Fill the image with a transparent background
|
||||
img.InverseOpaque(MagickColors.None, MagickColors.White); // Change all the pixels that are not transparent to white.
|
||||
img.ColorAlpha(MagickColors.Black); // Change the transparent pixels to black.
|
||||
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));
|
||||
img.Write(outPath);
|
||||
alphaImg.Write(outPath);
|
||||
counter++;
|
||||
if (sw.ElapsedMilliseconds > 250)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Flowframes.AudioVideo;
|
||||
using Flowframes.Media;
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.MiscUtils;
|
||||
@@ -28,7 +28,7 @@ namespace Flowframes.Main
|
||||
|
||||
public static void UpdateChunkAndBufferSizes ()
|
||||
{
|
||||
chunkSize = GetChunkSize(IOUtils.GetAmountOfFiles(Interpolate.current.framesFolder, false, "*.png") * Interpolate.current.interpFactor);
|
||||
chunkSize = GetChunkSize((IOUtils.GetAmountOfFiles(Interpolate.current.framesFolder, false, "*.png") * Interpolate.current.interpFactor).RoundToInt());
|
||||
bool isNcnn = Interpolate.current.ai.aiName.ToUpper().Contains("NCNN");
|
||||
safetyBufferFrames = isNcnn ? Config.GetInt("autoEncSafeBufferNcnn", 90) : Config.GetInt("autoEncSafeBufferCuda", 30); // Use bigger safety buffer for NCNN
|
||||
}
|
||||
@@ -49,7 +49,7 @@ namespace Flowframes.Main
|
||||
|
||||
Logger.Log($"[AutoEnc] Starting AutoEncode MainLoop - Chunk Size: {chunkSize} Frames - Safety Buffer: {safetyBufferFrames} Frames", true);
|
||||
int videoIndex = 1;
|
||||
string encFile = Path.Combine(interpFramesPath.GetParentDir(), $"vfr-{Interpolate.current.interpFactor}x.ini");
|
||||
string encFile = Path.Combine(interpFramesPath.GetParentDir(), Paths.GetFrameOrderFilename(Interpolate.current.interpFactor));
|
||||
interpFramesLines = IOUtils.ReadLines(encFile).Select(x => x.Split('/').Last().Remove("'").Split('#').First()).ToArray(); // Array with frame filenames
|
||||
|
||||
while (!Interpolate.canceled && GetInterpFramesAmount() < 2)
|
||||
@@ -77,7 +77,6 @@ namespace Flowframes.Main
|
||||
|
||||
if (unencodedFrameLines.Count > 0 && (unencodedFrameLines.Count >= (chunkSize + safetyBufferFrames) || !aiRunning)) // Encode every n frames, or after process has exited
|
||||
{
|
||||
|
||||
List<int> frameLinesToEncode = aiRunning ? unencodedFrameLines.Take(chunkSize).ToList() : unencodedFrameLines; // Take all remaining frames if process is done
|
||||
|
||||
string lastOfChunk = Path.Combine(interpFramesPath, interpFramesLines[frameLinesToEncode.Last()]);
|
||||
@@ -90,7 +89,6 @@ namespace Flowframes.Main
|
||||
|
||||
busy = true;
|
||||
string outpath = Path.Combine(videoChunksFolder, "chunks", $"{videoIndex.ToString().PadLeft(4, '0')}{FFmpegUtils.GetExt(Interpolate.current.outMode)}");
|
||||
//int firstFrameNum = frameLinesToEncode[0];
|
||||
int firstLineNum = frameLinesToEncode.First();
|
||||
int lastLineNum = frameLinesToEncode.Last();
|
||||
Logger.Log($"[AutoEnc] Encoding Chunk #{videoIndex} to '{outpath}' using line {firstLineNum} ({Path.GetFileName(interpFramesLines[firstLineNum])}) through {lastLineNum} ({Path.GetFileName(Path.GetFileName(interpFramesLines[frameLinesToEncode.Last()]))})", true, false, "ffmpeg");
|
||||
@@ -99,7 +97,7 @@ namespace Flowframes.Main
|
||||
|
||||
if (Interpolate.canceled) return;
|
||||
|
||||
if (Config.GetInt("autoEncMode") == 2)
|
||||
if (aiRunning && Config.GetInt("autoEncMode") == 2)
|
||||
Task.Run(() => DeleteOldFramesAsync(interpFramesPath, frameLinesToEncode));
|
||||
|
||||
if (Interpolate.canceled) return;
|
||||
@@ -116,8 +114,6 @@ namespace Flowframes.Main
|
||||
}
|
||||
|
||||
if (Interpolate.canceled) return;
|
||||
|
||||
IOUtils.ReverseRenaming(AiProcess.filenameMap, true); // Get timestamps back
|
||||
await CreateVideo.ChunksToVideos(Interpolate.current.tempFolder, videoChunksFolder, Interpolate.current.outFilename);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -132,16 +128,20 @@ namespace Flowframes.Main
|
||||
Logger.Log("[AutoEnc] Starting DeleteOldFramesAsync.", true, false, "ffmpeg");
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Restart();
|
||||
int counter = 0;
|
||||
|
||||
foreach (int frame in frameLinesToEncode)
|
||||
{
|
||||
bool delete = !FrameIsStillNeeded(interpFramesLines[frame], frame);
|
||||
if (delete) // Make sure frames are no longer needed (e.g. for dupes) before deleting!
|
||||
if (!FrameIsStillNeeded(interpFramesLines[frame], frame)) // Make sure frames are no longer needed (for dupes) before deleting!
|
||||
{
|
||||
string framePath = Path.Combine(interpFramesPath, interpFramesLines[frame]);
|
||||
File.WriteAllText(framePath, "THIS IS A DUMMY FILE - DO NOT DELETE ME"); // Overwrite to save space without breaking progress counter
|
||||
await Task.Delay(1);
|
||||
IOUtils.OverwriteFileWithText(framePath); // Overwrite to save space without breaking progress counter
|
||||
}
|
||||
|
||||
if(counter % 1000 == 0)
|
||||
await Task.Delay(1);
|
||||
|
||||
counter++;
|
||||
}
|
||||
|
||||
Logger.Log("[AutoEnc] DeleteOldFramesAsync finished in " + FormatUtils.TimeSw(sw), true, false, "ffmpeg");
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Windows.Forms;
|
||||
using Padding = Flowframes.Data.Padding;
|
||||
using i = Flowframes.Interpolate;
|
||||
using System.Diagnostics;
|
||||
using Flowframes.AudioVideo;
|
||||
using Flowframes.Media;
|
||||
|
||||
namespace Flowframes.Main
|
||||
{
|
||||
@@ -21,13 +21,13 @@ namespace Flowframes.Main
|
||||
{
|
||||
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 outPath, i.OutMode mode)
|
||||
public static async Task Export(string path, string outPath, i.OutMode mode, bool stepByStep)
|
||||
{
|
||||
if (!mode.ToString().ToLower().Contains("vid")) // Copy interp frames out of temp folder and skip video export for image seq export
|
||||
{
|
||||
try
|
||||
{
|
||||
await CopyOutputFrames(path, Path.GetFileNameWithoutExtension(outPath));
|
||||
await CopyOutputFrames(path, Path.GetFileNameWithoutExtension(outPath), stepByStep);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@@ -63,7 +63,7 @@ namespace Flowframes.Main
|
||||
}
|
||||
}
|
||||
|
||||
static async Task CopyOutputFrames (string framesPath, string folderName)
|
||||
static async Task CopyOutputFrames (string framesPath, string folderName, bool dontMove)
|
||||
{
|
||||
Program.mainForm.SetStatus("Copying output frames...");
|
||||
string copyPath = Path.Combine(i.current.outPath, folderName);
|
||||
@@ -73,7 +73,7 @@ namespace Flowframes.Main
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Restart();
|
||||
|
||||
string vfrFile = Path.Combine(framesPath.GetParentDir(), $"vfr-{i.current.interpFactor}x.ini");
|
||||
string vfrFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(i.current.interpFactor));
|
||||
string[] vfrLines = IOUtils.ReadLines(vfrFile);
|
||||
|
||||
for (int idx = 1; idx <= vfrLines.Length; idx++)
|
||||
@@ -83,7 +83,7 @@ namespace Flowframes.Main
|
||||
string framePath = Path.Combine(framesPath, inFilename);
|
||||
string outFilename = Path.Combine(copyPath, idx.ToString().PadLeft(Padding.interpFrames, '0')) + Path.GetExtension(framePath);
|
||||
|
||||
if ((idx < vfrLines.Length) && vfrLines[idx].Contains(inFilename)) // If file is re-used in the next line, copy instead of move
|
||||
if (dontMove || ((idx < vfrLines.Length) && vfrLines[idx].Contains(inFilename))) // If file is re-used in the next line, copy instead of move
|
||||
File.Copy(framePath, outFilename);
|
||||
else
|
||||
File.Move(framePath, outFilename);
|
||||
@@ -100,20 +100,17 @@ namespace Flowframes.Main
|
||||
static async Task Encode(i.OutMode mode, string framesPath, string outPath, float fps, float resampleFps = -1)
|
||||
{
|
||||
currentOutFile = outPath;
|
||||
string vfrFile = Path.Combine(framesPath.GetParentDir(), $"vfr-{i.current.interpFactor}x.ini");
|
||||
string vfrFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(i.current.interpFactor));
|
||||
|
||||
if (mode == i.OutMode.VidGif)
|
||||
{
|
||||
await FFmpegCommands.FramesToGifConcat(vfrFile, outPath, fps, true, Config.GetInt("gifColors"), resampleFps);
|
||||
await FfmpegEncode.FramesToGifConcat(vfrFile, outPath, fps, true, Config.GetInt("gifColors"), resampleFps);
|
||||
}
|
||||
else
|
||||
{
|
||||
await FFmpegCommands.FramesToVideoConcat(vfrFile, outPath, mode, fps, resampleFps);
|
||||
await FfmpegEncode.FramesToVideoConcat(vfrFile, outPath, mode, fps, resampleFps);
|
||||
await MergeAudio(i.current.inPath, outPath);
|
||||
|
||||
int looptimes = GetLoopTimes();
|
||||
if (looptimes > 0)
|
||||
await Loop(currentOutFile, looptimes);
|
||||
await Loop(currentOutFile, GetLoopTimes());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,18 +149,15 @@ namespace Flowframes.Main
|
||||
|
||||
static async Task MergeChunks(string vfrFile, string outPath)
|
||||
{
|
||||
await FFmpegCommands.ConcatVideos(vfrFile, outPath, -1);
|
||||
await FfmpegCommands.ConcatVideos(vfrFile, outPath, -1);
|
||||
await MergeAudio(i.current.inPath, outPath);
|
||||
|
||||
int looptimes = GetLoopTimes();
|
||||
if (looptimes > 0)
|
||||
await Loop(outPath, looptimes);
|
||||
await Loop(outPath, GetLoopTimes());
|
||||
}
|
||||
|
||||
public static async Task EncodeChunk(string outPath, i.OutMode mode, int firstFrameNum, int framesAmount)
|
||||
{
|
||||
string vfrFileOriginal = Path.Combine(i.current.tempFolder, $"vfr-{i.current.interpFactor}x.ini");
|
||||
string vfrFile = Path.Combine(i.current.tempFolder, $"vfr-chunk-{firstFrameNum}-{firstFrameNum + framesAmount}.ini");
|
||||
string vfrFileOriginal = Path.Combine(i.current.tempFolder, Paths.GetFrameOrderFilename(i.current.interpFactor));
|
||||
string vfrFile = Path.Combine(i.current.tempFolder, Paths.GetFrameOrderFilenameChunk(firstFrameNum, firstFrameNum + framesAmount));
|
||||
File.WriteAllLines(vfrFile, IOUtils.ReadLines(vfrFileOriginal).Skip(firstFrameNum).Take(framesAmount));
|
||||
|
||||
float maxFps = Config.GetFloat("maxFps");
|
||||
@@ -172,21 +166,22 @@ namespace Flowframes.Main
|
||||
bool dontEncodeFullFpsVid = fpsLimit && Config.GetInt("maxFpsMode") == 0;
|
||||
|
||||
if(!dontEncodeFullFpsVid)
|
||||
await FFmpegCommands.FramesToVideoConcat(vfrFile, outPath, mode, i.current.outFps, AvProcess.LogMode.Hidden, true); // Encode
|
||||
await FfmpegEncode.FramesToVideoConcat(vfrFile, outPath, mode, i.current.outFps, AvProcess.LogMode.Hidden, true); // Encode
|
||||
|
||||
if (fpsLimit)
|
||||
{
|
||||
string filename = Path.GetFileName(outPath);
|
||||
string newParentDir = outPath.GetParentDir() + "-" + maxFps.ToStringDot("0.00") + "fps";
|
||||
outPath = Path.Combine(newParentDir, filename);
|
||||
await FFmpegCommands.FramesToVideoConcat(vfrFile, outPath, mode, i.current.outFps, maxFps, AvProcess.LogMode.Hidden, true); // Encode with limited fps
|
||||
await FfmpegEncode.FramesToVideoConcat(vfrFile, outPath, mode, i.current.outFps, maxFps, AvProcess.LogMode.Hidden, true); // Encode with limited fps
|
||||
}
|
||||
}
|
||||
|
||||
static async Task Loop(string outPath, int looptimes)
|
||||
{
|
||||
if (looptimes < 1 || !Config.GetBool("enableLoop")) return;
|
||||
Logger.Log($"Looping {looptimes} {(looptimes == 1 ? "time" : "times")} to reach target length of {Config.GetInt("minOutVidLength")}s...");
|
||||
await FFmpegCommands.LoopVideo(outPath, looptimes, Config.GetInt("loopMode") == 0);
|
||||
await FfmpegCommands.LoopVideo(outPath, looptimes, Config.GetInt("loopMode") == 0);
|
||||
}
|
||||
|
||||
static int GetLoopTimes()
|
||||
@@ -194,7 +189,7 @@ namespace Flowframes.Main
|
||||
int times = -1;
|
||||
int minLength = Config.GetInt("minOutVidLength");
|
||||
int minFrameCount = (minLength * i.current.outFps).RoundToInt();
|
||||
int outFrames = i.currentInputFrameCount * i.current.interpFactor;
|
||||
int outFrames = (i.currentInputFrameCount * i.current.interpFactor).RoundToInt();
|
||||
if (outFrames / i.current.outFps < minLength)
|
||||
times = (int)Math.Ceiling((double)minFrameCount / (double)outFrames);
|
||||
times--; // Not counting the 1st play (0 loops)
|
||||
@@ -213,7 +208,7 @@ namespace Flowframes.Main
|
||||
audioFileBasePath = Path.Combine(i.current.tempFolder.GetParentDir(), "audio");
|
||||
|
||||
if (!File.Exists(IOUtils.GetAudioFile(audioFileBasePath)))
|
||||
await FFmpegCommands.ExtractAudio(inputPath, audioFileBasePath); // Extract from sourceVideo to audioFile unless it already exists
|
||||
await FfmpegAudioAndMetadata.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)
|
||||
{
|
||||
@@ -221,7 +216,7 @@ namespace Flowframes.Main
|
||||
return;
|
||||
}
|
||||
|
||||
await FFmpegCommands.MergeAudioAndSubs(outVideo, IOUtils.GetAudioFile(audioFileBasePath), i.current.tempFolder); // Merge from audioFile into outVideo
|
||||
await FfmpegAudioAndMetadata.MergeAudioAndSubs(outVideo, IOUtils.GetAudioFile(audioFileBasePath), i.current.tempFolder); // Merge from audioFile into outVideo
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -14,16 +14,17 @@ namespace Flowframes.Main
|
||||
{
|
||||
class FrameOrder
|
||||
{
|
||||
public enum Mode { CFR, VFR }
|
||||
public static int timebase = 10000;
|
||||
static Stopwatch benchmark = new Stopwatch();
|
||||
|
||||
public static async Task CreateFrameOrderFile(string framesPath, bool loopEnabled, int times)
|
||||
public static async Task CreateFrameOrderFile(string framesPath, bool loopEnabled, float times)
|
||||
{
|
||||
Logger.Log("Generating frame order information...");
|
||||
try
|
||||
{
|
||||
benchmark.Restart();
|
||||
await CreateEncFile(framesPath, loopEnabled, times, false);
|
||||
Logger.Log($"Generating frame order information... Done.", false, true);
|
||||
Logger.Log($"Generated frame order info file in {benchmark.ElapsedMilliseconds} ms", true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -45,7 +46,7 @@ namespace Flowframes.Main
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task CreateEncFile (string framesPath, bool loopEnabled, int 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);
|
||||
@@ -55,7 +56,7 @@ namespace Flowframes.Main
|
||||
string ext = InterpolateUtils.GetOutExt();
|
||||
|
||||
FileInfo[] frameFiles = new DirectoryInfo(framesPath).GetFiles($"*.png");
|
||||
string vfrFile = Path.Combine(framesPath.GetParentDir(), $"vfr-{interpFactor}x.ini");
|
||||
string vfrFile = Path.Combine(framesPath.GetParentDir(), Paths.GetFrameOrderFilename(interpFactor));
|
||||
string fileContent = "";
|
||||
string dupesFile = Path.Combine(framesPath.GetParentDir(), $"dupes.ini");
|
||||
LoadDupesFile(dupesFile);
|
||||
@@ -69,41 +70,32 @@ namespace Flowframes.Main
|
||||
|
||||
bool debug = Config.GetBool("frameOrderDebug", false);
|
||||
|
||||
int interpFramesAmount = (int)interpFactor; // TODO: This code won't work with fractional factors
|
||||
int totalFileCount = 0;
|
||||
|
||||
for (int i = 0; i < (frameFiles.Length - 1); i++)
|
||||
{
|
||||
if (Interpolate.canceled) return;
|
||||
|
||||
int interpFramesAmount = interpFactor;
|
||||
string inputFilenameNoExt = Path.GetFileNameWithoutExtension(frameFiles[i].Name);
|
||||
int dupesAmount = dupesDict.ContainsKey(inputFilenameNoExt) ? dupesDict[inputFilenameNoExt] : 0;
|
||||
|
||||
if(debug) Logger.Log($"{Path.GetFileNameWithoutExtension(frameFiles[i].Name)} has {dupesAmount} dupes", true);
|
||||
|
||||
bool discardThisFrame = (sceneDetection && (i + 2) < frameFiles.Length && sceneFrames.Contains(Path.GetFileNameWithoutExtension(frameFiles[i + 1].Name))); // i+2 is in scene detection folder, means i+1 is ugly interp frame
|
||||
|
||||
// If loop is enabled, account for the extra frame added to the end for loop continuity
|
||||
if (loopEnabled && i == (frameFiles.Length - 2))
|
||||
interpFramesAmount = interpFramesAmount * 2;
|
||||
|
||||
if (debug) Logger.Log($"Writing out frames for in frame {i} which has {dupesAmount} dupes", true);
|
||||
// Generate frames file lines
|
||||
for (int frm = 0; frm < interpFramesAmount; frm++)
|
||||
for (int frm = 0; frm < interpFramesAmount; frm++) // Generate frames file lines
|
||||
{
|
||||
//if (debug) Logger.Log($"Writing out frame {frm+1}/{interpFramesAmount}", true);
|
||||
|
||||
if (discardThisFrame) // If frame is scene cut frame
|
||||
{
|
||||
//if (debug) Logger.Log($"Writing frame {totalFileCount} [Discarding Next]", true);
|
||||
totalFileCount++;
|
||||
int lastNum = totalFileCount;
|
||||
fileContent = WriteFrameWithDupes(dupesAmount, fileContent, totalFileCount, interpPath, ext, debug, $"[In: {inputFilenameNoExt}] [{((frm == 0) ? " Source " : $"Interp {frm}")}] [DiscardNext]");
|
||||
|
||||
//if (debug) Logger.Log("Discarding interp frames with out num " + totalFileCount);
|
||||
for (int dupeCount = 1; dupeCount < interpFramesAmount; dupeCount++)
|
||||
{
|
||||
totalFileCount++;
|
||||
if (debug) Logger.Log($"Writing frame {totalFileCount} which is actually repeated frame {lastNum}", true);
|
||||
fileContent = WriteFrameWithDupes(dupesAmount, fileContent, lastNum, interpPath, ext, debug, $"[In: {inputFilenameNoExt}] [DISCARDED]");
|
||||
}
|
||||
|
||||
@@ -116,20 +108,21 @@ namespace Flowframes.Main
|
||||
}
|
||||
}
|
||||
|
||||
if ((i + 1) % 100 == 0)
|
||||
if (i % 250 == 0)
|
||||
{
|
||||
if (i % 1000 == 0)
|
||||
Logger.Log($"Generating frame order information... {i}/{frameFiles.Length}.", false, true);
|
||||
await Task.Delay(1);
|
||||
}
|
||||
|
||||
// if(debug) Logger.Log("target: " + ((frameFiles.Length * interpFactor) - (interpFactor - 1)), true);
|
||||
// if(debug) Logger.Log("totalFileCount: " + totalFileCount, true);
|
||||
}
|
||||
|
||||
totalFileCount++;
|
||||
fileContent += $"file '{interpPath}/{totalFileCount.ToString().PadLeft(Padding.interpFrames, '0')}.{ext}'\n";
|
||||
|
||||
string finalFileContent = fileContent.Trim();
|
||||
if(loop)
|
||||
finalFileContent = finalFileContent.Remove(finalFileContent.LastIndexOf("\n"));
|
||||
File.WriteAllText(vfrFile, finalFileContent);
|
||||
fileContent = fileContent.Remove(fileContent.LastIndexOf("\n"));
|
||||
|
||||
File.WriteAllText(vfrFile, fileContent);
|
||||
|
||||
if (notFirstRun) return; // Skip all steps that only need to be done once
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Flowframes;
|
||||
using Flowframes.Media;
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Magick;
|
||||
@@ -25,68 +26,81 @@ namespace Flowframes
|
||||
|
||||
public static int currentInputFrameCount;
|
||||
public static bool currentlyUsingAutoEnc;
|
||||
|
||||
public static InterpSettings current;
|
||||
|
||||
public static bool canceled = false;
|
||||
|
||||
static Stopwatch sw = new Stopwatch();
|
||||
|
||||
public static async Task Start()
|
||||
{
|
||||
if (!BatchProcessing.busy && Program.busy) return;
|
||||
canceled = false;
|
||||
if (!Utils.InputIsValid(current.inPath, current.outPath, current.outFps, current.interpFactor, current.outMode)) return; // General input checks
|
||||
if (!Utils.CheckAiAvailable(current.ai)) return; // Check if selected AI pkg is installed
|
||||
if (!Utils.CheckDeleteOldTempFolder()) return; // Try to delete temp folder if an old one exists
|
||||
if (!ResumeUtils.resumeNextRun && !Utils.CheckDeleteOldTempFolder()) return; // Try to delete temp folder if an old one exists
|
||||
if (!Utils.CheckPathValid(current.inPath)) return; // Check if input path/file is valid
|
||||
Utils.PathAsciiCheck(current.inPath, current.outPath);
|
||||
Utils.PathAsciiCheck(current.outPath, "output path");
|
||||
currentInputFrameCount = await Utils.GetInputFrameCountAsync(current.inPath);
|
||||
current.stepByStep = false;
|
||||
Program.mainForm.SetStatus("Starting...");
|
||||
Program.mainForm.SetWorking(true);
|
||||
|
||||
if (!ResumeUtils.resumeNextRun)
|
||||
{
|
||||
await GetFrames();
|
||||
if (canceled) return;
|
||||
sw.Restart();
|
||||
await PostProcessFrames();
|
||||
await PostProcessFrames(false);
|
||||
}
|
||||
|
||||
if (canceled) return;
|
||||
await ResumeUtils.PrepareResumedRun();
|
||||
//Task.Run(() => Utils.DeleteInterpolatedInputFrames());
|
||||
await RunAi(current.interpFolder, current.ai);
|
||||
if (canceled) return;
|
||||
Program.mainForm.SetProgress(100);
|
||||
if(!currentlyUsingAutoEnc)
|
||||
await CreateVideo.Export(current.interpFolder, current.outFilename, current.outMode);
|
||||
IOUtils.ReverseRenaming(AiProcess.filenameMap, true); // Get timestamps back
|
||||
Cleanup(current.interpFolder);
|
||||
await CreateVideo.Export(current.interpFolder, current.outFilename, current.outMode, false);
|
||||
await IOUtils.ReverseRenaming(current.framesFolder, AiProcess.filenameMap); // Get timestamps back
|
||||
AiProcess.filenameMap.Clear();
|
||||
await Cleanup();
|
||||
Program.mainForm.SetWorking(false);
|
||||
Logger.Log("Total processing time: " + FormatUtils.Time(sw.Elapsed));
|
||||
sw.Stop();
|
||||
Program.mainForm.SetStatus("Done interpolating!");
|
||||
}
|
||||
|
||||
public static async Task GetFrames ()
|
||||
public static async Task GetFrames (bool stepByStep = false)
|
||||
{
|
||||
if (!current.inputIsFrames) // Input is video - extract frames first
|
||||
{
|
||||
current.alpha = (Config.GetBool("enableAlpha", false) && (Path.GetExtension(current.inPath).ToLower() == ".gif"));
|
||||
await ExtractFrames(current.inPath, current.framesFolder, current.alpha);
|
||||
}
|
||||
current.RefreshAlpha();
|
||||
|
||||
if (!current.inputIsFrames) // Extract if input is video, import if image sequence
|
||||
await ExtractFrames(current.inPath, current.framesFolder, current.alpha, !stepByStep);
|
||||
else
|
||||
await FfmpegExtract.ImportImages(current.inPath, current.framesFolder, current.alpha, await Utils.GetOutputResolution(current.inPath, true));
|
||||
|
||||
if (current.alpha)
|
||||
{
|
||||
current.alpha = (Config.GetBool("enableAlpha", false) && Path.GetExtension(IOUtils.GetFilesSorted(current.inPath).First()).ToLower() == ".gif");
|
||||
await FFmpegCommands.ImportImages(current.inPath, current.framesFolder, current.alpha, await Utils.GetOutputResolution(current.inPath, true));
|
||||
Program.mainForm.SetStatus("Extracting transparency...");
|
||||
Logger.Log("Extracting transparency... (1/2)");
|
||||
await FfmpegAlpha.ExtractAlphaDir(current.framesFolder, current.framesFolder + Paths.alphaSuffix);
|
||||
Logger.Log("Extracting transparency... (2/2)", false, true);
|
||||
await FfmpegAlpha.RemoveAlpha(current.framesFolder, current.framesFolder);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ExtractFrames(string inPath, string outPath, bool alpha, bool allowSceneDetect = true, bool extractAudio = true)
|
||||
public static async Task ExtractFrames(string inPath, string outPath, bool alpha, bool sceneDetect)
|
||||
{
|
||||
if (Config.GetBool("scnDetect"))
|
||||
if (sceneDetect && Config.GetBool("scnDetect"))
|
||||
{
|
||||
Program.mainForm.SetStatus("Extracting scenes from video...");
|
||||
await FFmpegCommands.ExtractSceneChanges(inPath, Path.Combine(current.tempFolder, Paths.scenesDir), current.inFps);
|
||||
await FfmpegExtract.ExtractSceneChanges(inPath, Path.Combine(current.tempFolder, Paths.scenesDir), current.inFps);
|
||||
await Task.Delay(10);
|
||||
}
|
||||
|
||||
if (canceled) return;
|
||||
Program.mainForm.SetStatus("Extracting frames from video...");
|
||||
bool mpdecimate = Config.GetInt("dedupMode") == 2;
|
||||
await FFmpegCommands.VideoToFrames(inPath, outPath, alpha, current.inFps, mpdecimate, false, await Utils.GetOutputResolution(inPath, true), false);
|
||||
await FfmpegExtract.VideoToFrames(inPath, outPath, alpha, current.inFps, mpdecimate, false, await Utils.GetOutputResolution(inPath, true, true));
|
||||
|
||||
if (mpdecimate)
|
||||
{
|
||||
@@ -100,17 +114,19 @@ namespace Flowframes
|
||||
if(!Config.GetBool("allowConsecutiveSceneChanges", true))
|
||||
Utils.FixConsecutiveSceneFrames(Path.Combine(current.tempFolder, Paths.scenesDir), current.framesFolder);
|
||||
|
||||
if (extractAudio)
|
||||
{
|
||||
if (canceled) return;
|
||||
Program.mainForm.SetStatus("Extracting audio from video...");
|
||||
string audioFile = Path.Combine(current.tempFolder, "audio");
|
||||
|
||||
if (audioFile != null && !File.Exists(audioFile))
|
||||
await FFmpegCommands.ExtractAudio(inPath, audioFile);
|
||||
await FfmpegAudioAndMetadata.ExtractAudio(inPath, audioFile);
|
||||
|
||||
if (canceled) return;
|
||||
Program.mainForm.SetStatus("Extracting subtitles from video...");
|
||||
await FfmpegAudioAndMetadata.ExtractSubtitles(inPath, current.tempFolder, current.outMode);
|
||||
}
|
||||
|
||||
await FFmpegCommands.ExtractSubtitles(inPath, current.tempFolder, current.outMode);
|
||||
}
|
||||
|
||||
public static async Task PostProcessFrames (bool sbsMode = false)
|
||||
public static async Task PostProcessFrames (bool stepByStep)
|
||||
{
|
||||
if (canceled) return;
|
||||
|
||||
@@ -136,14 +152,22 @@ namespace Flowframes
|
||||
|
||||
if (canceled) return;
|
||||
|
||||
if(current.alpha)
|
||||
await Converter.ExtractAlpha(current.framesFolder, current.framesFolder + "-a");
|
||||
|
||||
await FrameOrder.CreateFrameOrderFile(current.framesFolder, Config.GetBool("enableLoop"), current.interpFactor);
|
||||
|
||||
if (canceled) return;
|
||||
|
||||
AiProcess.filenameMap = IOUtils.RenameCounterDirReversible(current.framesFolder, "png", 1, 8);
|
||||
try
|
||||
{
|
||||
Dictionary<string, string> renamedFilesDict = await IOUtils.RenameCounterDirReversibleAsync(current.framesFolder, "png", 1, Padding.inputFramesRenamed);
|
||||
|
||||
if(stepByStep)
|
||||
AiProcess.filenameMap = renamedFilesDict.ToDictionary(x => Path.GetFileName(x.Key), x => Path.GetFileName(x.Value)); // Save rel paths
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"Error renaming frame files: {e.Message}");
|
||||
Cancel("Error renaming frame files. Check the log for details.");
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task RunAi(string outpath, AI ai, bool stepByStep = false)
|
||||
@@ -162,7 +186,7 @@ namespace Flowframes
|
||||
tasks.Add(AiProcess.RunRifeCuda(current.framesFolder, current.interpFactor, current.model));
|
||||
|
||||
if (ai.aiName == Networks.rifeNcnn.aiName)
|
||||
tasks.Add(AiProcess.RunRifeNcnn(current.framesFolder, outpath, current.interpFactor, current.model));
|
||||
tasks.Add(AiProcess.RunRifeNcnn(current.framesFolder, outpath, (int)current.interpFactor, current.model));
|
||||
|
||||
if (ai.aiName == Networks.dainNcnn.aiName)
|
||||
tasks.Add(AiProcess.RunDainNcnn(current.framesFolder, outpath, current.interpFactor, current.model, Config.GetInt("dainNcnnTilesize", 512)));
|
||||
@@ -186,30 +210,44 @@ namespace Flowframes
|
||||
canceled = true;
|
||||
Program.mainForm.SetStatus("Canceled.");
|
||||
Program.mainForm.SetProgress(0);
|
||||
if (Config.GetInt("processingMode") == 0 && !Config.GetBool("keepTempFolder"))
|
||||
if (!current.stepByStep && !Config.GetBool("keepTempFolder"))
|
||||
{
|
||||
if(false /* IOUtils.GetAmountOfFiles(Path.Combine(current.tempFolder, Paths.resumeDir), true) > 0 */) // TODO: Uncomment for 1.23
|
||||
{
|
||||
DialogResult dialogResult = MessageBox.Show($"Delete the temp folder (Yes) or keep it for resuming later (No)?", "Delete temporary files?", MessageBoxButtons.YesNo);
|
||||
if (dialogResult == DialogResult.Yes)
|
||||
IOUtils.TryDeleteIfExists(current.tempFolder);
|
||||
}
|
||||
else
|
||||
{
|
||||
IOUtils.TryDeleteIfExists(current.tempFolder);
|
||||
}
|
||||
}
|
||||
AutoEncode.busy = false;
|
||||
Program.mainForm.SetWorking(false);
|
||||
Program.mainForm.SetTab("interpolation");
|
||||
if(!Logger.GetLastLine().Contains("Canceled interpolation."))
|
||||
Logger.Log("Canceled interpolation.");
|
||||
Logger.LogIfLastLineDoesNotContainMsg("Canceled interpolation.");
|
||||
if (!string.IsNullOrWhiteSpace(reason) && !noMsgBox)
|
||||
Utils.ShowMessage($"Canceled:\n\n{reason}");
|
||||
}
|
||||
|
||||
public static void Cleanup(string interpFramesDir, bool ignoreKeepSetting = false)
|
||||
public static async Task Cleanup(bool ignoreKeepSetting = false, int retriesLeft = 3, bool isRetry = false)
|
||||
{
|
||||
if (!ignoreKeepSetting && Config.GetBool("keepTempFolder")) return;
|
||||
if ((!ignoreKeepSetting && Config.GetBool("keepTempFolder")) || !Program.busy) return;
|
||||
if (!isRetry)
|
||||
Logger.Log("Deleting temporary files...");
|
||||
try
|
||||
{
|
||||
if (Config.GetBool("keepFrames"))
|
||||
IOUtils.Copy(interpFramesDir, Path.Combine(current.tempFolder.GetParentDir(), Path.GetFileName(current.tempFolder).Replace("-temp", "-interpframes")));
|
||||
Directory.Delete(current.tempFolder, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log("Cleanup Error: " + e.Message, true);
|
||||
if(retriesLeft > 0)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
await Cleanup(ignoreKeepSetting, retriesLeft - 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Flowframes.AudioVideo;
|
||||
using Flowframes.Media;
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using System;
|
||||
@@ -17,18 +17,14 @@ namespace Flowframes.Main
|
||||
{
|
||||
public enum Step { ExtractScnChanges, ExtractFrames, Interpolate, CreateVid, Reset }
|
||||
|
||||
//public static string current.inPath;
|
||||
//public static string currentOutPath;
|
||||
//public static string current.interpFolder;
|
||||
//public static AI currentAi;
|
||||
//public static OutMode currentOutMode;
|
||||
|
||||
public static async Task Run(string step)
|
||||
{
|
||||
Logger.Log($"[SBS] Running step '{step}'", true);
|
||||
canceled = false;
|
||||
Program.mainForm.SetWorking(true);
|
||||
current = Program.mainForm.GetCurrentSettings();
|
||||
current.RefreshAlpha();
|
||||
current.stepByStep = false;
|
||||
|
||||
if (!InterpolateUtils.InputIsValid(current.inPath, current.outPath, current.outFps, current.interpFactor, current.outMode)) return; // General input checks
|
||||
|
||||
@@ -41,9 +37,7 @@ namespace Flowframes.Main
|
||||
}
|
||||
|
||||
if (step.Contains("Extract Frames"))
|
||||
{
|
||||
await GetFrames();
|
||||
}
|
||||
await ExtractFramesStep();
|
||||
|
||||
if (step.Contains("Run Interpolation"))
|
||||
await DoInterpolate();
|
||||
@@ -55,6 +49,7 @@ namespace Flowframes.Main
|
||||
await Reset();
|
||||
|
||||
Program.mainForm.SetWorking(false);
|
||||
Program.mainForm.SetStatus("Done running step.");
|
||||
Logger.Log("Done running this step.");
|
||||
}
|
||||
|
||||
@@ -67,11 +62,11 @@ namespace Flowframes.Main
|
||||
return;
|
||||
}
|
||||
Program.mainForm.SetStatus("Extracting scenes from video...");
|
||||
await FFmpegCommands.ExtractSceneChanges(current.inPath, scenesPath, current.inFps);
|
||||
await FfmpegExtract.ExtractSceneChanges(current.inPath, scenesPath, current.inFps);
|
||||
await Task.Delay(10);
|
||||
}
|
||||
|
||||
public static async Task ExtractVideoFrames()
|
||||
public static async Task ExtractFramesStep()
|
||||
{
|
||||
if (!IOUtils.TryDeleteIfExists(current.framesFolder))
|
||||
{
|
||||
@@ -82,12 +77,13 @@ namespace Flowframes.Main
|
||||
currentInputFrameCount = await InterpolateUtils.GetInputFrameCountAsync(current.inPath);
|
||||
AiProcess.filenameMap.Clear();
|
||||
|
||||
await ExtractFrames(current.inPath, current.framesFolder, false, true);
|
||||
await GetFrames(true);
|
||||
}
|
||||
|
||||
public static async Task DoInterpolate()
|
||||
{
|
||||
current.framesFolder = Path.Combine(current.tempFolder, Paths.framesDir);
|
||||
|
||||
if (!Directory.Exists(current.framesFolder) || IOUtils.GetAmountOfFiles(current.framesFolder, false, "*.png") < 2)
|
||||
{
|
||||
InterpolateUtils.ShowMessage("There are no extracted frames that can be interpolated!\nDid you run the extraction step?", "Error");
|
||||
@@ -101,28 +97,24 @@ namespace Flowframes.Main
|
||||
|
||||
currentInputFrameCount = await InterpolateUtils.GetInputFrameCountAsync(current.inPath);
|
||||
|
||||
foreach (string ini in Directory.GetFiles(current.tempFolder, "*.ini", SearchOption.TopDirectoryOnly))
|
||||
IOUtils.TryDeleteIfExists(ini);
|
||||
|
||||
IOUtils.ReverseRenaming(AiProcess.filenameMap, true); // Get timestamps back
|
||||
|
||||
// TODO: Check if this works lol, remove if it does
|
||||
//if (Config.GetBool("sbsAllowAutoEnc"))
|
||||
// nextOutPath = Path.Combine(currentOutPath, Path.GetFileNameWithoutExtension(current.inPath) + IOUtils.GetAiSuffix(current.ai, current.interpFactor) + InterpolateUtils.GetExt(current.outMode));
|
||||
|
||||
await PostProcessFrames(true);
|
||||
|
||||
int frames = IOUtils.GetAmountOfFiles(current.framesFolder, false, "*.png");
|
||||
int targetFrameCount = frames * current.interpFactor;
|
||||
if (canceled) return;
|
||||
Program.mainForm.SetStatus("Running AI...");
|
||||
await RunAi(current.interpFolder, current.ai, true);
|
||||
await IOUtils.ReverseRenaming(current.framesFolder, AiProcess.filenameMap); // Get timestamps back
|
||||
AiProcess.filenameMap.Clear();
|
||||
Program.mainForm.SetProgress(0);
|
||||
}
|
||||
|
||||
public static async Task CreateOutputVid()
|
||||
{
|
||||
string[] outFrames = IOUtils.GetFilesSorted(current.interpFolder, $"*.{InterpolateUtils.GetOutExt()}");
|
||||
|
||||
if (outFrames.Length > 0 && !IOUtils.CheckImageValid(outFrames[0]))
|
||||
{
|
||||
InterpolateUtils.ShowMessage("Invalid frame files detected!\n\nIf you used Auto-Encode, this is normal, and you don't need to run " +
|
||||
@@ -131,12 +123,12 @@ namespace Flowframes.Main
|
||||
}
|
||||
|
||||
string outPath = Path.Combine(current.outPath, Path.GetFileNameWithoutExtension(current.inPath) + IOUtils.GetCurrentExportSuffix() + FFmpegUtils.GetExt(current.outMode));
|
||||
await CreateVideo.Export(current.interpFolder, outPath, current.outMode);
|
||||
await CreateVideo.Export(current.interpFolder, outPath, current.outMode, true);
|
||||
}
|
||||
|
||||
public static async Task Reset()
|
||||
{
|
||||
Cleanup(current.interpFolder, true);
|
||||
await Cleanup(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Flowframes.Data;
|
||||
using Flowframes.Media;
|
||||
using Flowframes.Data;
|
||||
using Flowframes.Forms;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.MiscUtils;
|
||||
@@ -37,11 +38,11 @@ namespace Flowframes.Main
|
||||
if (frameFolderInput)
|
||||
{
|
||||
string lastFramePath = IOUtils.GetFilesSorted(i.current.inPath, false).Last();
|
||||
await FFmpegCommands.ExtractLastFrame(lastFramePath, targetPath, res);
|
||||
await FfmpegExtract.ExtractLastFrame(lastFramePath, targetPath, res);
|
||||
}
|
||||
else
|
||||
{
|
||||
await FFmpegCommands.ExtractLastFrame(i.current.inPath, targetPath, res);
|
||||
await FfmpegExtract.ExtractLastFrame(i.current.inPath, targetPath, res);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -60,7 +61,7 @@ namespace Flowframes.Main
|
||||
|
||||
public static int targetFrames;
|
||||
public static string currentOutdir;
|
||||
public static int currentFactor;
|
||||
public static float currentFactor;
|
||||
public static bool progressPaused = false;
|
||||
public static bool progCheckRunning = false;
|
||||
public static async void GetProgressByFrameAmount(string outdir, int target)
|
||||
@@ -95,10 +96,13 @@ namespace Flowframes.Main
|
||||
Program.mainForm.SetProgress(0);
|
||||
}
|
||||
|
||||
public static int interpolatedInputFramesCount;
|
||||
|
||||
public static void UpdateInterpProgress(int frames, int target, string latestFramePath = "")
|
||||
{
|
||||
if (i.canceled) return;
|
||||
|
||||
interpolatedInputFramesCount = ((frames / i.current.interpFactor).RoundToInt() - 1);
|
||||
ResumeUtils.Save();
|
||||
frames = frames.Clamp(0, target);
|
||||
int percent = (int)Math.Round(((float)frames / target) * 100f);
|
||||
Program.mainForm.SetProgress(percent);
|
||||
@@ -132,11 +136,28 @@ namespace Flowframes.Main
|
||||
catch { }
|
||||
}
|
||||
|
||||
public static async Task DeleteInterpolatedInputFrames ()
|
||||
{
|
||||
interpolatedInputFramesCount = 0;
|
||||
string[] inputFrames = IOUtils.GetFilesSorted(i.current.framesFolder);
|
||||
|
||||
for (int i = 0; i < inputFrames.Length; i++)
|
||||
{
|
||||
while (Program.busy && (i + 10) > interpolatedInputFramesCount) await Task.Delay(1000);
|
||||
if (!Program.busy) break;
|
||||
if(i != 0 && i != inputFrames.Length - 1)
|
||||
IOUtils.OverwriteFileWithText(inputFrames[i]);
|
||||
if (i % 10 == 0) await Task.Delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetPreviewImg (Image img)
|
||||
{
|
||||
if (img == null)
|
||||
return;
|
||||
|
||||
preview.Image = img;
|
||||
|
||||
if (bigPreviewForm != null)
|
||||
bigPreviewForm.SetImage(img);
|
||||
}
|
||||
@@ -144,7 +165,14 @@ namespace Flowframes.Main
|
||||
public static Dictionary<string, int> frameCountCache = new Dictionary<string, int>();
|
||||
public static async Task<int> GetInputFrameCountAsync (string path)
|
||||
{
|
||||
string hash = await IOUtils.GetHashAsync(path, IOUtils.Hash.xxHash); // Get checksum for caching
|
||||
int maxMb = Config.GetInt("storeHashedFramecountMaxSizeMb", 256);
|
||||
string hash = "";
|
||||
|
||||
if (IOUtils.GetFilesize(path) >= 0 && IOUtils.GetFilesize(path) < maxMb * 1024 * 1024)
|
||||
hash = await IOUtils.GetHashAsync(path, IOUtils.Hash.xxHash); // Get checksum for caching
|
||||
else
|
||||
Logger.Log($"GetInputFrameCountAsync: File bigger than {maxMb}mb, won't hash.", true);
|
||||
|
||||
if (hash.Length > 1 && frameCountCache.ContainsKey(hash))
|
||||
{
|
||||
Logger.Log($"FrameCountCache contains this hash ({hash}), using cached frame count.", true);
|
||||
@@ -156,16 +184,18 @@ namespace Flowframes.Main
|
||||
}
|
||||
|
||||
int frameCount = 0;
|
||||
|
||||
if (IOUtils.IsPathDirectory(path))
|
||||
frameCount = IOUtils.GetAmountOfFiles(path, false);
|
||||
else
|
||||
frameCount = await FFmpegCommands.GetFrameCountAsync(path);
|
||||
frameCount = await FfmpegCommands.GetFrameCountAsync(path);
|
||||
|
||||
if (hash.Length > 1 && frameCount > 5000) // Cache if >5k frames to avoid re-reading it every single time
|
||||
{
|
||||
Logger.Log($"Adding hash ({hash}) with frame count {frameCount} to cache.", true);
|
||||
frameCountCache[hash] = frameCount; // Use CRC32 instead of path to avoid using cached value if file was changed
|
||||
}
|
||||
|
||||
return frameCount;
|
||||
}
|
||||
|
||||
@@ -213,7 +243,7 @@ namespace Flowframes.Main
|
||||
return Path.Combine(basePath, Path.GetFileNameWithoutExtension(inPath).StripBadChars().Remove(" ").Trunc(30, false) + "-temp");
|
||||
}
|
||||
|
||||
public static bool InputIsValid(string inDir, string outDir, float fpsOut, int interp, Interpolate.OutMode outMode)
|
||||
public static bool InputIsValid(string inDir, string outDir, float fpsOut, float factor, Interpolate.OutMode outMode)
|
||||
{
|
||||
bool passes = true;
|
||||
|
||||
@@ -229,7 +259,7 @@ namespace Flowframes.Main
|
||||
ShowMessage("Output path is not valid!");
|
||||
passes = false;
|
||||
}
|
||||
if (passes && interp != 2 && interp != 4 && interp != 8)
|
||||
if (passes && /*factor != 2 && factor != 4 && factor != 8*/ factor > 16)
|
||||
{
|
||||
ShowMessage("Interpolation factor is not valid!");
|
||||
passes = false;
|
||||
@@ -239,9 +269,9 @@ namespace Flowframes.Main
|
||||
ShowMessage("Invalid output frame rate!\nGIF does not properly support frame rates above 40 FPS.\nPlease use MP4, WEBM or another video format.");
|
||||
passes = false;
|
||||
}
|
||||
if (passes && fpsOut < 1 || fpsOut > 500)
|
||||
if (passes && fpsOut < 1 || fpsOut > 1000)
|
||||
{
|
||||
ShowMessage("Invalid output frame rate - Must be 1-500.");
|
||||
ShowMessage("Invalid output frame rate - Must be 1-1000.");
|
||||
passes = false;
|
||||
}
|
||||
if (!passes)
|
||||
@@ -249,18 +279,10 @@ namespace Flowframes.Main
|
||||
return passes;
|
||||
}
|
||||
|
||||
public static void PathAsciiCheck (string inpath, string outpath)
|
||||
public static void PathAsciiCheck (string path, string pathTitle)
|
||||
{
|
||||
bool shownMsg = false;
|
||||
|
||||
if (OSUtils.HasNonAsciiChars(inpath))
|
||||
{
|
||||
ShowMessage("Warning: Input path includes non-ASCII characters. This might cause problems.");
|
||||
shownMsg = true;
|
||||
}
|
||||
|
||||
if (!shownMsg && OSUtils.HasNonAsciiChars(outpath))
|
||||
ShowMessage("Warning: Output path includes non-ASCII characters. This might cause problems.");
|
||||
if (IOUtils.HasBadChars(path) || OSUtils.HasNonAsciiChars(path))
|
||||
ShowMessage($"Warning: Your {pathTitle} includes special characters. This might cause problems.");
|
||||
}
|
||||
|
||||
public static void GifCompatCheck (Interpolate.OutMode outMode, float fpsOut, int targetFrameCount)
|
||||
@@ -327,9 +349,6 @@ namespace Flowframes.Main
|
||||
{
|
||||
if (videoPath == null || !IOUtils.IsFileValid(videoPath))
|
||||
return false;
|
||||
// string ext = Path.GetExtension(videoPath).ToLower();
|
||||
// if (!Formats.supported.Contains(ext))
|
||||
// return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -340,13 +359,13 @@ namespace Flowframes.Main
|
||||
Logger.Log("Message: " + msg, true);
|
||||
}
|
||||
|
||||
public static async Task<Size> GetOutputResolution (string inputPath, bool print)
|
||||
public static async Task<Size> GetOutputResolution (string inputPath, bool print, bool returnZeroIfUnchanged = false)
|
||||
{
|
||||
Size resolution = await IOUtils.GetVideoOrFramesRes(inputPath);
|
||||
return GetOutputResolution(resolution, print);
|
||||
return GetOutputResolution(resolution, print, returnZeroIfUnchanged);
|
||||
}
|
||||
|
||||
public static Size GetOutputResolution(Size inputRes, bool print = false)
|
||||
public static Size GetOutputResolution(Size inputRes, bool print = false, bool returnZeroIfUnchanged = false)
|
||||
{
|
||||
int maxHeight = RoundDiv2(Config.GetInt("maxVidHeight"));
|
||||
if (inputRes.Height > maxHeight)
|
||||
@@ -360,7 +379,11 @@ namespace Flowframes.Main
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Size(RoundDiv2(inputRes.Width), RoundDiv2(inputRes.Height));
|
||||
//return new Size(RoundDiv2(inputRes.Width), RoundDiv2(inputRes.Height));
|
||||
if (returnZeroIfUnchanged)
|
||||
return new Size();
|
||||
else
|
||||
return inputRes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
120
Code/Main/ResumeUtils.cs
Normal file
120
Code/Main/ResumeUtils.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using I = Flowframes.Interpolate;
|
||||
|
||||
namespace Flowframes.Main
|
||||
{
|
||||
|
||||
class ResumeUtils
|
||||
{
|
||||
public static bool resumeNextRun;
|
||||
|
||||
public static float timeBetweenSaves = 10;
|
||||
public static int minFrames = 100;
|
||||
public static int safetyDelayFrames = 50;
|
||||
public static string resumeFilename = "resume.ini";
|
||||
public static string interpSettingsFilename = "interpSettings.ini";
|
||||
public static string filenameMapFilename = "frameFilenames.ini";
|
||||
|
||||
public static Stopwatch timeSinceLastSave = new Stopwatch();
|
||||
|
||||
public static void Save ()
|
||||
{
|
||||
if (timeSinceLastSave.IsRunning && timeSinceLastSave.ElapsedMilliseconds < (timeBetweenSaves * 1000f).RoundToInt()) return;
|
||||
int frames = (int)Math.Round((float)InterpolateUtils.interpolatedInputFramesCount / I.current.interpFactor) - safetyDelayFrames;
|
||||
if (frames < 1) return;
|
||||
timeSinceLastSave.Restart();
|
||||
Directory.CreateDirectory(Path.Combine(I.current.tempFolder, Paths.resumeDir));
|
||||
SaveState(frames);
|
||||
SaveInterpSettings();
|
||||
SaveFilenameMap();
|
||||
}
|
||||
|
||||
static void SaveState (int frames)
|
||||
{
|
||||
ResumeState state = new ResumeState(I.currentlyUsingAutoEnc, frames);
|
||||
string filePath = Path.Combine(I.current.tempFolder, Paths.resumeDir, resumeFilename);
|
||||
File.WriteAllText(filePath, state.ToString());
|
||||
}
|
||||
|
||||
static async Task SaveFilenameMap ()
|
||||
{
|
||||
string filePath = Path.Combine(I.current.tempFolder, Paths.resumeDir, filenameMapFilename);
|
||||
|
||||
if (File.Exists(filePath) && IOUtils.GetFilesize(filePath) > 0)
|
||||
return;
|
||||
|
||||
string fileContent = "";
|
||||
int counter = 0;
|
||||
|
||||
foreach (KeyValuePair<string, string> entry in AiProcess.filenameMap)
|
||||
{
|
||||
if (counter % 1000 == 0) await Task.Delay(1);
|
||||
fileContent += $"{entry.Key}|{entry.Value}\n";
|
||||
counter++;
|
||||
}
|
||||
|
||||
File.WriteAllText(filePath, fileContent);
|
||||
}
|
||||
|
||||
static void SaveInterpSettings ()
|
||||
{
|
||||
string filepath = Path.Combine(I.current.tempFolder, Paths.resumeDir, interpSettingsFilename);
|
||||
File.WriteAllText(filepath, I.current.Serialize());
|
||||
}
|
||||
|
||||
public static void LoadTempFolder (string tempFolderPath)
|
||||
{
|
||||
string resumeFolderPath = Path.Combine(tempFolderPath, Paths.resumeDir);
|
||||
string interpSettingsPath = Path.Combine(resumeFolderPath, interpSettingsFilename);
|
||||
InterpSettings interpSettings = new InterpSettings(File.ReadAllText(interpSettingsPath));
|
||||
Program.mainForm.LoadBatchEntry(interpSettings);
|
||||
}
|
||||
|
||||
public static async Task PrepareResumedRun ()
|
||||
{
|
||||
if (!resumeNextRun) return;
|
||||
|
||||
string stateFilepath = Path.Combine(I.current.tempFolder, Paths.resumeDir, resumeFilename);
|
||||
ResumeState state = new ResumeState(File.ReadAllText(stateFilepath));
|
||||
|
||||
string fileMapFilepath = Path.Combine(I.current.tempFolder, Paths.resumeDir, filenameMapFilename);
|
||||
List<string> inputFrameLines = File.ReadAllLines(fileMapFilepath).Where(l => l.Trim().Length > 3).ToList();
|
||||
List<string> inputFrames = inputFrameLines.Select(l => Path.Combine(I.current.framesFolder, l.Split('|')[1])).ToList();
|
||||
|
||||
for (int i = 0; i < state.interpolatedInputFrames; i++)
|
||||
{
|
||||
IOUtils.TryDeleteIfExists(inputFrames[i]);
|
||||
if (i % 1000 == 0) await Task.Delay(1);
|
||||
}
|
||||
|
||||
Directory.Move(I.current.interpFolder, I.current.interpFolder + Paths.prevSuffix); // Move existing interp frames
|
||||
Directory.CreateDirectory(I.current.interpFolder); // Re-create empty interp folder
|
||||
|
||||
LoadFilenameMap();
|
||||
}
|
||||
|
||||
static void LoadFilenameMap()
|
||||
{
|
||||
Dictionary<string, string> dict = new Dictionary<string, string>();
|
||||
string filePath = Path.Combine(I.current.tempFolder, Paths.resumeDir, filenameMapFilename);
|
||||
string[] dictLines = File.ReadAllLines(filePath);
|
||||
|
||||
foreach (string line in dictLines)
|
||||
{
|
||||
if (line.Length < 5) continue;
|
||||
string[] keyValuePair = line.Split('|');
|
||||
dict.Add(keyValuePair[0].Trim(), keyValuePair[1].Trim());
|
||||
}
|
||||
|
||||
AiProcess.filenameMap = dict;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,16 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Flowframes.MiscUtils;
|
||||
|
||||
namespace Flowframes
|
||||
{
|
||||
class AvProcess
|
||||
{
|
||||
public static Process lastProcess;
|
||||
public static Stopwatch timeSinceLastOutput = new Stopwatch();
|
||||
public enum TaskType { ExtractFrames, Encode, GetInfo, Merge, Other };
|
||||
public static TaskType lastTask = TaskType.Other;
|
||||
|
||||
@@ -21,17 +24,20 @@ namespace Flowframes
|
||||
|
||||
public enum LogMode { Visible, OnlyLastLine, Hidden }
|
||||
static LogMode currentLogMode;
|
||||
static bool showProgressBar;
|
||||
|
||||
public static async Task RunFfmpeg(string args, LogMode logMode, TaskType taskType = TaskType.Other)
|
||||
public static async Task RunFfmpeg(string args, LogMode logMode, TaskType taskType = TaskType.Other, bool progressBar = false)
|
||||
{
|
||||
await RunFfmpeg(args, "", logMode, taskType);
|
||||
await RunFfmpeg(args, "", logMode, taskType, progressBar);
|
||||
}
|
||||
|
||||
public static async Task RunFfmpeg(string args, string workingDir, LogMode logMode, TaskType taskType = TaskType.Other)
|
||||
public static async Task RunFfmpeg(string args, string workingDir, LogMode logMode, TaskType taskType = TaskType.Other, bool progressBar = false)
|
||||
{
|
||||
lastOutputFfmpeg = "";
|
||||
currentLogMode = logMode;
|
||||
showProgressBar = progressBar;
|
||||
Process ffmpeg = OSUtils.NewProcess(true);
|
||||
timeSinceLastOutput.Restart();
|
||||
lastProcess = ffmpeg;
|
||||
lastTask = taskType;
|
||||
if(!string.IsNullOrWhiteSpace(workingDir))
|
||||
@@ -45,12 +51,17 @@ namespace Flowframes
|
||||
ffmpeg.Start();
|
||||
ffmpeg.BeginOutputReadLine();
|
||||
ffmpeg.BeginErrorReadLine();
|
||||
|
||||
while (!ffmpeg.HasExited)
|
||||
await Task.Delay(1);
|
||||
|
||||
if(progressBar)
|
||||
Program.mainForm.SetProgress(0);
|
||||
}
|
||||
|
||||
static void FfmpegOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
|
||||
{
|
||||
timeSinceLastOutput.Restart();
|
||||
if (outLine == null || outLine.Data == null)
|
||||
return;
|
||||
string line = outLine.Data;
|
||||
@@ -65,10 +76,17 @@ namespace Flowframes
|
||||
|
||||
if (line.Contains("No NVENC capable devices found"))
|
||||
Interpolate.Cancel($"FFmpeg Error: {line}\nMake sure you have an NVENC-capable Nvidia GPU.");
|
||||
|
||||
if (!hidden && showProgressBar && line.Contains("time="))
|
||||
{
|
||||
Regex timeRegex = new Regex("(?<=time=).*(?= )");
|
||||
UpdateFfmpegProgress(timeRegex.Match(line).Value);
|
||||
}
|
||||
}
|
||||
|
||||
static void FfmpegOutputHandlerSilent (object sendingProcess, DataReceivedEventArgs outLine)
|
||||
{
|
||||
timeSinceLastOutput.Restart();
|
||||
if (outLine == null || outLine.Data == null || outLine.Data.Trim().Length < 2)
|
||||
return;
|
||||
string line = outLine.Data;
|
||||
@@ -77,6 +95,13 @@ namespace Flowframes
|
||||
lastOutputFfmpeg += "\n";
|
||||
lastOutputFfmpeg = lastOutputFfmpeg + line;
|
||||
Logger.Log(line, true, false, "ffmpeg");
|
||||
|
||||
if (showProgressBar && line.Contains("time="))
|
||||
{
|
||||
Logger.Log($"showProgressBar, contains: {line.Contains("time=")}", true, false, "ffmpeg");
|
||||
Regex timeRegex = new Regex("(?<=time=).*(?= )");
|
||||
UpdateFfmpegProgress(timeRegex.Match(line).Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetFfmpegOutput (string args)
|
||||
@@ -93,28 +118,25 @@ namespace Flowframes
|
||||
return output;
|
||||
}
|
||||
|
||||
public static async Task<string> GetFfmpegOutputAsync(string args, bool setBusy = false)
|
||||
public static async Task<string> GetFfmpegOutputAsync(string args, bool setBusy = false, bool progressBar = false)
|
||||
{
|
||||
if (Program.busy)
|
||||
setBusy = false;
|
||||
timeSinceLastOutput.Restart();
|
||||
if (Program.busy) setBusy = false;
|
||||
lastOutputFfmpeg = "";
|
||||
showProgressBar = progressBar;
|
||||
Process ffmpeg = OSUtils.NewProcess(true);
|
||||
lastProcess = ffmpeg;
|
||||
ffmpeg.StartInfo.Arguments = $"{GetCmdArg()} cd /D {GetAvDir().Wrap()} & ffmpeg.exe -hide_banner -y -stats {args}";
|
||||
Logger.Log("cmd.exe " + ffmpeg.StartInfo.Arguments, true, false, "ffmpeg");
|
||||
if (setBusy)
|
||||
Program.mainForm.SetWorking(true);
|
||||
ffmpeg.Start();
|
||||
if (setBusy) Program.mainForm.SetWorking(true);
|
||||
ffmpeg.OutputDataReceived += new DataReceivedEventHandler(FfmpegOutputHandlerSilent);
|
||||
ffmpeg.ErrorDataReceived += new DataReceivedEventHandler(FfmpegOutputHandlerSilent);
|
||||
ffmpeg.Start();
|
||||
ffmpeg.BeginOutputReadLine();
|
||||
ffmpeg.BeginErrorReadLine();
|
||||
while (!ffmpeg.HasExited)
|
||||
await Task.Delay(10);
|
||||
if (setBusy)
|
||||
Program.mainForm.SetWorking(false);
|
||||
await Task.Delay(100);
|
||||
while (!ffmpeg.HasExited) await Task.Delay(50);
|
||||
while(timeSinceLastOutput.ElapsedMilliseconds < 200) await Task.Delay(50);
|
||||
if (setBusy) Program.mainForm.SetWorking(false);
|
||||
return lastOutputFfmpeg;
|
||||
}
|
||||
|
||||
@@ -131,47 +153,18 @@ namespace Flowframes
|
||||
return output;
|
||||
}
|
||||
|
||||
public static async Task<string> GetFfprobeOutputAsync(string args)
|
||||
public static void UpdateFfmpegProgress(string ffmpegTime)
|
||||
{
|
||||
Process ffprobe = OSUtils.NewProcess(true);
|
||||
ffprobe.StartInfo.Arguments = $"{GetCmdArg()} cd /D {GetAvDir().Wrap()} & ffprobe.exe {args}";
|
||||
Logger.Log("cmd.exe " + ffprobe.StartInfo.Arguments, true, false, "ffmpeg");
|
||||
ffprobe.Start();
|
||||
while (!ffprobe.HasExited)
|
||||
await Task.Delay(1);
|
||||
string output = ffprobe.StandardOutput.ReadToEnd();
|
||||
string err = ffprobe.StandardError.ReadToEnd();
|
||||
if (!string.IsNullOrWhiteSpace(err)) output += "\n" + err;
|
||||
return output;
|
||||
}
|
||||
|
||||
public static async Task RunGifski(string args, LogMode logMode)
|
||||
if (Program.mainForm.currInDuration < 1)
|
||||
{
|
||||
lastOutputGifski = "";
|
||||
currentLogMode = logMode;
|
||||
Process gifski = OSUtils.NewProcess(true);
|
||||
lastProcess = gifski;
|
||||
gifski.StartInfo.Arguments = $"{GetCmdArg()} cd /D {GetAvDir().Wrap()} & gifski.exe {args}";
|
||||
Logger.Log("Running gifski...");
|
||||
Logger.Log("cmd.exe " + gifski.StartInfo.Arguments, true, false, "ffmpeg");
|
||||
gifski.OutputDataReceived += new DataReceivedEventHandler(OutputHandlerGifski);
|
||||
gifski.ErrorDataReceived += new DataReceivedEventHandler(OutputHandlerGifski);
|
||||
gifski.Start();
|
||||
gifski.BeginOutputReadLine();
|
||||
gifski.BeginErrorReadLine();
|
||||
while (!gifski.HasExited)
|
||||
await Task.Delay(100);
|
||||
Logger.Log("Done running gifski.", true);
|
||||
}
|
||||
|
||||
static void OutputHandlerGifski(object sendingProcess, DataReceivedEventArgs outLine)
|
||||
{
|
||||
if (outLine == null || outLine.Data == null)
|
||||
Program.mainForm.SetProgress(0);
|
||||
return;
|
||||
lastOutputGifski = lastOutputGifski + outLine.Data + "\n";
|
||||
bool hidden = currentLogMode == LogMode.Hidden;
|
||||
bool replaceLastLine = currentLogMode == LogMode.OnlyLastLine;
|
||||
Logger.Log(outLine.Data, hidden, replaceLastLine);
|
||||
}
|
||||
|
||||
long total = Program.mainForm.currInDuration / 100;
|
||||
long current = FormatUtils.MsFromTimestamp(ffmpegTime);
|
||||
int progress = Convert.ToInt32(current / total);
|
||||
Program.mainForm.SetProgress(progress);
|
||||
}
|
||||
|
||||
static string GetAvDir ()
|
||||
@@ -5,7 +5,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flowframes.AudioVideo
|
||||
namespace Flowframes.Media
|
||||
{
|
||||
class FFmpegUtils
|
||||
{
|
||||
@@ -66,12 +66,12 @@ namespace Flowframes.AudioVideo
|
||||
|
||||
if (codec == Codec.NVENC264)
|
||||
{
|
||||
args += $"-crf {Config.GetInt("h264Crf")} -preset default -pix_fmt yuv420p";
|
||||
args += $"-cq {Config.GetInt("h264Crf")} -preset slow -pix_fmt yuv420p";
|
||||
}
|
||||
|
||||
if (codec == Codec.NVENC265)
|
||||
{
|
||||
args += $"-crf {Config.GetInt("h265Crf")} -preset default -pix_fmt yuv420p";
|
||||
args += $"-cq {Config.GetInt("h265Crf")} -preset slow -pix_fmt yuv420p";
|
||||
}
|
||||
|
||||
if (codec == Codec.VP9)
|
||||
@@ -123,7 +123,7 @@ namespace Flowframes.AudioVideo
|
||||
|
||||
public static string GetAudioExt(string videoFile)
|
||||
{
|
||||
switch (FFmpegCommands.GetAudioCodec(videoFile))
|
||||
switch (FfmpegCommands.GetAudioCodec(videoFile))
|
||||
{
|
||||
case "vorbis": return "ogg";
|
||||
case "opus": return "ogg";
|
||||
@@ -137,7 +137,7 @@ namespace Flowframes.AudioVideo
|
||||
{
|
||||
containerExt = containerExt.Remove(".");
|
||||
string codec = "aac";
|
||||
string bitrate = $"{Config.GetInt("aacBitrate", 160)}k";
|
||||
string bitrate = $"{Config.GetInt("aacBitrate", 160)}";
|
||||
|
||||
if(containerExt == "webm" || containerExt == "mkv")
|
||||
{
|
||||
52
Code/Media/FfmpegAlpha.cs
Normal file
52
Code/Media/FfmpegAlpha.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Main;
|
||||
using Flowframes.MiscUtils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static Flowframes.AvProcess;
|
||||
|
||||
namespace Flowframes.Media
|
||||
{
|
||||
class FfmpegAlpha
|
||||
{
|
||||
public static async Task ExtractAlphaDir(string rgbDir, string alphaDir)
|
||||
{
|
||||
Directory.CreateDirectory(alphaDir);
|
||||
foreach (FileInfo file in IOUtils.GetFileInfosSorted(rgbDir))
|
||||
{
|
||||
string args = $"-i {file.FullName.Wrap()} -vf format=yuva444p16le,alphaextract,format=yuv420p {Path.Combine(alphaDir, file.Name).Wrap()}";
|
||||
await RunFfmpeg(args, LogMode.Hidden);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task RemoveAlpha(string inputDir, string outputDir, string fillColor = "black")
|
||||
{
|
||||
Directory.CreateDirectory(outputDir);
|
||||
foreach (FileInfo file in IOUtils.GetFileInfosSorted(inputDir))
|
||||
{
|
||||
string outFilename = Path.Combine(outputDir, "_" + file.Name);
|
||||
Size res = IOUtils.GetImage(file.FullName).Size;
|
||||
string args = $" -f lavfi -i color={fillColor}:s={res.Width}x{res.Height} -i {file.FullName.Wrap()} " +
|
||||
$"-filter_complex overlay=0:0:shortest=1 -pix_fmt rgb24 {outFilename.Wrap()}";
|
||||
await RunFfmpeg(args, LogMode.Hidden);
|
||||
file.Delete();
|
||||
File.Move(outFilename, file.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task MergeAlphaIntoRgb(string rgbDir, int rgbPad, string alphaDir, int aPad, bool deleteAlphaDir)
|
||||
{
|
||||
string filter = "-filter_complex [0:v:0][1:v:0]alphamerge[out] -map [out]";
|
||||
string args = $"-i \"{rgbDir}/%{rgbPad}d.png\" -i \"{alphaDir}/%{aPad}d.png\" {filter} \"{rgbDir}/%{rgbPad}d.png\"";
|
||||
await RunFfmpeg(args, LogMode.Hidden);
|
||||
if (deleteAlphaDir)
|
||||
IOUtils.TryDeleteIfExists(alphaDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
178
Code/Media/FfmpegAudioAndMetadata.cs
Normal file
178
Code/Media/FfmpegAudioAndMetadata.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Main;
|
||||
using Flowframes.MiscUtils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static Flowframes.AvProcess;
|
||||
using Utils = Flowframes.Media.FFmpegUtils;
|
||||
|
||||
namespace Flowframes.Media
|
||||
{
|
||||
partial class FfmpegAudioAndMetadata : FfmpegCommands
|
||||
{
|
||||
public static async Task ExtractAudio(string inputFile, string outFile) // https://stackoverflow.com/a/27413824/14274419
|
||||
{
|
||||
try
|
||||
{
|
||||
string audioExt = Utils.GetAudioExt(inputFile);
|
||||
outFile = Path.ChangeExtension(outFile, audioExt);
|
||||
Logger.Log($"[FFCmds] Extracting audio from {inputFile} to {outFile}", true);
|
||||
string args = $" -loglevel panic -i {inputFile.Wrap()} -vn -c:a copy {outFile.Wrap()}";
|
||||
await RunFfmpeg(args, LogMode.Hidden);
|
||||
if (File.Exists(outFile) && IOUtils.GetFilesize(outFile) < 512)
|
||||
{
|
||||
Logger.Log("Failed to extract audio losslessly! Trying to re-encode.");
|
||||
File.Delete(outFile);
|
||||
|
||||
outFile = Path.ChangeExtension(outFile, Utils.GetAudioExtForContainer(Path.GetExtension(inputFile)));
|
||||
args = $" -loglevel panic -i {inputFile.Wrap()} -vn {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} {outFile.Wrap()}";
|
||||
await RunFfmpeg(args, LogMode.Hidden);
|
||||
|
||||
if ((File.Exists(outFile) && IOUtils.GetFilesize(outFile) < 512) || lastOutputFfmpeg.Contains("Invalid data"))
|
||||
{
|
||||
Logger.Log("Failed to extract audio, even with re-encoding. Output will not have audio.");
|
||||
IOUtils.TryDeleteIfExists(outFile);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Log($"Source audio has been re-encoded as it can't be extracted losslessly. This may decrease the quality slightly.", false, true);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log("Error extracting audio: " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ExtractSubtitles(string inputFile, string outFolder, Interpolate.OutMode outMode,bool showMsg = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
string msg = "Extracting subtitles from video...";
|
||||
if (showMsg) Logger.Log(msg);
|
||||
|
||||
List<SubtitleTrack> subtitleTracks = await GetSubtitleTracks(inputFile);
|
||||
int counter = 1;
|
||||
|
||||
foreach (SubtitleTrack subTrack in subtitleTracks)
|
||||
{
|
||||
string outPath = Path.Combine(outFolder, $"{subTrack.streamIndex}_{subTrack.langFriendly}_{subTrack.encoding}.srt");
|
||||
string args = $" -loglevel error -sub_charenc {subTrack.encoding} -i {inputFile.Wrap()} -map 0:s:{subTrack.streamIndex} {outPath.Wrap()}";
|
||||
await RunFfmpeg(args, LogMode.Hidden);
|
||||
if (subtitleTracks.Count > 4) Program.mainForm.SetProgress(FormatUtils.RatioInt(counter, subtitleTracks.Count));
|
||||
Logger.Log($"[FFCmds] Extracted subtitle track {subTrack.streamIndex} to {outPath} ({FormatUtils.Bytes(IOUtils.GetFilesize(outPath))})", true, false, "ffmpeg");
|
||||
counter++;
|
||||
}
|
||||
|
||||
if (subtitleTracks.Count > 0)
|
||||
Utils.ContainerSupportsSubs(Utils.GetExt(outMode), true);
|
||||
|
||||
Logger.Log($"Extracted {subtitleTracks.Count} subtitle tracks from the input video.".Replace(" 0 ", " no "), false, Logger.GetLastLine().Contains(msg));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log("Error extracting subtitles: " + e.Message);
|
||||
}
|
||||
|
||||
Program.mainForm.SetProgress(0);
|
||||
}
|
||||
|
||||
public static async Task<List<SubtitleTrack>> GetSubtitleTracks(string inputFile)
|
||||
{
|
||||
List<SubtitleTrack> subtitleTracks = new List<SubtitleTrack>();
|
||||
string args = $"-i {inputFile.Wrap()}";
|
||||
Logger.Log("GetSubtitleTracks()", true, false, "ffmpeg");
|
||||
string[] outputLines = (await GetFfmpegOutputAsync(args)).SplitIntoLines();
|
||||
int idx = 0;
|
||||
|
||||
for (int i = 0; i < outputLines.Length; i++)
|
||||
{
|
||||
string line = outputLines[i];
|
||||
if (!line.Contains(" Subtitle: ")) continue;
|
||||
|
||||
string lang = "unknown";
|
||||
string subEnc = "UTF-8";
|
||||
|
||||
if (line.Contains("(") && line.Contains("): Subtitle: ")) // Lang code in stream name, like "Stream #0:2(eng): Subtitle: ..."
|
||||
lang = line.Split('(')[1].Split(')')[0];
|
||||
|
||||
if ((i + 2 < outputLines.Length) && outputLines[i + 1].Contains("Metadata:") && outputLines[i + 2].Contains("SUB_CHARENC")) // Subtitle encoding is in metadata!
|
||||
subEnc = outputLines[i + 2].Remove("SUB_CHARENC").Remove(":").TrimWhitespaces();
|
||||
|
||||
Logger.Log($"Found subtitle track #{idx} with language '{lang}' and encoding '{subEnc}'", true, false, "ffmpeg");
|
||||
subtitleTracks.Add(new SubtitleTrack(idx, lang, subEnc));
|
||||
idx++;
|
||||
}
|
||||
|
||||
return subtitleTracks;
|
||||
}
|
||||
|
||||
public static async Task MergeAudioAndSubs(string inputFile, string audioPath, string tempFolder, int looptimes = -1) // https://superuser.com/a/277667
|
||||
{
|
||||
Logger.Log($"[FFCmds] Merging audio from {audioPath} into {inputFile}", true);
|
||||
string containerExt = Path.GetExtension(inputFile);
|
||||
string tempPath = Path.Combine(tempFolder, $"vid{containerExt}"); // inputFile + "-temp" + Path.GetExtension(inputFile);
|
||||
string outPath = Path.Combine(tempFolder, $"muxed{containerExt}"); // inputFile + "-temp" + Path.GetExtension(inputFile);
|
||||
File.Move(inputFile, tempPath);
|
||||
string inName = Path.GetFileName(tempPath);
|
||||
string audioName = Path.GetFileName(audioPath);
|
||||
string outName = Path.GetFileName(outPath);
|
||||
|
||||
bool subs = Utils.ContainerSupportsSubs(containerExt, false) && Config.GetBool("keepSubs");
|
||||
string subInputArgs = "";
|
||||
string subMapArgs = "";
|
||||
string subMetaArgs = "";
|
||||
string[] subTracks = subs ? IOUtils.GetFilesSorted(tempFolder, false, "*.srt") : new string[0];
|
||||
|
||||
for (int subTrack = 0; subTrack < subTracks.Length; subTrack++)
|
||||
{
|
||||
subInputArgs += $" -i {Path.GetFileName(subTracks[subTrack])}";
|
||||
subMapArgs += $" -map {subTrack + 2}";
|
||||
subMetaArgs += $" -metadata:s:s:{subTrack} language={Path.GetFileNameWithoutExtension(subTracks[subTrack]).Split('_')[1]}";
|
||||
}
|
||||
|
||||
string subCodec = Utils.GetSubCodecForContainer(containerExt);
|
||||
string args = $" -i {inName} -stream_loop {looptimes} -i {audioName.Wrap()}" +
|
||||
$"{subInputArgs} -map 0:v -map 1:a {subMapArgs} -c:v copy -c:a copy -c:s {subCodec} {subMetaArgs} -shortest {outName}";
|
||||
|
||||
await RunFfmpeg(args, tempFolder, LogMode.Hidden);
|
||||
|
||||
if ((File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024) || lastOutputFfmpeg.Contains("Invalid data") || lastOutputFfmpeg.Contains("Error initializing output stream"))
|
||||
{
|
||||
Logger.Log("Failed to merge audio losslessly! Trying to re-encode.", false, false, "ffmpeg");
|
||||
|
||||
args = $" -i {inName} -stream_loop {looptimes} -i {audioName.Wrap()}" +
|
||||
$"{subInputArgs} -map 0:v -map 1:a {subMapArgs} -c:v copy {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} -c:s {subCodec} {subMetaArgs} -shortest {outName}";
|
||||
|
||||
await RunFfmpeg(args, tempFolder, LogMode.Hidden);
|
||||
|
||||
if ((File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024) || lastOutputFfmpeg.Contains("Invalid data") || lastOutputFfmpeg.Contains("Error initializing output stream"))
|
||||
{
|
||||
Logger.Log("Failed to merge audio, even with re-encoding. Output will not have audio.", false, false, "ffmpeg");
|
||||
IOUtils.TryMove(tempPath, inputFile); // Move temp file back
|
||||
IOUtils.TryDeleteIfExists(tempPath);
|
||||
return;
|
||||
}
|
||||
|
||||
string audioExt = Path.GetExtension(audioPath).Remove(".").ToUpper();
|
||||
Logger.Log($"Source audio ({audioExt}) has been re-encoded to fit into the target container ({containerExt.Remove(".").ToUpper()}). This may decrease the quality slightly.", false, true, "ffmpeg");
|
||||
}
|
||||
|
||||
if (File.Exists(outPath) && IOUtils.GetFilesize(outPath) > 512)
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
File.Move(outPath, inputFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Move(tempPath, inputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
196
Code/Media/FfmpegCommands.cs
Normal file
196
Code/Media/FfmpegCommands.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using Flowframes.Media;
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Main;
|
||||
using Flowframes.MiscUtils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static Flowframes.AvProcess;
|
||||
using Utils = Flowframes.Media.FFmpegUtils;
|
||||
|
||||
namespace Flowframes
|
||||
{
|
||||
class FfmpegCommands
|
||||
{
|
||||
public static string divisionFilter = "pad=width=ceil(iw/2)*2:height=ceil(ih/2)*2:color=black@0";
|
||||
public static string pngComprArg = "-compression_level 3";
|
||||
public static string mpDecDef = "\"mpdecimate\"";
|
||||
public static string mpDecAggr = "\"mpdecimate=hi=64*32:lo=64*32:frac=0.1\"";
|
||||
|
||||
public static async Task ConcatVideos(string concatFile, string outPath, int looptimes = -1)
|
||||
{
|
||||
Logger.Log($"Merging videos...", false, Logger.GetLastLine().Contains("frame"));
|
||||
string loopStr = (looptimes > 0) ? $"-stream_loop {looptimes}" : "";
|
||||
string vfrFilename = Path.GetFileName(concatFile);
|
||||
string args = $" {loopStr} -vsync 1 -f concat -i {vfrFilename} -c copy -movflags +faststart {outPath.Wrap()}";
|
||||
await RunFfmpeg(args, concatFile.GetParentDir(), LogMode.Hidden, TaskType.Merge);
|
||||
}
|
||||
|
||||
public static async Task LoopVideo(string inputFile, int times, bool delSrc = false)
|
||||
{
|
||||
string pathNoExt = Path.ChangeExtension(inputFile, null);
|
||||
string ext = Path.GetExtension(inputFile);
|
||||
string args = $" -stream_loop {times} -i {inputFile.Wrap()} -c copy \"{pathNoExt}-Loop{times}{ext}\"";
|
||||
await RunFfmpeg(args, LogMode.Hidden);
|
||||
if (delSrc)
|
||||
DeleteSource(inputFile);
|
||||
}
|
||||
|
||||
public static async Task ChangeSpeed(string inputFile, float newSpeedPercent, bool delSrc = false)
|
||||
{
|
||||
string pathNoExt = Path.ChangeExtension(inputFile, null);
|
||||
string ext = Path.GetExtension(inputFile);
|
||||
float val = newSpeedPercent / 100f;
|
||||
string speedVal = (1f / val).ToString("0.0000").Replace(",", ".");
|
||||
string args = " -itsscale " + speedVal + " -i \"" + inputFile + "\" -c copy \"" + pathNoExt + "-" + newSpeedPercent + "pcSpeed" + ext + "\"";
|
||||
await RunFfmpeg(args, LogMode.OnlyLastLine);
|
||||
if (delSrc)
|
||||
DeleteSource(inputFile);
|
||||
}
|
||||
|
||||
public static long GetDuration(string inputFile)
|
||||
{
|
||||
Logger.Log($"GetDuration({inputFile}) - Reading Duration using ffprobe.", true, false, "ffmpeg");
|
||||
string args = $" -v panic -select_streams v:0 -show_entries format=duration -of csv=s=x:p=0 -sexagesimal {inputFile.Wrap()}";
|
||||
string info = GetFfprobeOutput(args);
|
||||
return FormatUtils.MsFromTimestamp(info);
|
||||
}
|
||||
|
||||
public static async Task<float> GetFramerate(string inputFile)
|
||||
{
|
||||
Logger.Log($"GetFramerate('{inputFile}')", true, false, "ffmpeg");
|
||||
|
||||
try
|
||||
{
|
||||
string args = $" -i {inputFile.Wrap()}";
|
||||
string output = await GetFfmpegOutputAsync(args);
|
||||
string[] entries = output.Split(',');
|
||||
|
||||
foreach (string entry in entries)
|
||||
{
|
||||
if (entry.Contains(" fps") && !entry.Contains("Input ")) // Avoid reading FPS from the filename, in case filename contains "fps"
|
||||
{
|
||||
string num = entry.Replace(" fps", "").Trim().Replace(",", ".");
|
||||
float value;
|
||||
float.TryParse(num, NumberStyles.Any, CultureInfo.InvariantCulture, out value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Logger.Log("GetFramerate Error: " + e.Message, true, false);
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public static Size GetSize(string inputFile)
|
||||
{
|
||||
string args = $" -v panic -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 {inputFile.Wrap()}";
|
||||
string output = GetFfprobeOutput(args);
|
||||
|
||||
if (output.Length > 4 && output.Contains("x"))
|
||||
{
|
||||
string[] numbers = output.Split('x');
|
||||
return new Size(numbers[0].GetInt(), numbers[1].GetInt());
|
||||
}
|
||||
return new Size(0, 0);
|
||||
}
|
||||
|
||||
public static async Task<int> GetFrameCountAsync(string inputFile)
|
||||
{
|
||||
Logger.Log($"GetFrameCountAsync('{inputFile}') - Trying ffprobe first.", true, false, "ffmpeg");
|
||||
int frames = 0;
|
||||
|
||||
frames = await ReadFrameCountFfprobeAsync(inputFile, Config.GetBool("ffprobeCountFrames")); // Try reading frame count with ffprobe
|
||||
if (frames > 0) return frames;
|
||||
|
||||
Logger.Log($"Failed to get frame count using ffprobe (frames = {frames}). Trying to calculate with duration * fps.", true, false, "ffmpeg");
|
||||
frames = await ReadFrameCountFfmpegAsync(inputFile); // Try reading frame count with ffmpeg
|
||||
if (frames > 0) return frames;
|
||||
|
||||
Logger.Log("Failed to get total frame count of video.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ReadFrameCountFromDuration (string inputFile, long durationMs, float fps)
|
||||
{
|
||||
float durationSeconds = durationMs / 1000f;
|
||||
float frameCount = durationSeconds * fps;
|
||||
int frameCountRounded = frameCount.RoundToInt();
|
||||
Logger.Log($"ReadFrameCountFromDuration: Got frame count of {frameCount}, rounded to {frameCountRounded}");
|
||||
return frameCountRounded;
|
||||
}
|
||||
|
||||
static async Task<int> ReadFrameCountFfprobeAsync(string inputFile, bool readFramesSlow)
|
||||
{
|
||||
string args = $" -v panic -select_streams v:0 -show_entries stream=nb_frames -of default=noprint_wrappers=1 {inputFile.Wrap()}";
|
||||
if (readFramesSlow)
|
||||
{
|
||||
Logger.Log("Counting total frames using FFprobe. This can take a moment...");
|
||||
await Task.Delay(10);
|
||||
args = $" -v panic -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 {inputFile.Wrap()}";
|
||||
}
|
||||
string info = GetFfprobeOutput(args);
|
||||
string[] entries = info.SplitIntoLines();
|
||||
try
|
||||
{
|
||||
if (readFramesSlow)
|
||||
return info.GetInt();
|
||||
foreach (string entry in entries)
|
||||
{
|
||||
if (entry.Contains("nb_frames="))
|
||||
return entry.GetInt();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return -1;
|
||||
}
|
||||
|
||||
static async Task<int> ReadFrameCountFfmpegAsync (string inputFile)
|
||||
{
|
||||
string args = $" -loglevel panic -i {inputFile.Wrap()} -map 0:v:0 -c copy -f null - ";
|
||||
string info = await GetFfmpegOutputAsync(args, true, true);
|
||||
try
|
||||
{
|
||||
string[] lines = info.SplitIntoLines();
|
||||
string lastLine = lines.Last();
|
||||
return lastLine.Substring(0, lastLine.IndexOf("fps")).GetInt();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetAudioCodec(string path)
|
||||
{
|
||||
string args = $" -v panic -show_streams -select_streams a -show_entries stream=codec_name {path.Wrap()}";
|
||||
string info = GetFfprobeOutput(args);
|
||||
string[] entries = info.SplitIntoLines();
|
||||
foreach (string entry in entries)
|
||||
{
|
||||
if (entry.Contains("codec_name="))
|
||||
return entry.Split('=')[1];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static void DeleteSource(string path)
|
||||
{
|
||||
Logger.Log("[FFCmds] Deleting input file/dir: " + path, true);
|
||||
|
||||
if (IOUtils.IsPathDirectory(path) && Directory.Exists(path))
|
||||
Directory.Delete(path, true);
|
||||
|
||||
if (!IOUtils.IsPathDirectory(path) && File.Exists(path))
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Code/Media/FfmpegEncode.cs
Normal file
65
Code/Media/FfmpegEncode.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Main;
|
||||
using Flowframes.MiscUtils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static Flowframes.AvProcess;
|
||||
using Utils = Flowframes.Media.FFmpegUtils;
|
||||
|
||||
namespace Flowframes.Media
|
||||
{
|
||||
partial class FfmpegEncode : FfmpegCommands
|
||||
{
|
||||
public static async Task FramesToVideoConcat(string framesFile, string outPath, Interpolate.OutMode outMode, float fps, LogMode logMode = LogMode.OnlyLastLine, bool isChunk = false)
|
||||
{
|
||||
await FramesToVideoConcat(framesFile, outPath, outMode, fps, 0, logMode, isChunk);
|
||||
}
|
||||
|
||||
public static async Task FramesToVideoConcat(string framesFile, string outPath, Interpolate.OutMode outMode, float fps, float resampleFps, LogMode logMode = LogMode.OnlyLastLine, bool isChunk = false)
|
||||
{
|
||||
if (logMode != LogMode.Hidden)
|
||||
Logger.Log((resampleFps <= 0) ? $"Encoding video..." : $"Encoding video resampled to {resampleFps.ToString().Replace(",", ".")} FPS...");
|
||||
Directory.CreateDirectory(outPath.GetParentDir());
|
||||
string encArgs = Utils.GetEncArgs(Utils.GetCodec(outMode));
|
||||
if (!isChunk) encArgs += $" -movflags +faststart";
|
||||
string vfrFilename = Path.GetFileName(framesFile);
|
||||
string rate = fps.ToString().Replace(",", ".");
|
||||
string vf = (resampleFps <= 0) ? "" : $"-vf fps=fps={resampleFps.ToStringDot()}";
|
||||
string extraArgs = Config.Get("ffEncArgs");
|
||||
string args = $"-loglevel error -vsync 0 -f concat -r {rate} -i {vfrFilename} {encArgs} {vf} {extraArgs} -threads {Config.GetInt("ffEncThreads")} {outPath.Wrap()}";
|
||||
await RunFfmpeg(args, framesFile.GetParentDir(), logMode, TaskType.Encode, !isChunk);
|
||||
}
|
||||
|
||||
public static async Task FramesToGifConcat(string framesFile, string outPath, float fps, bool palette, int colors = 64, float resampleFps = -1, LogMode logMode = LogMode.OnlyLastLine)
|
||||
{
|
||||
if (logMode != LogMode.Hidden)
|
||||
Logger.Log((resampleFps <= 0) ? $"Encoding GIF..." : $"Encoding GIF resampled to {resampleFps.ToString().Replace(",", ".")} FPS...");
|
||||
string vfrFilename = Path.GetFileName(framesFile);
|
||||
string paletteFilter = palette ? $"-vf \"split[s0][s1];[s0]palettegen={colors}[p];[s1][p]paletteuse=dither=floyd_steinberg:diff_mode=rectangle\"" : "";
|
||||
string fpsFilter = (resampleFps <= 0) ? "" : $"fps=fps={resampleFps.ToStringDot()}";
|
||||
string vf = FormatUtils.ConcatStrings(new string[] { paletteFilter, fpsFilter });
|
||||
string rate = fps.ToStringDot();
|
||||
string args = $"-loglevel error -f concat -r {rate} -i {vfrFilename.Wrap()} -f gif {vf} {outPath.Wrap()}";
|
||||
await RunFfmpeg(args, framesFile.GetParentDir(), LogMode.OnlyLastLine, TaskType.Encode);
|
||||
}
|
||||
|
||||
public static async Task Encode(string inputFile, string vcodec, string acodec, int crf, int audioKbps = 0, bool delSrc = false)
|
||||
{
|
||||
string outPath = Path.ChangeExtension(inputFile, null) + "-convert.mp4";
|
||||
string args = $" -i {inputFile.Wrap()} -c:v {vcodec} -crf {crf} -pix_fmt yuv420p -c:a {acodec} -b:a {audioKbps}k -vf {divisionFilter} {outPath.Wrap()}";
|
||||
if (string.IsNullOrWhiteSpace(acodec))
|
||||
args = args.Replace("-c:a", "-an");
|
||||
if (audioKbps < 0)
|
||||
args = args.Replace($" -b:a {audioKbps}", "");
|
||||
await RunFfmpeg(args, LogMode.OnlyLastLine, TaskType.Encode, true);
|
||||
if (delSrc)
|
||||
DeleteSource(inputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
107
Code/Media/FfmpegExtract.cs
Normal file
107
Code/Media/FfmpegExtract.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Main;
|
||||
using Flowframes.MiscUtils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static Flowframes.AvProcess;
|
||||
|
||||
namespace Flowframes.Media
|
||||
{
|
||||
partial class FfmpegExtract : FfmpegCommands
|
||||
{
|
||||
public static async Task ExtractSceneChanges(string inputFile, string frameFolderPath, float rate)
|
||||
{
|
||||
Logger.Log("Extracting scene changes...");
|
||||
await VideoToFrames(inputFile, frameFolderPath, false, rate, false, false, new Size(320, 180), true);
|
||||
bool hiddenLog = Interpolate.currentInputFrameCount <= 50;
|
||||
int amount = IOUtils.GetAmountOfFiles(frameFolderPath, false);
|
||||
Logger.Log($"Detected {amount} scene {(amount == 1 ? "change" : "changes")}.".Replace(" 0 ", " no "), false, !hiddenLog);
|
||||
}
|
||||
|
||||
public static async Task VideoToFrames(string inputFile, string framesDir, bool alpha, float rate, bool deDupe, bool delSrc)
|
||||
{
|
||||
await VideoToFrames(inputFile, framesDir, alpha, rate, deDupe, delSrc, new Size());
|
||||
}
|
||||
|
||||
public static async Task VideoToFrames(string inputFile, string framesDir, bool alpha, float rate, bool deDupe, bool delSrc, Size size, bool sceneDetect = false)
|
||||
{
|
||||
if (!sceneDetect) Logger.Log("Extracting video frames from input video...");
|
||||
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
|
||||
IOUtils.CreateDir(framesDir);
|
||||
string timecodeStr = /* timecodes ? $"-copyts -r {FrameOrder.timebase} -frame_pts true" : */ "-copyts -frame_pts true";
|
||||
string scnDetect = sceneDetect ? $"\"select='gt(scene,{Config.GetFloatString("scnDetectValue")})'\"" : "";
|
||||
string mpStr = deDupe ? ((Config.GetInt("mpdecimateMode") == 0) ? mpDecDef : mpDecAggr) : "";
|
||||
string filters = FormatUtils.ConcatStrings(new string[] { divisionFilter, scnDetect, mpStr });
|
||||
string vf = filters.Length > 2 ? $"-vf {filters}" : "";
|
||||
string rateArg = (rate > 0) ? $" -r {rate.ToStringDot()}" : "";
|
||||
string pixFmt = alpha ? "-pix_fmt rgba" : "-pix_fmt rgb24"; // Use RGBA for GIF for alpha support
|
||||
string args = $"{rateArg} -i {inputFile.Wrap()} {pngComprArg} -vsync 0 {pixFmt} {timecodeStr} {vf} {sizeStr} \"{framesDir}/%{Padding.inputFrames}d.png\"";
|
||||
LogMode logMode = Interpolate.currentInputFrameCount > 50 ? LogMode.OnlyLastLine : LogMode.Hidden;
|
||||
await AvProcess.RunFfmpeg(args, logMode, TaskType.ExtractFrames, true);
|
||||
int amount = IOUtils.GetAmountOfFiles(framesDir, false, "*.png");
|
||||
if (!sceneDetect) Logger.Log($"Extracted {amount} {(amount == 1 ? "frame" : "frames")} from input.", false, true);
|
||||
await Task.Delay(1);
|
||||
if (delSrc)
|
||||
DeleteSource(inputFile);
|
||||
}
|
||||
|
||||
public static async Task ImportImages(string inpath, string outpath, bool alpha, Size size, bool delSrc = false, bool showLog = true)
|
||||
{
|
||||
if (showLog) Logger.Log("Importing images...");
|
||||
Logger.Log($"Importing images from {inpath} to {outpath}.", true);
|
||||
IOUtils.CreateDir(outpath);
|
||||
string concatFile = Path.Combine(Paths.GetDataPath(), "png-concat-temp.ini");
|
||||
string concatFileContent = "";
|
||||
string[] files = IOUtils.GetFilesSorted(inpath);
|
||||
foreach (string img in files)
|
||||
concatFileContent += $"file '{img.Replace(@"\", "/")}'\n";
|
||||
File.WriteAllText(concatFile, concatFileContent);
|
||||
|
||||
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
|
||||
string pixFmt = alpha ? "-pix_fmt rgba" : "-pix_fmt rgb24"; // Use RGBA for GIF for alpha support
|
||||
string vf = alpha ? $"-filter_complex \"[0:v]{divisionFilter},split[a][b];[a]palettegen=reserve_transparent=on:transparency_color=ffffff[p];[b][p]paletteuse\"" : $"-vf {divisionFilter}";
|
||||
string args = $" -loglevel panic -f concat -safe 0 -i {concatFile.Wrap()} {pngComprArg} {sizeStr} {pixFmt} -vsync 0 {vf} \"{outpath}/%{Padding.inputFrames}d.png\"";
|
||||
LogMode logMode = IOUtils.GetAmountOfFiles(inpath, false) > 50 ? LogMode.OnlyLastLine : LogMode.Hidden;
|
||||
await AvProcess.RunFfmpeg(args, logMode, TaskType.ExtractFrames);
|
||||
if (delSrc)
|
||||
DeleteSource(inpath);
|
||||
}
|
||||
|
||||
public static async Task ImportSingleImage(string inputFile, string outPath, Size size)
|
||||
{
|
||||
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
|
||||
bool isPng = (Path.GetExtension(outPath).ToLower() == ".png");
|
||||
string comprArg = isPng ? pngComprArg : "";
|
||||
string pixFmt = "-pix_fmt " + (isPng ? $"rgb24 {comprArg}" : "yuvj420p");
|
||||
string args = $"-i {inputFile.Wrap()} {comprArg} {sizeStr} {pixFmt} -vf {divisionFilter} {outPath.Wrap()}";
|
||||
await AvProcess.RunFfmpeg(args, LogMode.Hidden, TaskType.ExtractFrames);
|
||||
}
|
||||
|
||||
public static async Task ExtractSingleFrame(string inputFile, string outputPath, int frameNum)
|
||||
{
|
||||
bool isPng = (Path.GetExtension(outputPath).ToLower() == ".png");
|
||||
string comprArg = isPng ? pngComprArg : "";
|
||||
string pixFmt = "-pix_fmt " + (isPng ? $"rgb24 {comprArg}" : "yuvj420p");
|
||||
string args = $"-i {inputFile.Wrap()} -vf \"select=eq(n\\,{frameNum})\" -vframes 1 {pixFmt} {outputPath.Wrap()}";
|
||||
await AvProcess.RunFfmpeg(args, LogMode.Hidden, TaskType.ExtractFrames);
|
||||
}
|
||||
|
||||
public static async Task ExtractLastFrame(string inputFile, string outputPath, Size size)
|
||||
{
|
||||
if (IOUtils.IsPathDirectory(outputPath))
|
||||
outputPath = Path.Combine(outputPath, "last.png");
|
||||
bool isPng = (Path.GetExtension(outputPath).ToLower() == ".png");
|
||||
string comprArg = isPng ? pngComprArg : "";
|
||||
string pixFmt = "-pix_fmt " + (isPng ? $"rgb24 {comprArg}" : "yuvj420p");
|
||||
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
|
||||
string args = $"-sseof -1 -i {inputFile.Wrap()} -update 1 {pixFmt} {sizeStr} {outputPath.Wrap()}";
|
||||
await AvProcess.RunFfmpeg(args, LogMode.Hidden, TaskType.ExtractFrames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,15 +57,45 @@ namespace Flowframes.MiscUtils
|
||||
return Time(elapsedMs);
|
||||
}
|
||||
|
||||
public static long MsFromTimestamp(string timestamp)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] values = timestamp.Split(':');
|
||||
int hours = int.Parse(values[0]);
|
||||
int minutes = int.Parse(values[1]);
|
||||
int seconds = int.Parse(values[2].Split('.')[0]);
|
||||
int milliseconds = int.Parse(values[2].Split('.')[1].Substring(0, 2)) * 10;
|
||||
long ms = hours * 3600000 + minutes * 60000 + seconds * 1000 + milliseconds;
|
||||
return ms;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"MsFromTimeStamp({timestamp}) Exception: {e.Message}", true);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static string MsToTimestamp(long milliseconds)
|
||||
{
|
||||
return (new DateTime(1970, 1, 1)).AddMilliseconds(milliseconds).ToString("HH:mm:ss");
|
||||
}
|
||||
|
||||
public static string Ratio(long numFrom, long numTo)
|
||||
{
|
||||
float ratio = ((float)numTo / (float)numFrom) * 100f;
|
||||
float ratio = ((float)numFrom / (float)numTo) * 100f;
|
||||
return ratio.ToString("0.00") + "%";
|
||||
}
|
||||
|
||||
public static string RatioInt(long numFrom, long numTo)
|
||||
public static int RatioInt(long numFrom, long numTo)
|
||||
{
|
||||
double ratio = Math.Round(((float)numTo / (float)numFrom) * 100f);
|
||||
double ratio = Math.Round(((float)numFrom / (float)numTo) * 100f);
|
||||
return (int)ratio;
|
||||
}
|
||||
|
||||
public static string RatioIntStr(long numFrom, long numTo)
|
||||
{
|
||||
double ratio = Math.Round(((float)numFrom / (float)numTo) * 100f);
|
||||
return ratio + "%";
|
||||
}
|
||||
|
||||
@@ -86,5 +116,18 @@ namespace Flowframes.MiscUtils
|
||||
|
||||
return outStr;
|
||||
}
|
||||
|
||||
public static System.Drawing.Size ParseSize (string str)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] values = str.Split('x');
|
||||
return new System.Drawing.Size(values[0].GetInt(), values[1].GetInt());
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new System.Drawing.Size();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ using Flowframes.UI;
|
||||
using Flowframes.Main;
|
||||
using Flowframes.Data;
|
||||
using Flowframes.MiscUtils;
|
||||
using Flowframes.Magick;
|
||||
using Flowframes.Media;
|
||||
|
||||
namespace Flowframes
|
||||
{
|
||||
@@ -37,10 +39,10 @@ namespace Flowframes
|
||||
hasShownError = false;
|
||||
}
|
||||
|
||||
static void SetProgressCheck(string interpPath, int factor)
|
||||
static void SetProgressCheck(string interpPath, float factor)
|
||||
{
|
||||
int frames = IOUtils.GetAmountOfFiles(lastInPath, false, "*.png");
|
||||
int target = (frames * factor) - (factor - 1);
|
||||
int target = ((frames * factor) - (factor - 1)).RoundToInt();
|
||||
InterpolateUtils.progressPaused = false;
|
||||
InterpolateUtils.currentFactor = factor;
|
||||
|
||||
@@ -52,6 +54,7 @@ namespace Flowframes
|
||||
|
||||
static async Task AiFinished (string aiName)
|
||||
{
|
||||
if (Interpolate.canceled) return;
|
||||
Program.mainForm.SetProgress(100);
|
||||
InterpolateUtils.UpdateInterpProgress(IOUtils.GetAmountOfFiles(Interpolate.current.interpFolder, false, "*.png"), InterpolateUtils.targetFrames);
|
||||
string logStr = $"Done running {aiName} - Interpolation took {FormatUtils.Time(processTime.Elapsed)}";
|
||||
@@ -60,28 +63,34 @@ namespace Flowframes
|
||||
Logger.Log(logStr);
|
||||
processTime.Stop();
|
||||
|
||||
Stopwatch timeSinceFfmpegRan = new Stopwatch();
|
||||
timeSinceFfmpegRan.Restart();
|
||||
|
||||
while (Interpolate.currentlyUsingAutoEnc && Program.busy)
|
||||
{
|
||||
if (AvProcess.lastProcess != null && !AvProcess.lastProcess.HasExited && AvProcess.lastTask == AvProcess.TaskType.Encode)
|
||||
{
|
||||
timeSinceFfmpegRan.Restart();
|
||||
string lastLine = AvProcess.lastOutputFfmpeg.SplitIntoLines().Last();
|
||||
Logger.Log(lastLine.Trim().TrimWhitespaces(), false, Logger.GetLastLine().Contains("frame"));
|
||||
}
|
||||
if (timeSinceFfmpegRan.ElapsedMilliseconds > 3000)
|
||||
|
||||
if (AvProcess.timeSinceLastOutput.IsRunning && AvProcess.timeSinceLastOutput.ElapsedMilliseconds > 2500)
|
||||
break;
|
||||
// Logger.Log($"AiProcess loop - Program.busy = {Program.busy}");
|
||||
|
||||
await Task.Delay(500);
|
||||
}
|
||||
|
||||
if (!Interpolate.canceled && Interpolate.current.alpha)
|
||||
{
|
||||
Logger.Log("Processing alpha...");
|
||||
string rgbInterpDir = Path.Combine(Interpolate.current.tempFolder, Paths.interpDir);
|
||||
string alphaInterpDir = Path.Combine(Interpolate.current.tempFolder, Paths.interpDir + Paths.alphaSuffix);
|
||||
if (!Directory.Exists(alphaInterpDir)) return;
|
||||
await FfmpegAlpha.MergeAlphaIntoRgb(rgbInterpDir, Padding.interpFrames, alphaInterpDir, Padding.interpFrames, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task RunRifeCuda(string framesPath, int interpFactor, string mdl)
|
||||
public static async Task RunRifeCuda(string framesPath, float interpFactor, string mdl)
|
||||
{
|
||||
string rifeDir = Path.Combine(Paths.GetPkgPath(), Path.GetFileNameWithoutExtension(Packages.rifeCuda.fileName));
|
||||
string script = "inference_video.py";
|
||||
string script = "rife.py";
|
||||
|
||||
if (!File.Exists(Path.Combine(rifeDir, script)))
|
||||
{
|
||||
@@ -95,23 +104,20 @@ namespace Flowframes
|
||||
{
|
||||
InterpolateUtils.progressPaused = true;
|
||||
Logger.Log("Interpolating alpha channel...");
|
||||
await RunRifeCudaProcess(framesPath + "-a", Paths.interpDir + "-a", script, interpFactor, mdl);
|
||||
await RunRifeCudaProcess(framesPath + Paths.alphaSuffix, Paths.interpDir + Paths.alphaSuffix, script, interpFactor, mdl);
|
||||
}
|
||||
|
||||
await AiFinished("RIFE");
|
||||
|
||||
if (!Interpolate.canceled && Interpolate.current.alpha)
|
||||
{
|
||||
Logger.Log("Processing alpha...");
|
||||
string rgbInterpDir = Path.Combine(Interpolate.current.tempFolder, Paths.interpDir);
|
||||
await FFmpegCommands.MergeAlphaIntoRgb(rgbInterpDir, Padding.interpFrames, rgbInterpDir + "-a", Padding.interpFrames);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task RunRifeCudaProcess (string inPath, string outDir, string script, int interpFactor, string mdl)
|
||||
public static async Task RunRifeCudaProcess (string inPath, string outDir, string script, float interpFactor, string mdl)
|
||||
{
|
||||
bool parallel = false;
|
||||
string uhdStr = await InterpolateUtils.UseUHD() ? "--UHD" : "";
|
||||
string args = $" --input {inPath.Wrap()} --model {mdl} --exp {(int)Math.Log(interpFactor, 2)} {uhdStr} --imgformat {InterpolateUtils.GetOutExt()} --output {outDir}";
|
||||
string outPath = Path.Combine(inPath.GetParentDir(), outDir);
|
||||
string args = $" --input {inPath.Wrap()} --output {outDir} --model {mdl} --exp {(int)Math.Log(interpFactor, 2)} ";
|
||||
if (parallel) args = $" --input {inPath.Wrap()} --output {outPath} --model {mdl} --factor {interpFactor}";
|
||||
if (parallel) script = "rife-parallel.py";
|
||||
|
||||
Process rifePy = OSUtils.NewProcess(!OSUtils.ShowHiddenCmd());
|
||||
AiStarted(rifePy, 3500);
|
||||
@@ -141,50 +147,50 @@ namespace Flowframes
|
||||
processTimeMulti.Restart();
|
||||
Logger.Log($"Running RIFE{(await InterpolateUtils.UseUHD() ? " (UHD Mode)" : "")}...", false);
|
||||
|
||||
bool useAutoEnc = Interpolate.currentlyUsingAutoEnc;
|
||||
if(factor > 2)
|
||||
await RunRifeNcnnMulti(framesPath, outPath, factor, mdl);
|
||||
|
||||
if (!Interpolate.canceled && Interpolate.current.alpha)
|
||||
{
|
||||
InterpolateUtils.progressPaused = true;
|
||||
Logger.Log("Interpolating alpha channel...");
|
||||
await RunRifeNcnnMulti(framesPath + Paths.alphaSuffix, outPath + Paths.alphaSuffix, factor, mdl);
|
||||
}
|
||||
|
||||
await AiFinished("RIFE");
|
||||
}
|
||||
|
||||
static async Task RunRifeNcnnMulti(string framesPath, string outPath, int factor, string mdl)
|
||||
{
|
||||
int times = (int)Math.Log(factor, 2);
|
||||
|
||||
if (times > 1)
|
||||
AutoEncode.paused = true; // Disable autoenc until the last iteration
|
||||
|
||||
for (int iteration = 1; iteration <= times; iteration++)
|
||||
{
|
||||
if (Interpolate.canceled) return;
|
||||
|
||||
if (Interpolate.currentlyUsingAutoEnc && iteration == times) // Enable autoenc if this is the last iteration
|
||||
AutoEncode.paused = false;
|
||||
|
||||
if (iteration > 1)
|
||||
{
|
||||
Logger.Log($"Re-Running RIFE for {Math.Pow(2, iteration)}x interpolation...", false);
|
||||
string lastInterpPath = outPath + $"-run{iteration - 1}";
|
||||
Directory.Move(outPath, lastInterpPath); // Rename last interp folder
|
||||
await RunRifeNcnnProcess(lastInterpPath, outPath, mdl);
|
||||
IOUtils.TryDeleteIfExists(lastInterpPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
await RunRifeNcnnProcess(framesPath, outPath, mdl);
|
||||
|
||||
if (factor == 4 || factor == 8) // #2
|
||||
{
|
||||
if (Interpolate.canceled) return;
|
||||
Logger.Log("Re-Running RIFE for 4x interpolation...", false);
|
||||
string run1ResultsPath = outPath + "-run1";
|
||||
IOUtils.TryDeleteIfExists(run1ResultsPath);
|
||||
Directory.Move(outPath, run1ResultsPath);
|
||||
Directory.CreateDirectory(outPath);
|
||||
if (useAutoEnc && factor == 4)
|
||||
AutoEncode.paused = false;
|
||||
await RunRifeNcnnProcess(run1ResultsPath, outPath, mdl);
|
||||
IOUtils.TryDeleteIfExists(run1ResultsPath);
|
||||
}
|
||||
|
||||
if (factor == 8) // #3
|
||||
{
|
||||
if (Interpolate.canceled) return;
|
||||
Logger.Log("Re-Running RIFE for 8x interpolation...", false);
|
||||
string run2ResultsPath = outPath + "-run2";
|
||||
IOUtils.TryDeleteIfExists(run2ResultsPath);
|
||||
Directory.Move(outPath, run2ResultsPath);
|
||||
Directory.CreateDirectory(outPath);
|
||||
if (useAutoEnc && factor == 8)
|
||||
AutoEncode.paused = false;
|
||||
await RunRifeNcnnProcess(run2ResultsPath, outPath, mdl);
|
||||
IOUtils.TryDeleteIfExists(run2ResultsPath);
|
||||
}
|
||||
|
||||
if (Interpolate.canceled) return;
|
||||
|
||||
if (!Interpolate.currentlyUsingAutoEnc)
|
||||
IOUtils.ZeroPadDir(outPath, InterpolateUtils.GetOutExt(), Padding.interpFrames);
|
||||
|
||||
AiFinished("RIFE");
|
||||
}
|
||||
|
||||
static async Task RunRifeNcnnProcess(string inPath, string outPath, string mdl)
|
||||
{
|
||||
Directory.CreateDirectory(outPath);
|
||||
Process rifeNcnn = OSUtils.NewProcess(!OSUtils.ShowHiddenCmd());
|
||||
AiStarted(rifeNcnn, 1500, inPath);
|
||||
SetProgressCheck(outPath, 2);
|
||||
@@ -214,15 +220,28 @@ namespace Flowframes
|
||||
while (!rifeNcnn.HasExited) await Task.Delay(1);
|
||||
}
|
||||
|
||||
public static async Task RunDainNcnn(string framesPath, string outPath, int factor, string mdl, int tilesize)
|
||||
public static async Task RunDainNcnn(string framesPath, string outPath, float factor, string mdl, int tilesize)
|
||||
{
|
||||
await RunDainNcnnProcess(framesPath, outPath, factor, mdl, tilesize);
|
||||
|
||||
if (!Interpolate.canceled && Interpolate.current.alpha)
|
||||
{
|
||||
InterpolateUtils.progressPaused = true;
|
||||
Logger.Log("Interpolating alpha channel...");
|
||||
await RunDainNcnnProcess(framesPath + Paths.alphaSuffix, outPath + Paths.alphaSuffix, factor, mdl, tilesize);
|
||||
}
|
||||
|
||||
await AiFinished("DAIN");
|
||||
}
|
||||
|
||||
public static async Task RunDainNcnnProcess (string framesPath, string outPath, float factor, string mdl, int tilesize)
|
||||
{
|
||||
string dainDir = Path.Combine(Paths.GetPkgPath(), Path.GetFileNameWithoutExtension(Packages.dainNcnn.fileName));
|
||||
Directory.CreateDirectory(outPath);
|
||||
Process dain = OSUtils.NewProcess(!OSUtils.ShowHiddenCmd());
|
||||
|
||||
AiStarted(dain, 1500);
|
||||
SetProgressCheck(outPath, factor);
|
||||
|
||||
int targetFrames = (IOUtils.GetAmountOfFiles(lastInPath, false, "*.png") * factor) - (factor - 1);
|
||||
int targetFrames = ((IOUtils.GetAmountOfFiles(lastInPath, false, "*.png") * factor).RoundToInt()) - (factor.RoundToInt() - 1); // TODO: Won't work with fractional factors
|
||||
|
||||
string args = $" -v -i {framesPath.Wrap()} -o {outPath.Wrap()} -n {targetFrames} -m {mdl.ToLower()}" +
|
||||
$" -t {GetNcnnTilesize(tilesize)} -g {Config.Get("ncnnGpus")} -f {GetNcnnPattern()} -j 2:1:2";
|
||||
@@ -245,14 +264,7 @@ namespace Flowframes
|
||||
}
|
||||
|
||||
while (!dain.HasExited)
|
||||
await Task.Delay(1);
|
||||
|
||||
if (Interpolate.canceled) return;
|
||||
|
||||
//if (!Interpolate.currentlyUsingAutoEnc)
|
||||
// IOUtils.ZeroPadDir(outPath, InterpolateUtils.GetOutExt(), Padding.interpFrames);
|
||||
|
||||
AiFinished("DAIN");
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
static void LogOutput (string line, string logFilename, bool err = false)
|
||||
|
||||
@@ -14,58 +14,49 @@ namespace Flowframes.OS
|
||||
{
|
||||
class Updater
|
||||
{
|
||||
public enum VersionCompareResult { Older, Newer, Equal };
|
||||
public static string latestVerUrl = "https://dl.nmkd.de/flowframes/exe/ver.ini";
|
||||
|
||||
public static SemVer GetInstalledVer()
|
||||
public static Version GetInstalledVer()
|
||||
{
|
||||
try
|
||||
{
|
||||
string verStr = IOUtils.ReadLines(Paths.GetVerPath())[0];
|
||||
return new SemVer(verStr);
|
||||
return new Version(verStr);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log("Error getting installed version: " + e.Message);
|
||||
return new SemVer(0, 0, 0);
|
||||
return new Version(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsVersionNewer (SemVer currentVer, SemVer newVer)
|
||||
public static VersionCompareResult CompareVersions (Version currentVersion, Version newVersion)
|
||||
{
|
||||
if (newVer.major > currentVer.major)
|
||||
Logger.Log($"Checking if {newVersion} > {currentVersion}", true);
|
||||
int result = newVersion.CompareTo(currentVersion);
|
||||
|
||||
if (result > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(newVer.minor > currentVer.minor)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (newVer.patch > currentVer.patch)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Logger.Log($"{newVersion} is newer than {currentVersion}.", true);
|
||||
return VersionCompareResult.Newer;
|
||||
}
|
||||
|
||||
public static bool VersionMatches (SemVer v1, SemVer v2)
|
||||
if (result < 0)
|
||||
{
|
||||
return v1.major == v2.major && v1.minor == v2.minor && v1.patch == v2.patch;
|
||||
Logger.Log($"{newVersion} is older than {currentVersion}.", true);
|
||||
return VersionCompareResult.Older;
|
||||
}
|
||||
|
||||
public static SemVer GetLatestVer (bool patreon)
|
||||
Logger.Log($"{newVersion} is equal to {currentVersion}.", true);
|
||||
return VersionCompareResult.Equal;
|
||||
}
|
||||
|
||||
public static Version GetLatestVer (bool patreon)
|
||||
{
|
||||
var client = new WebClient();
|
||||
int line = patreon ? 0 : 2;
|
||||
return new SemVer(client.DownloadString(latestVerUrl).SplitIntoLines()[line]);
|
||||
return new Version(client.DownloadString(latestVerUrl).SplitIntoLines()[line]);
|
||||
}
|
||||
|
||||
public static string GetLatestVerLink(bool patreon)
|
||||
@@ -134,16 +125,11 @@ namespace Flowframes.OS
|
||||
|
||||
public static async Task AsyncUpdateCheck ()
|
||||
{
|
||||
SemVer installed = GetInstalledVer();
|
||||
SemVer latestPat = GetLatestVer(true);
|
||||
SemVer latestFree = GetLatestVer(false);
|
||||
Version installed = GetInstalledVer();
|
||||
Version latestPat = GetLatestVer(true);
|
||||
Version latestFree = GetLatestVer(false);
|
||||
|
||||
Logger.Log($"You are running Flowframes {installed}. The latest Patreon version is {latestPat}, the latest free version is {latestFree}.");
|
||||
|
||||
// if (IsVersionNewer(installed, latest))
|
||||
// Logger.Log($"An update for Flowframes ({latest}) is available! Download it from the Updater.");
|
||||
// else
|
||||
// Logger.Log($"Flowframes is up to date ({installed}).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace Flowframes
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
Paths.Init();
|
||||
Config.Init();
|
||||
|
||||
if (Config.GetBool("delLogsOnStartup"))
|
||||
|
||||
@@ -13,6 +13,8 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyCopyright("Copyright © 2020")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: System.Resources.NeutralResourcesLanguage("en-US")]
|
||||
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
|
||||
@@ -35,6 +35,8 @@ namespace Flowframes.UI
|
||||
}
|
||||
|
||||
public static string ParsePatreonCsv(string csvData)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<string> goldPatrons = new List<string>();
|
||||
List<string> silverPatrons = new List<string>();
|
||||
@@ -42,7 +44,7 @@ namespace Flowframes.UI
|
||||
string[] lines = csvData.SplitIntoLines();
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
{
|
||||
string line = lines[i];
|
||||
string line = lines[i].Replace(";", ",");
|
||||
string[] values = line.Split(',');
|
||||
if (i == 0 || line.Length < 10 || values.Length < 5) continue;
|
||||
string name = values[0];
|
||||
@@ -66,5 +68,11 @@ namespace Flowframes.UI
|
||||
|
||||
return str;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log("Failed to parse Patreon CSV: " + e.Message, true);
|
||||
return "Failed to load patron list.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Media;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Magick;
|
||||
using Flowframes.Main;
|
||||
using Flowframes.OS;
|
||||
@@ -18,32 +19,30 @@ namespace Flowframes.UI
|
||||
public static async Task InitInput (TextBox outputTbox, TextBox inputTbox, TextBox fpsInTbox)
|
||||
{
|
||||
Program.mainForm.SetTab("interpolate");
|
||||
Program.mainForm.ResetInputInfo();
|
||||
string path = inputTbox.Text.Trim();
|
||||
InterpolateUtils.PathAsciiCheck(path, "input path");
|
||||
|
||||
if (Config.GetBool("clearLogOnInput"))
|
||||
Logger.ClearLogBox();
|
||||
|
||||
outputTbox.Text = inputTbox.Text.Trim().GetParentDir();
|
||||
string path = inputTbox.Text.Trim();
|
||||
Program.lastInputPath = path;
|
||||
|
||||
Program.lastInputPathIsSsd = OSUtils.DriveIsSSD(path);
|
||||
|
||||
if (!Program.lastInputPathIsSsd)
|
||||
Logger.Log("Your file seems to be on an HDD or USB device. It is recommended to interpolate videos on an SSD drive for best performance.");
|
||||
|
||||
Logger.Log("Loading metadata...");
|
||||
Program.mainForm.currInDuration = FfmpegCommands.GetDuration(path);
|
||||
int frameCount = await InterpolateUtils.GetInputFrameCountAsync(path);
|
||||
|
||||
string fpsStr = "Not Found";
|
||||
float fps = IOUtils.GetFpsFolderOrVideo(path);
|
||||
if (fps > 0)
|
||||
{
|
||||
fpsStr = fps.ToString();
|
||||
fpsInTbox.Text = fpsStr;
|
||||
}
|
||||
float fps = await IOUtils.GetFpsFolderOrVideo(path);
|
||||
fpsInTbox.Text = fps.ToString();
|
||||
|
||||
if (fps > 0)
|
||||
fpsStr = fps.ToString();
|
||||
|
||||
if (IOUtils.IsPathDirectory(path))
|
||||
Logger.Log($"Video FPS (Loaded from fps.ini): {fpsStr} - Total Number Of Frames: {frameCount}", false, true);
|
||||
else
|
||||
Logger.Log($"Video FPS: {fpsStr} - Total Number Of Frames: {frameCount}", false, true);
|
||||
|
||||
Program.mainForm.currInFps = fps;
|
||||
@@ -59,7 +58,7 @@ namespace Flowframes.UI
|
||||
|
||||
static void CheckExistingFolder (string inpath, string outpath)
|
||||
{
|
||||
if (Config.GetInt("processingMode") == 0) return;
|
||||
if (!Interpolate.current.stepByStep) return;
|
||||
string tmpFolder = InterpolateUtils.GetTempFolderLoc(inpath, outpath);
|
||||
if (Directory.Exists(tmpFolder))
|
||||
{
|
||||
@@ -72,11 +71,7 @@ namespace Flowframes.UI
|
||||
string msg = $"A temporary folder for this video already exists. It contains {scnFrames}, {srcFrames}, {interpFrames}.";
|
||||
|
||||
DialogResult dialogResult = MessageBox.Show($"{msg}\n\nClick \"Yes\" to use the existing files or \"No\" to delete them.", "Use files from existing temp folder?", MessageBoxButtons.YesNo);
|
||||
if (dialogResult == DialogResult.Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (dialogResult == DialogResult.No)
|
||||
if (dialogResult == DialogResult.No)
|
||||
{
|
||||
IOUtils.TryDeleteIfExists(tmpFolder);
|
||||
Logger.Log("Deleted old temp folder.");
|
||||
@@ -107,7 +102,7 @@ namespace Flowframes.UI
|
||||
{
|
||||
if (!IOUtils.IsPathDirectory(path)) // If path is video - Extract first frame
|
||||
{
|
||||
await FFmpegCommands.ExtractSingleFrame(path, imgOnDisk, 1);
|
||||
await FfmpegExtract.ExtractSingleFrame(path, imgOnDisk, 1);
|
||||
return IOUtils.GetImage(imgOnDisk);
|
||||
}
|
||||
else // Path is frame folder - Get first frame
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Media;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Magick;
|
||||
using Flowframes.Main;
|
||||
using System;
|
||||
@@ -14,17 +15,17 @@ namespace Flowframes.UI
|
||||
class UtilsTab
|
||||
{
|
||||
|
||||
|
||||
public static async Task ExtractVideo(string videoPath, bool withAudio)
|
||||
{
|
||||
string outPath = Path.ChangeExtension(videoPath, null) + "-extracted";
|
||||
Program.mainForm.SetWorking(true);
|
||||
await FFmpegCommands.VideoToFrames(videoPath, Path.Combine(outPath, Paths.framesDir), false, Interpolate.current.inFps, false, false, false);
|
||||
await FfmpegExtract.VideoToFrames(videoPath, Path.Combine(outPath, Paths.framesDir), false, Interpolate.current.inFps, false, false);
|
||||
File.WriteAllText(Path.Combine(outPath, "fps.ini"), Interpolate.current.inFps.ToString());
|
||||
if (withAudio)
|
||||
await FFmpegCommands.ExtractAudio(videoPath, Path.Combine(outPath, "audio"));
|
||||
await FfmpegAudioAndMetadata.ExtractAudio(videoPath, Path.Combine(outPath, "audio"));
|
||||
Program.mainForm.SetWorking(false);
|
||||
Logger.Log("Done.");
|
||||
Program.mainForm.SetProgress(0);
|
||||
}
|
||||
|
||||
public static async Task LoopVideo (string inputFile, ComboBox loopTimes)
|
||||
@@ -33,8 +34,9 @@ namespace Flowframes.UI
|
||||
return;
|
||||
int times = loopTimes.GetInt();
|
||||
Logger.Log("Lopping video " + times + "x...", true);
|
||||
await FFmpegCommands.LoopVideo(inputFile, times, false);
|
||||
await FfmpegCommands.LoopVideo(inputFile, times, false);
|
||||
Logger.Log("Done", true);
|
||||
Program.mainForm.SetProgress(0);
|
||||
}
|
||||
|
||||
public static async Task ChangeSpeed(string inputFile, ComboBox speed)
|
||||
@@ -43,8 +45,9 @@ namespace Flowframes.UI
|
||||
return;
|
||||
float speedFloat = speed.GetFloat();
|
||||
Logger.Log("Creating video with " + speed + "% speed...", true);
|
||||
await FFmpegCommands.ChangeSpeed(inputFile, speedFloat, false);
|
||||
await FfmpegCommands.ChangeSpeed(inputFile, speedFloat, false);
|
||||
Logger.Log("Done", true);
|
||||
Program.mainForm.SetProgress(0);
|
||||
}
|
||||
|
||||
public static async Task Convert(string inputFile, ComboBox crfBox)
|
||||
@@ -54,10 +57,11 @@ namespace Flowframes.UI
|
||||
int crf = crfBox.GetInt();
|
||||
Logger.Log("Creating MP4 with CRF " + crf + "...", true);
|
||||
if(Path.GetExtension(inputFile).ToUpper() != ".MP4")
|
||||
await FFmpegCommands.Encode(inputFile, "libx264", "aac", crf, 128);
|
||||
await FfmpegEncode.Encode(inputFile, "libx264", "aac", crf, 128);
|
||||
else
|
||||
await FFmpegCommands.Encode(inputFile, "libx264", "copy", crf); // Copy audio if input is MP4
|
||||
await FfmpegEncode.Encode(inputFile, "libx264", "copy", crf); // Copy audio if input is MP4
|
||||
Logger.Log("Done", true);
|
||||
Program.mainForm.SetProgress(0);
|
||||
}
|
||||
|
||||
static bool InputIsValid (string inPath)
|
||||
@@ -94,7 +98,7 @@ namespace Flowframes.UI
|
||||
await Task.Delay(10);
|
||||
framesPath = Path.ChangeExtension(inPath, null) + "-frames";
|
||||
Directory.CreateDirectory(framesPath);
|
||||
await Interpolate.ExtractFrames(inPath, framesPath, false);
|
||||
await Interpolate.ExtractFrames(inPath, framesPath, false, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ Flowframes is **open-source donationware**. Builds are released for free on itch
|
||||
|
||||
However, **I do not provide support for self-built versions** as I can't guarantee that the code of this repo is stable at any given moment. Refer to the releases if you want to get the most stable source code.
|
||||
|
||||

|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
RIFE 1.5 - Optimized for general and HD content
|
||||
RIFE 1.6 - Optimized for general and HD/UHD content
|
||||
RIFE 1.5 - Optimized for general content
|
||||
RIFE 1.6 - Updated general model
|
||||
RIFE 1.7 - Optimized for 2D animation (Recommended)
|
||||
RIFE 1.8 - Optimized for all content (Experimental)
|
||||
RIFE 1.8 - Updated 2D animation model (Experimental)
|
||||
@@ -1,3 +1,4 @@
|
||||
RIFE 1.5 - Optimized for general and HD content
|
||||
RIFE 1.6 - Optimized for general and HD/UHD content
|
||||
RIFE 1.5 - Optimized for general content
|
||||
RIFE 1.6 - Updated general model
|
||||
RIFE 1.7 - Optimized for 2D animation (Recommended)
|
||||
RIFE 1.8 - Updated 2D animation model (Experimental)
|
||||
Reference in New Issue
Block a user