mirror of
https://github.com/n00mkrad/flowframes.git
synced 2026-09-01 19:51:28 +02:00
split FFmpegCommands into multiple classes for extraction/encoding
This commit is contained in:
@@ -10,126 +10,17 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static Flowframes.AvProcess;
|
||||
using Utils = Flowframes.AudioVideo.FFmpegUtils;
|
||||
using TaskType = Flowframes.AvProcess.TaskType;
|
||||
using LogMode = Flowframes.AvProcess.LogMode;
|
||||
|
||||
namespace Flowframes
|
||||
{
|
||||
class FFmpegCommands
|
||||
{
|
||||
static string divisionFilter = "\"pad=width=ceil(iw/2)*2:height=ceil(ih/2)*2:color=black@0\"";
|
||||
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[] { 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 args = $" -loglevel panic -f concat -safe 0 -i {concatFile.Wrap()} {pngComprArg} {sizeStr} {pixFmt} -vsync 0 -vf {divisionFilter} \"{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);
|
||||
}
|
||||
|
||||
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 AvProcess.RunFfmpeg(args, framesFile.GetParentDir(), logMode, TaskType.Encode, !isChunk);
|
||||
}
|
||||
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)
|
||||
{
|
||||
@@ -137,7 +28,7 @@ namespace Flowframes
|
||||
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(), LogMode.Hidden, TaskType.Merge);
|
||||
await RunFfmpeg(args, concatFile.GetParentDir(), LogMode.Hidden, TaskType.Merge);
|
||||
}
|
||||
|
||||
public static async Task FramesToGifConcat(string framesFile, string outPath, float fps, bool palette, int colors = 64, float resampleFps = -1, LogMode logMode = LogMode.OnlyLastLine)
|
||||
@@ -150,7 +41,7 @@ namespace Flowframes
|
||||
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(), LogMode.OnlyLastLine, TaskType.Encode);
|
||||
await RunFfmpeg(args, framesFile.GetParentDir(), LogMode.OnlyLastLine, TaskType.Encode);
|
||||
}
|
||||
|
||||
public static async Task ExtractAlphaDir (string rgbDir, string alphaDir)
|
||||
@@ -159,7 +50,7 @@ namespace Flowframes
|
||||
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 AvProcess.RunFfmpeg(args, LogMode.Hidden);
|
||||
await RunFfmpeg(args, LogMode.Hidden);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +63,7 @@ namespace Flowframes
|
||||
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 AvProcess.RunFfmpeg(args, LogMode.Hidden);
|
||||
await RunFfmpeg(args, LogMode.Hidden);
|
||||
file.Delete();
|
||||
File.Move(outFilename, file.FullName);
|
||||
}
|
||||
@@ -182,7 +73,7 @@ namespace Flowframes
|
||||
{
|
||||
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, LogMode.Hidden);
|
||||
await RunFfmpeg(args, LogMode.Hidden);
|
||||
if (deleteAlphaDir)
|
||||
IOUtils.TryDeleteIfExists(alphaDir);
|
||||
}
|
||||
@@ -192,7 +83,7 @@ namespace Flowframes
|
||||
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, LogMode.Hidden);
|
||||
await RunFfmpeg(args, LogMode.Hidden);
|
||||
if (delSrc)
|
||||
DeleteSource(inputFile);
|
||||
}
|
||||
@@ -204,20 +95,7 @@ namespace Flowframes
|
||||
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, 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, LogMode.OnlyLastLine, TaskType.Encode, true);
|
||||
await RunFfmpeg(args, LogMode.OnlyLastLine);
|
||||
if (delSrc)
|
||||
DeleteSource(inputFile);
|
||||
}
|
||||
@@ -228,7 +106,7 @@ namespace Flowframes
|
||||
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, LogMode.Hidden);
|
||||
await RunFfmpeg(args, LogMode.Hidden);
|
||||
if (File.Exists(outFile) && IOUtils.GetFilesize(outFile) < 512)
|
||||
{
|
||||
Logger.Log("Failed to extract audio losslessly! Trying to re-encode.");
|
||||
@@ -236,9 +114,9 @@ namespace Flowframes
|
||||
|
||||
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, LogMode.Hidden);
|
||||
await RunFfmpeg(args, LogMode.Hidden);
|
||||
|
||||
if ((File.Exists(outFile) && IOUtils.GetFilesize(outFile) < 512) || AvProcess.lastOutputFfmpeg.Contains("Invalid data"))
|
||||
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);
|
||||
@@ -258,8 +136,8 @@ namespace Flowframes
|
||||
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, LogMode.Hidden);
|
||||
if (AvProcess.lastOutputFfmpeg.Contains("matches no streams")) // Break if there are no more subtitle tracks
|
||||
await RunFfmpeg(args, LogMode.Hidden);
|
||||
if (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");
|
||||
}
|
||||
@@ -275,7 +153,7 @@ namespace Flowframes
|
||||
{
|
||||
Dictionary<int, string> subDict = new Dictionary<int, string>();
|
||||
string args = $"-i {inputFile.Wrap()}";
|
||||
string[] outputLines = (await AvProcess.GetFfmpegOutputAsync(args)).SplitIntoLines();
|
||||
string[] outputLines = (await GetFfmpegOutputAsync(args)).SplitIntoLines();
|
||||
string[] filteredLines = outputLines.Where(l => l.Contains(" Subtitle: ")).ToArray();
|
||||
int idx = 0;
|
||||
foreach(string line in filteredLines)
|
||||
@@ -318,18 +196,18 @@ namespace Flowframes
|
||||
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, LogMode.Hidden);
|
||||
await RunFfmpeg(args, tempFolder, LogMode.Hidden);
|
||||
|
||||
if ((File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024) || AvProcess.lastOutputFfmpeg.Contains("Invalid data") || AvProcess.lastOutputFfmpeg.Contains("Error initializing output stream"))
|
||||
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 AvProcess.RunFfmpeg(args, tempFolder, LogMode.Hidden);
|
||||
await RunFfmpeg(args, tempFolder, LogMode.Hidden);
|
||||
|
||||
if ((File.Exists(outPath) && IOUtils.GetFilesize(outPath) < 1024) || AvProcess.lastOutputFfmpeg.Contains("Invalid data") || AvProcess.lastOutputFfmpeg.Contains("Error initializing output stream"))
|
||||
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.TryDeleteIfExists(tempPath);
|
||||
@@ -340,8 +218,6 @@ namespace Flowframes
|
||||
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);
|
||||
@@ -357,7 +233,7 @@ namespace Flowframes
|
||||
{
|
||||
Logger.Log("Reading Duration using ffprobe.", true, false, "ffprobe");
|
||||
string args = $" -v panic -select_streams v:0 -show_entries format=duration -of csv=s=x:p=0 -sexagesimal {inputFile.Wrap()}";
|
||||
string info = AvProcess.GetFfprobeOutput(args);
|
||||
string info = GetFfprobeOutput(args);
|
||||
return FormatUtils.MsFromTimestamp(info);
|
||||
return -1;
|
||||
}
|
||||
@@ -366,7 +242,7 @@ namespace Flowframes
|
||||
{
|
||||
Logger.Log("Reading FPS using ffmpeg.", true, false, "ffmpeg");
|
||||
string args = $" -i {inputFile.Wrap()}";
|
||||
string output = AvProcess.GetFfmpegOutput(args);
|
||||
string output = GetFfmpegOutput(args);
|
||||
string[] entries = output.Split(',');
|
||||
foreach (string entry in entries)
|
||||
{
|
||||
@@ -385,7 +261,7 @@ namespace Flowframes
|
||||
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);
|
||||
string output = GetFfprobeOutput(args);
|
||||
|
||||
if (output.Length > 4 && output.Contains("x"))
|
||||
{
|
||||
@@ -439,7 +315,7 @@ namespace Flowframes
|
||||
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 info = GetFfprobeOutput(args);
|
||||
string[] entries = info.SplitIntoLines();
|
||||
try
|
||||
{
|
||||
@@ -464,7 +340,7 @@ namespace Flowframes
|
||||
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 info = GetFfprobeOutput(args);
|
||||
string[] entries = info.SplitIntoLines();
|
||||
try
|
||||
{
|
||||
@@ -483,7 +359,7 @@ namespace Flowframes
|
||||
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 info = GetFfmpegOutput(args);
|
||||
string[] entries = info.SplitIntoLines();
|
||||
foreach (string entry in entries)
|
||||
{
|
||||
@@ -496,7 +372,7 @@ namespace Flowframes
|
||||
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);
|
||||
string info = await GetFfmpegOutputAsync(args, true);
|
||||
try
|
||||
{
|
||||
string[] lines = info.SplitIntoLines();
|
||||
@@ -512,7 +388,7 @@ namespace Flowframes
|
||||
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 info = GetFfprobeOutput(args);
|
||||
string[] entries = info.SplitIntoLines();
|
||||
foreach (string entry in entries)
|
||||
{
|
||||
@@ -522,7 +398,7 @@ namespace Flowframes
|
||||
return "";
|
||||
}
|
||||
|
||||
static void DeleteSource(string path)
|
||||
public static void DeleteSource(string path)
|
||||
{
|
||||
Logger.Log("[FFCmds] Deleting input file/dir: " + path, true);
|
||||
|
||||
|
||||
52
Code/AudioVideo/FfmpegEncode.cs
Normal file
52
Code/AudioVideo/FfmpegEncode.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Main;
|
||||
using Flowframes.MiscUtils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using static Flowframes.AvProcess;
|
||||
using Utils = Flowframes.AudioVideo.FFmpegUtils;
|
||||
|
||||
namespace Flowframes.AudioVideo
|
||||
{
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
106
Code/AudioVideo/FfmpegExtract.cs
Normal file
106
Code/AudioVideo/FfmpegExtract.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
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.AudioVideo
|
||||
{
|
||||
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), 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[] { 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 args = $" -loglevel panic -f concat -safe 0 -i {concatFile.Wrap()} {pngComprArg} {sizeStr} {pixFmt} -vsync 0 -vf {divisionFilter} \"{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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,6 +200,8 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AudioVideo\FfmpegEncode.cs" />
|
||||
<Compile Include="AudioVideo\FfmpegExtract.cs" />
|
||||
<Compile Include="AudioVideo\FFmpegUtils.cs" />
|
||||
<Compile Include="Data\AI.cs" />
|
||||
<Compile Include="Data\InterpSettings.cs" />
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace Flowframes.Main
|
||||
}
|
||||
else
|
||||
{
|
||||
await FFmpegCommands.FramesToVideoConcat(vfrFile, outPath, mode, fps, resampleFps);
|
||||
await FfmpegEncode.FramesToVideoConcat(vfrFile, outPath, mode, fps, resampleFps);
|
||||
await MergeAudio(i.current.inPath, outPath);
|
||||
await Loop(currentOutFile, GetLoopTimes());
|
||||
}
|
||||
@@ -166,14 +166,14 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Flowframes;
|
||||
using Flowframes.AudioVideo;
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Magick;
|
||||
@@ -66,7 +67,7 @@ namespace Flowframes
|
||||
if (!current.inputIsFrames) // Input is video - extract frames first
|
||||
await ExtractFrames(current.inPath, current.framesFolder, current.alpha);
|
||||
else
|
||||
await FFmpegCommands.ImportImages(current.inPath, current.framesFolder, current.alpha, await Utils.GetOutputResolution(current.inPath, true));
|
||||
await FfmpegExtract.ImportImages(current.inPath, current.framesFolder, current.alpha, await Utils.GetOutputResolution(current.inPath, true));
|
||||
|
||||
if (current.alpha)
|
||||
{
|
||||
@@ -83,13 +84,13 @@ namespace Flowframes
|
||||
if (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);
|
||||
}
|
||||
|
||||
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, true), false);
|
||||
await FfmpegExtract.VideoToFrames(inPath, outPath, alpha, current.inFps, mpdecimate, false, await Utils.GetOutputResolution(inPath, true, true), false);
|
||||
|
||||
if (mpdecimate)
|
||||
{
|
||||
|
||||
@@ -63,7 +63,7 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Flowframes.Data;
|
||||
using Flowframes.AudioVideo;
|
||||
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)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Flowframes.IO;
|
||||
using Flowframes.AudioVideo;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Magick;
|
||||
using Flowframes.Main;
|
||||
using Flowframes.OS;
|
||||
@@ -105,7 +106,7 @@ namespace Flowframes.UI
|
||||
{
|
||||
if (!IOUtils.IsPathDirectory(path)) // If path is video - Extract first frame
|
||||
{
|
||||
await FFmpegCommands.ExtractSingleFrame(path, imgOnDisk, 1);
|
||||
await FfmpegExtract.ExtractSingleFrame(path, imgOnDisk, 1);
|
||||
return IOUtils.GetImage(imgOnDisk);
|
||||
}
|
||||
else // Path is frame folder - Get first frame
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Flowframes.IO;
|
||||
using Flowframes.AudioVideo;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.Magick;
|
||||
using Flowframes.Main;
|
||||
using System;
|
||||
@@ -18,7 +19,7 @@ namespace Flowframes.UI
|
||||
{
|
||||
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, false);
|
||||
File.WriteAllText(Path.Combine(outPath, "fps.ini"), Interpolate.current.inFps.ToString());
|
||||
if (withAudio)
|
||||
await FFmpegCommands.ExtractAudio(videoPath, Path.Combine(outPath, "audio"));
|
||||
@@ -56,9 +57,9 @@ 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user