mirror of
https://github.com/n00mkrad/flowframes.git
synced 2025-12-24 04:09:29 +01:00
Fixed cutoff if vid ends with dupes, lossless audio transfer, better audio err handl.
This commit is contained in:
@@ -16,11 +16,8 @@ namespace Flowframes
|
|||||||
class FFmpegCommands
|
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 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 videoEncArgs = "-pix_fmt yuv420p -movflags +faststart";
|
|
||||||
static string divisionFilter = "\"crop=trunc(iw/2)*2:trunc(ih/2)*2\"";
|
static string divisionFilter = "\"crop=trunc(iw/2)*2:trunc(ih/2)*2\"";
|
||||||
static string pngComprArg = "-compression_level 3";
|
static string pngComprArg = "-compression_level 3";
|
||||||
|
|
||||||
static string mpDecDef = "\"mpdecimate\"";
|
static string mpDecDef = "\"mpdecimate\"";
|
||||||
static string mpDecAggr = "\"mpdecimate=hi=64*32:lo=64*32:frac=0.1\"";
|
static string mpDecAggr = "\"mpdecimate=hi=64*32:lo=64*32:frac=0.1\"";
|
||||||
|
|
||||||
@@ -79,12 +76,34 @@ namespace Flowframes
|
|||||||
DeleteSource(inpath);
|
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)
|
public static async Task ExtractSingleFrame(string inputFile, string outputPath, int frameNum)
|
||||||
{
|
{
|
||||||
bool isPng = (Path.GetExtension(outputPath).ToLower() == ".png");
|
bool isPng = (Path.GetExtension(outputPath).ToLower() == ".png");
|
||||||
string comprArg = isPng ? pngComprArg : "";
|
string comprArg = isPng ? pngComprArg : "";
|
||||||
string pixFmt = "-pix_fmt " + (isPng ? "rgb24" : "yuvj420p");
|
string pixFmt = "-pix_fmt " + (isPng ? $"rgb24 {comprArg}" : "yuvj420p");
|
||||||
string args = $"-i {inputFile.Wrap()} {comprArg} -vf \"select=eq(n\\,{frameNum})\" -vframes 1 {pixFmt} {outputPath.Wrap()}";
|
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);
|
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden, AvProcess.TaskType.ExtractFrames);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,31 +198,54 @@ namespace Flowframes
|
|||||||
|
|
||||||
public static async Task ExtractAudio(string inputFile, string outFile) // https://stackoverflow.com/a/27413824/14274419
|
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);
|
Logger.Log($"[FFCmds] Extracting audio from {inputFile} to {outFile}", true);
|
||||||
outFile = Path.ChangeExtension(outFile, ".ogg");
|
string args = $" -loglevel panic -i {inputFile.Wrap()} -vn -c:a copy {outFile.Wrap()}";
|
||||||
string args = $" -loglevel panic -i {inputFile.Wrap()} -vn -acodec libopus -b:a 256k {outFile.Wrap()}";
|
|
||||||
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
|
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
|
||||||
if (AvProcess.lastOutputFfmpeg.ToLower().Contains("error") && File.Exists(outFile)) // If broken file was written
|
if (File.Exists(outFile) && IOUtils.GetFilesize(outFile) < 512)
|
||||||
|
{
|
||||||
|
Logger.Log("Failed to extract audio losslessly! Trying to re-encode.");
|
||||||
File.Delete(outFile);
|
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 MergeAudio(string inputFile, string audioPath, int looptimes = -1) // https://superuser.com/a/277667
|
public static async Task MergeAudio(string inputFile, string audioPath, int looptimes = -1) // https://superuser.com/a/277667
|
||||||
{
|
{
|
||||||
Logger.Log($"[FFCmds] Merging audio from {audioPath} into {inputFile}", true);
|
Logger.Log($"[FFCmds] Merging audio from {audioPath} into {inputFile}", true);
|
||||||
string tempPath = inputFile + "-temp" + Path.GetExtension(inputFile);
|
string tempPath = inputFile + "-temp" + Path.GetExtension(inputFile);
|
||||||
// if (Path.GetExtension(audioPath) == ".wav")
|
string args = $" -i {inputFile.Wrap()} -stream_loop {looptimes} -i {audioPath.Wrap()} -shortest -c copy {tempPath.Wrap()}";
|
||||||
// {
|
|
||||||
// Logger.Log("Using MKV instead of MP4 to enable support for raw audio.");
|
|
||||||
// tempPath = Path.ChangeExtension(tempPath, "mkv");
|
|
||||||
// }
|
|
||||||
string aCodec = Utils.GetAudioEnc(Utils.GetCodec(Interpolate.current.outMode));
|
|
||||||
int aKbits = Utils.GetAudioKbits(aCodec);
|
|
||||||
string args = $" -i {inputFile.Wrap()} -stream_loop {looptimes} -i {audioPath.Wrap()} -shortest -c:v copy -c:a {aCodec} -b:a {aKbits}k {tempPath.Wrap()}";
|
|
||||||
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
|
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
|
||||||
if (AvProcess.lastOutputFfmpeg.Contains("Invalid data"))
|
if ((File.Exists(tempPath) && IOUtils.GetFilesize(tempPath) < 512) || AvProcess.lastOutputFfmpeg.Contains("Invalid data"))
|
||||||
{
|
{
|
||||||
Logger.Log("Failed to merge audio!");
|
Logger.Log("Failed to merge audio losslessly! Trying to re-encode.");
|
||||||
return;
|
|
||||||
|
args = $" -i {inputFile.Wrap()} -stream_loop {looptimes} -i {audioPath.Wrap()} -shortest -c:v copy {Utils.GetAudioFallbackArgs(Path.GetExtension(inputFile))} {tempPath.Wrap()}";
|
||||||
|
await AvProcess.RunFfmpeg(args, AvProcess.LogMode.Hidden);
|
||||||
|
|
||||||
|
if ((File.Exists(tempPath) && IOUtils.GetFilesize(tempPath) < 512) || AvProcess.lastOutputFfmpeg.Contains("Invalid data"))
|
||||||
|
{
|
||||||
|
Logger.Log("Failed to merge audio, even with re-encoding. Output will not have audio.");
|
||||||
|
IOUtils.TryDeleteIfExists(tempPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string audioExt = Path.GetExtension(audioPath).Remove(".").ToUpper();
|
||||||
|
string containerExt = Path.GetExtension(inputFile).Remove(".").ToUpper();
|
||||||
|
Logger.Log($"Source audio ({audioExt}) has been re-encoded to fit into the target container ({containerExt}). This may decrease the quality slightly.", false, true);
|
||||||
}
|
}
|
||||||
string movePath = Path.ChangeExtension(inputFile, Path.GetExtension(tempPath));
|
string movePath = Path.ChangeExtension(inputFile, Path.GetExtension(tempPath));
|
||||||
File.Delete(movePath);
|
File.Delete(movePath);
|
||||||
|
|||||||
@@ -104,16 +104,6 @@ namespace Flowframes.AudioVideo
|
|||||||
return "aac";
|
return "aac";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int GetAudioKbits (string codec)
|
|
||||||
{
|
|
||||||
if (codec.Trim().ToLower() == "aac")
|
|
||||||
return Config.GetInt("aacBitrate", 160);
|
|
||||||
if (codec.Trim().ToLower().Contains("opus"))
|
|
||||||
return Config.GetInt("opusBitrate", 128);
|
|
||||||
|
|
||||||
return 192;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetExt(Interpolate.OutMode outMode, bool dot = true)
|
public static string GetExt(Interpolate.OutMode outMode, bool dot = true)
|
||||||
{
|
{
|
||||||
string ext = dot ? "." : "";
|
string ext = dot ? "." : "";
|
||||||
@@ -130,7 +120,7 @@ namespace Flowframes.AudioVideo
|
|||||||
return ext;
|
return ext;
|
||||||
}
|
}
|
||||||
|
|
||||||
static string GetAudioExt(string videoFile)
|
public static string GetAudioExt(string videoFile)
|
||||||
{
|
{
|
||||||
switch (FFmpegCommands.GetAudioCodec(videoFile))
|
switch (FFmpegCommands.GetAudioCodec(videoFile))
|
||||||
{
|
{
|
||||||
@@ -141,5 +131,31 @@ namespace Flowframes.AudioVideo
|
|||||||
default: return "wav";
|
default: return "wav";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetAudioFallbackArgs (string containerExt)
|
||||||
|
{
|
||||||
|
containerExt = containerExt.Remove(".");
|
||||||
|
string codec = "aac";
|
||||||
|
string bitrate = $"{Config.GetInt("aacBitrate", 160)}k";
|
||||||
|
|
||||||
|
if(containerExt == "webm" || containerExt == "mkv")
|
||||||
|
{
|
||||||
|
codec = "libopus";
|
||||||
|
bitrate = $"{Config.GetInt("opusBitrate", 128)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"-c:a {codec} -b:a {bitrate} -ac 2";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetAudioExtForContainer(string containerExt)
|
||||||
|
{
|
||||||
|
containerExt = containerExt.Remove(".");
|
||||||
|
string ext = "m4a";
|
||||||
|
|
||||||
|
if (containerExt == "webm" || containerExt == "mkv")
|
||||||
|
ext = "ogg";
|
||||||
|
|
||||||
|
return ext;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ namespace Flowframes.Forms
|
|||||||
{
|
{
|
||||||
LoadSettings();
|
LoadSettings();
|
||||||
initialized = true;
|
initialized = true;
|
||||||
LazyLoadingStuff();
|
CheckModelCacheSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LazyLoadingStuff ()
|
public async Task CheckModelCacheSize ()
|
||||||
{
|
{
|
||||||
long modelFoldersBytes = 0;
|
long modelFoldersBytes = 0;
|
||||||
|
|
||||||
@@ -196,6 +196,7 @@ namespace Flowframes.Forms
|
|||||||
{
|
{
|
||||||
ModelDownloader.DeleteAllModels();
|
ModelDownloader.DeleteAllModels();
|
||||||
clearModelCacheBtn.Text = "Clear Model Cache";
|
clearModelCacheBtn.Text = "Clear Model Cache";
|
||||||
|
CheckModelCacheSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ namespace Flowframes.Magick
|
|||||||
sw.Restart();
|
sw.Restart();
|
||||||
Logger.Log($"[Deduplication] Running de-duplication ({i}/{framePaths.Length}), deleted {statsFramesDeleted} ({(((float)statsFramesDeleted / framePaths.Length) * 100f).ToString("0")}%) duplicate frames so far...", false, true);
|
Logger.Log($"[Deduplication] Running de-duplication ({i}/{framePaths.Length}), deleted {statsFramesDeleted} ({(((float)statsFramesDeleted / framePaths.Length) * 100f).ToString("0")}%) duplicate frames so far...", false, true);
|
||||||
Program.mainForm.SetProgress((int)Math.Round(((float)i / framePaths.Length) * 100f));
|
Program.mainForm.SetProgress((int)Math.Round(((float)i / framePaths.Length) * 100f));
|
||||||
if (imageCache.Count > bufferSize || (imageCache.Count > 50 && OSUtils.GetFreeRamMb() < 2500))
|
if (imageCache.Count > bufferSize || (imageCache.Count > 50 && OSUtils.GetFreeRamMb() < 3500))
|
||||||
ClearCache();
|
ClearCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,10 +126,12 @@ namespace Flowframes
|
|||||||
else
|
else
|
||||||
Dedupe.ClearCache();
|
Dedupe.ClearCache();
|
||||||
|
|
||||||
if (Config.GetInt("dedupMode") == 2 || Config.GetInt("dedupMode") == 1)
|
await Utils.CopyLastFrame(currentInputFrameCount);
|
||||||
|
|
||||||
|
if (Config.GetInt("dedupMode") > 0)
|
||||||
await Dedupe.CreateDupesFile(current.framesFolder, currentInputFrameCount);
|
await Dedupe.CreateDupesFile(current.framesFolder, currentInputFrameCount);
|
||||||
|
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
bool useTimestamps = Config.GetInt("timingMode") == 1; // TODO: Auto-Disable timestamps if input frames are sequential, not timestamped
|
bool useTimestamps = Config.GetInt("timingMode") == 1; // TODO: Auto-Disable timestamps if input frames are sequential, not timestamped
|
||||||
await FrameOrder.CreateTimecodeFiles(current.framesFolder, FrameOrder.Mode.CFR, Config.GetBool("enableLoop"), current.interpFactor, !useTimestamps);
|
await FrameOrder.CreateTimecodeFiles(current.framesFolder, FrameOrder.Mode.CFR, Config.GetBool("enableLoop"), current.interpFactor, !useTimestamps);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using i = Flowframes.Interpolate;
|
using i = Flowframes.Interpolate;
|
||||||
|
using Padding = Flowframes.Data.Padding;
|
||||||
|
|
||||||
namespace Flowframes.Main
|
namespace Flowframes.Main
|
||||||
{
|
{
|
||||||
@@ -22,6 +23,33 @@ namespace Flowframes.Main
|
|||||||
public static PictureBox preview;
|
public static PictureBox preview;
|
||||||
public static BigPreviewForm bigPreviewForm;
|
public static BigPreviewForm bigPreviewForm;
|
||||||
|
|
||||||
|
public static async Task CopyLastFrame (int lastFrameNum)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lastFrameNum--; // We have to do this as extracted frames start at 0, not 1
|
||||||
|
bool frameFolderInput = IOUtils.IsPathDirectory(i.current.inPath);
|
||||||
|
string targetPath = Path.Combine(i.current.framesFolder, lastFrameNum.ToString().PadLeft(Padding.inputFrames, '0') + ".png");
|
||||||
|
if (File.Exists(targetPath)) return;
|
||||||
|
|
||||||
|
Size res = IOUtils.GetImage(IOUtils.GetFilesSorted(i.current.framesFolder, false).First()).Size;
|
||||||
|
|
||||||
|
if (frameFolderInput)
|
||||||
|
{
|
||||||
|
string lastFramePath = IOUtils.GetFilesSorted(i.current.inPath, false).Last();
|
||||||
|
await FFmpegCommands.ExtractLastFrame(lastFramePath, targetPath, res);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await FFmpegCommands.ExtractLastFrame(i.current.inPath, targetPath, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Log("CopyLastFrame Error: " + e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string GetOutExt (bool withDot = false)
|
public static string GetOutExt (bool withDot = false)
|
||||||
{
|
{
|
||||||
string dotStr = withDot ? "." : "";
|
string dotStr = withDot ? "." : "";
|
||||||
|
|||||||
Reference in New Issue
Block a user