Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Dankrushen
2021-02-08 15:16:37 -05:00
51 changed files with 1702 additions and 1073 deletions

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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
}
}

View File

@@ -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;
}
}
}

View File

@@ -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];
}
}
}

View File

@@ -9,6 +9,7 @@ namespace Flowframes.Data
class Padding
{
public const int inputFrames = 9;
public const int inputFramesRenamed = 8;
public const int interpFrames = 8;
}
}

View File

@@ -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
View 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;
}
}
}

View File

@@ -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}";
}
}
}

View 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();
}
}
}

View File

@@ -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;
}

View File

@@ -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" />

View File

@@ -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
//

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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}");
}
}
}
}

View File

@@ -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))

View File

@@ -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);
}

View File

@@ -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)
{

View File

@@ -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");

View File

@@ -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)
{

View File

@@ -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

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
View 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;
}
}
}

View File

@@ -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 ()

View File

@@ -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
View 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);
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}

View 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
View 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);
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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)

View File

@@ -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}).");
}
}
}

View File

@@ -24,7 +24,6 @@ namespace Flowframes
[STAThread]
static void Main()
{
Paths.Init();
Config.Init();
if (Config.GetBool("delLogsOnStartup"))

View File

@@ -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

View File

@@ -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.";
}
}
}
}

View File

@@ -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

View File

@@ -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
{

View File

@@ -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.
![img](https://img.itch.zone/aW1hZ2UvNzU1NTQwLzQ2MTYyMzMucG5n/original/ENGtu5.png)
![img](https://i.imgur.com/HHZxUYo.png)
## Installation

View File

@@ -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)

View File

@@ -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)