mirror of
https://github.com/n00mkrad/flowframes.git
synced 2025-12-25 04:39:25 +01:00
Case-sensitive rename operation (1/2)
This commit is contained in:
@@ -1,211 +0,0 @@
|
||||
using Flowframes.IO;
|
||||
using Flowframes.OS;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Flowframes.MiscUtils;
|
||||
using Microsoft.VisualBasic;
|
||||
|
||||
namespace Flowframes
|
||||
{
|
||||
class AvProcess
|
||||
{
|
||||
public static Process lastAvProcess;
|
||||
public static Stopwatch timeSinceLastOutput = new Stopwatch();
|
||||
public enum TaskType { ExtractFrames, ExtractOther, Encode, GetInfo, Merge, Other };
|
||||
public static TaskType lastTask = TaskType.Other;
|
||||
|
||||
public static string lastOutputFfmpeg;
|
||||
|
||||
public enum LogMode { Visible, OnlyLastLine, Hidden }
|
||||
static LogMode currentLogMode;
|
||||
static bool showProgressBar;
|
||||
|
||||
static readonly string defLogLevel = "warning";
|
||||
|
||||
public static void Kill()
|
||||
{
|
||||
if (lastAvProcess == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
OSUtils.KillProcessTree(lastAvProcess.Id);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Log($"Failed to kill lastAvProcess process tree: {e.Message}", true);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task RunFfmpeg(string args, LogMode logMode, TaskType taskType = TaskType.Other, bool progressBar = false)
|
||||
{
|
||||
await RunFfmpeg(args, "", logMode, defLogLevel, taskType, progressBar);
|
||||
}
|
||||
|
||||
public static async Task RunFfmpeg(string args, LogMode logMode, string loglevel, TaskType taskType = TaskType.Other, bool progressBar = false)
|
||||
{
|
||||
await RunFfmpeg(args, "", logMode, loglevel, taskType, progressBar);
|
||||
}
|
||||
|
||||
public static async Task RunFfmpeg(string args, string workingDir, LogMode logMode, TaskType taskType = TaskType.Other, bool progressBar = false)
|
||||
{
|
||||
await RunFfmpeg(args, workingDir, logMode, defLogLevel, taskType, progressBar);
|
||||
}
|
||||
|
||||
public static async Task RunFfmpeg(string args, string workingDir, LogMode logMode, string loglevel, TaskType taskType = TaskType.Other, bool progressBar = false)
|
||||
{
|
||||
lastOutputFfmpeg = "";
|
||||
currentLogMode = logMode;
|
||||
showProgressBar = progressBar;
|
||||
Process ffmpeg = OSUtils.NewProcess(true);
|
||||
timeSinceLastOutput.Restart();
|
||||
lastAvProcess = ffmpeg;
|
||||
lastTask = taskType;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(loglevel))
|
||||
loglevel = defLogLevel;
|
||||
|
||||
string beforeArgs = $"-hide_banner -stats -loglevel {loglevel} -y";
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(workingDir))
|
||||
ffmpeg.StartInfo.Arguments = $"{GetCmdArg()} cd /D {workingDir.Wrap()} & {Path.Combine(GetAvDir(), "ffmpeg.exe").Wrap()} {beforeArgs} {args}";
|
||||
else
|
||||
ffmpeg.StartInfo.Arguments = $"{GetCmdArg()} cd /D {GetAvDir().Wrap()} & ffmpeg.exe {beforeArgs} {args}";
|
||||
|
||||
if (logMode != LogMode.Hidden) Logger.Log("Running FFmpeg...", false);
|
||||
Logger.Log($"ffmpeg {beforeArgs} {args}", true, false, "ffmpeg");
|
||||
ffmpeg.OutputDataReceived += FfmpegOutputHandler;
|
||||
ffmpeg.ErrorDataReceived += FfmpegOutputHandler;
|
||||
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 (Interpolate.canceled || outLine == null || outLine.Data == null)
|
||||
return;
|
||||
|
||||
string line = outLine.Data;
|
||||
lastOutputFfmpeg = lastOutputFfmpeg + "\n" + line;
|
||||
|
||||
bool hidden = currentLogMode == LogMode.Hidden;
|
||||
|
||||
if (HideMessage(line)) // Don't print certain warnings
|
||||
hidden = true;
|
||||
|
||||
bool replaceLastLine = currentLogMode == LogMode.OnlyLastLine;
|
||||
|
||||
if (line.StartsWith("frame="))
|
||||
line = FormatUtils.BeautifyFfmpegStats(line);
|
||||
|
||||
Logger.Log(line, hidden, replaceLastLine, "ffmpeg");
|
||||
|
||||
if (line.Contains(".srt: Invalid data found"))
|
||||
Logger.Log($"Warning: Failed to encode subtitle track {line.Split(':')[2]}. This track will be missing in the output file.");
|
||||
|
||||
if (line.Contains("Could not open file"))
|
||||
Interpolate.Cancel($"FFmpeg Error: {line}");
|
||||
|
||||
if (line.Contains("No NVENC capable devices found") || line.MatchesWildcard("*nvcuda.dll*"))
|
||||
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 bool HideMessage (string msg)
|
||||
{
|
||||
string[] hiddenMsgs = new string[] { "can produce invalid output", "pixel format", "provided invalid" };
|
||||
|
||||
foreach (string str in hiddenMsgs)
|
||||
if (msg.MatchesWildcard($"*{str}*"))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static async Task<string> GetFfmpegOutputAsync(string args, bool setBusy = false, bool progressBar = false)
|
||||
{
|
||||
timeSinceLastOutput.Restart();
|
||||
if (Program.busy) setBusy = false;
|
||||
lastOutputFfmpeg = "";
|
||||
showProgressBar = progressBar;
|
||||
Process ffmpeg = OSUtils.NewProcess(true);
|
||||
lastAvProcess = ffmpeg;
|
||||
ffmpeg.StartInfo.Arguments = $"{GetCmdArg()} cd /D {GetAvDir().Wrap()} & ffmpeg.exe -hide_banner -y -stats {args}";
|
||||
Logger.Log($"ffmpeg {args}", true, false, "ffmpeg");
|
||||
if (setBusy) Program.mainForm.SetWorking(true);
|
||||
lastOutputFfmpeg = await OSUtils.GetOutputAsync(ffmpeg);
|
||||
while (!ffmpeg.HasExited) await Task.Delay(50);
|
||||
while(timeSinceLastOutput.ElapsedMilliseconds < 200) await Task.Delay(50);
|
||||
if (setBusy) Program.mainForm.SetWorking(false);
|
||||
return lastOutputFfmpeg;
|
||||
}
|
||||
|
||||
public static string GetFfprobeOutput (string args)
|
||||
{
|
||||
Process ffprobe = OSUtils.NewProcess(true);
|
||||
ffprobe.StartInfo.Arguments = $"{GetCmdArg()} cd /D {GetAvDir().Wrap()} & ffprobe.exe {args}";
|
||||
Logger.Log($"ffprobe {args}", true, false, "ffmpeg");
|
||||
ffprobe.Start();
|
||||
ffprobe.WaitForExit();
|
||||
string output = ffprobe.StandardOutput.ReadToEnd();
|
||||
string err = ffprobe.StandardError.ReadToEnd();
|
||||
if (!string.IsNullOrWhiteSpace(err)) output += "\n" + err;
|
||||
return output;
|
||||
}
|
||||
|
||||
public static void UpdateFfmpegProgress(string ffmpegTime)
|
||||
{
|
||||
Form1 form = Program.mainForm;
|
||||
long currInDuration = (form.currInDurationCut < form.currInDuration) ? form.currInDurationCut : form.currInDuration;
|
||||
|
||||
if (currInDuration < 1)
|
||||
{
|
||||
Program.mainForm.SetProgress(0);
|
||||
return;
|
||||
}
|
||||
|
||||
long total = currInDuration / 100;
|
||||
long current = FormatUtils.TimestampToMs(ffmpegTime);
|
||||
int progress = Convert.ToInt32(current / total);
|
||||
Program.mainForm.SetProgress(progress);
|
||||
}
|
||||
|
||||
static string GetAvDir ()
|
||||
{
|
||||
return Path.Combine(Paths.GetPkgPath(), Paths.audioVideoDir);
|
||||
}
|
||||
|
||||
static string GetCmdArg ()
|
||||
{
|
||||
return "/C";
|
||||
}
|
||||
|
||||
public static async Task SetBusyWhileRunning ()
|
||||
{
|
||||
if (Program.busy) return;
|
||||
|
||||
await Task.Delay(100);
|
||||
while(!lastAvProcess.HasExited)
|
||||
await Task.Delay(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
using Flowframes.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Flowframes.Media
|
||||
{
|
||||
class FFmpegUtils
|
||||
{
|
||||
public enum Codec { H264, H265, H264NVENC, H265NVENC, AV1, VP9, ProRes, AviRaw }
|
||||
|
||||
|
||||
public static Codec GetCodec(Interpolate.OutMode mode)
|
||||
{
|
||||
if (mode == Interpolate.OutMode.VidMp4 || mode == Interpolate.OutMode.VidMkv)
|
||||
{
|
||||
int mp4Enc = Config.GetInt(Config.Key.mp4Enc);
|
||||
if (mp4Enc == 0) return Codec.H264;
|
||||
if (mp4Enc == 1) return Codec.H265;
|
||||
if (mp4Enc == 2) return Codec.H264NVENC;
|
||||
if (mp4Enc == 3) return Codec.H265NVENC;
|
||||
if (mp4Enc == 4) return Codec.AV1;
|
||||
}
|
||||
|
||||
if (mode == Interpolate.OutMode.VidWebm)
|
||||
return Codec.VP9;
|
||||
|
||||
if (mode == Interpolate.OutMode.VidProRes)
|
||||
return Codec.ProRes;
|
||||
|
||||
if (mode == Interpolate.OutMode.VidAvi)
|
||||
return Codec.AviRaw;
|
||||
|
||||
return Codec.H264;
|
||||
}
|
||||
|
||||
public static string GetEnc(Codec codec)
|
||||
{
|
||||
switch (codec)
|
||||
{
|
||||
case Codec.H264: return "libx264";
|
||||
case Codec.H265: return "libx265";
|
||||
case Codec.H264NVENC: return "h264_nvenc";
|
||||
case Codec.H265NVENC: return "hevc_nvenc";
|
||||
case Codec.AV1: return "libsvtav1";
|
||||
case Codec.VP9: return "libvpx-vp9";
|
||||
case Codec.ProRes: return "prores_ks";
|
||||
case Codec.AviRaw: return Config.Get(Config.Key.aviCodec);
|
||||
}
|
||||
return "libx264";
|
||||
}
|
||||
|
||||
public static string GetEncArgs (Codec codec)
|
||||
{
|
||||
string args = $"-c:v {GetEnc(codec)} ";
|
||||
|
||||
if(codec == Codec.H264)
|
||||
{
|
||||
string preset = Config.Get(Config.Key.ffEncPreset).ToLower().Remove(" ");
|
||||
args += $"-crf {Config.GetInt(Config.Key.h264Crf)} -preset {preset} -pix_fmt {GetPixFmt()}";
|
||||
}
|
||||
|
||||
if (codec == Codec.H265)
|
||||
{
|
||||
string preset = Config.Get(Config.Key.ffEncPreset).ToLower().Remove(" ");
|
||||
int crf = Config.GetInt(Config.Key.h265Crf);
|
||||
args += $"{(crf > 0 ? $"-crf {crf}" : "-x265-params lossless=1")} -preset {preset} -pix_fmt {GetPixFmt()}";
|
||||
}
|
||||
|
||||
if (codec == Codec.H264NVENC)
|
||||
{
|
||||
int cq = (Config.GetInt(Config.Key.h264Crf) * 1.1f).RoundToInt();
|
||||
args += $"-b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")} -pix_fmt {GetPixFmt()}";
|
||||
}
|
||||
|
||||
if (codec == Codec.H265NVENC)
|
||||
{
|
||||
int cq = (Config.GetInt(Config.Key.h265Crf) * 1.1f).RoundToInt();
|
||||
args += $"-b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")} -pix_fmt {GetPixFmt()}";
|
||||
}
|
||||
|
||||
if (codec == Codec.AV1)
|
||||
{
|
||||
int cq = (Config.GetInt(Config.Key.av1Crf) * 1.0f).RoundToInt();
|
||||
args += $"-b:v 0 -qp {cq} -g 240 {GetSvtAv1Speed()} -tile_rows 2 -tile_columns 2 -pix_fmt {GetPixFmt()}";
|
||||
}
|
||||
|
||||
if (codec == Codec.VP9)
|
||||
{
|
||||
int crf = Config.GetInt(Config.Key.vp9Crf);
|
||||
string qualityStr = (crf > 0) ? $"-b:v 0 -crf {crf}" : "-lossless 1";
|
||||
args += $"{qualityStr} {GetVp9Speed()} -tile-columns 2 -tile-rows 2 -row-mt 1 -pix_fmt {GetPixFmt()}";
|
||||
}
|
||||
|
||||
if(codec == Codec.ProRes)
|
||||
{
|
||||
args += $"-profile:v {Config.GetInt(Config.Key.proResProfile)} -pix_fmt {GetPixFmt()}";
|
||||
}
|
||||
|
||||
if (codec == Codec.AviRaw)
|
||||
{
|
||||
args += $"-pix_fmt {Config.Get(Config.Key.aviColors)}";
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
static string GetVp9Speed ()
|
||||
{
|
||||
string preset = Config.Get(Config.Key.ffEncPreset).ToLower().Remove(" ");
|
||||
string arg = "";
|
||||
|
||||
if (preset == "veryslow") arg = "0";
|
||||
if (preset == "slower") arg = "1";
|
||||
if (preset == "slow") arg = "2";
|
||||
if (preset == "medium") arg = "3";
|
||||
if (preset == "fast") arg = "4";
|
||||
if (preset == "faster") arg = "5";
|
||||
if (preset == "veryfast") arg = "4 -deadline realtime";
|
||||
|
||||
return $"-cpu-used {arg}";
|
||||
}
|
||||
|
||||
static string GetSvtAv1Speed()
|
||||
{
|
||||
string preset = Config.Get(Config.Key.ffEncPreset).ToLower().Remove(" ");
|
||||
string arg = "8";
|
||||
|
||||
if (preset == "veryslow") arg = "2";
|
||||
if (preset == "slower") arg = "3";
|
||||
if (preset == "slow") arg = "4";
|
||||
if (preset == "medium") arg = "5";
|
||||
if (preset == "fast") arg = "6";
|
||||
if (preset == "faster") arg = "7";
|
||||
if (preset == "veryfast") arg = "8";
|
||||
|
||||
return $"-preset {arg}";
|
||||
}
|
||||
|
||||
static string GetPixFmt ()
|
||||
{
|
||||
switch (Config.GetInt(Config.Key.pixFmt))
|
||||
{
|
||||
case 0: return "yuv420p";
|
||||
case 1: return "yuv444p";
|
||||
case 2: return "yuv420p10le";
|
||||
case 3: return "yuv444p10le";
|
||||
}
|
||||
|
||||
return "yuv420p";
|
||||
}
|
||||
|
||||
public static bool ContainerSupportsAllAudioFormats (Interpolate.OutMode outMode, List<string> codecs)
|
||||
{
|
||||
if(codecs.Count < 1)
|
||||
Logger.Log($"Warning: ContainerSupportsAllAudioFormats() was called, but codec list has {codecs.Count} entries.", true, false, "ffmpeg");
|
||||
|
||||
foreach (string format in codecs)
|
||||
{
|
||||
if (!ContainerSupportsAudioFormat(outMode, format))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ContainerSupportsAudioFormat (Interpolate.OutMode outMode, string format)
|
||||
{
|
||||
bool supported = false;
|
||||
string alias = GetAudioExt(format);
|
||||
|
||||
string[] formatsMp4 = new string[] { "m4a", "ac3", "dts" };
|
||||
string[] formatsMkv = new string[] { "m4a", "ac3", "dts", "ogg", "mp2", "mp3", "wav", "wma" };
|
||||
string[] formatsWebm = new string[] { "ogg" };
|
||||
string[] formatsProres = new string[] { "m4a", "ac3", "dts", "wav" };
|
||||
string[] formatsAvi = new string[] { "m4a", "ac3", "dts" };
|
||||
|
||||
switch (outMode)
|
||||
{
|
||||
case Interpolate.OutMode.VidMp4: supported = formatsMp4.Contains(alias); break;
|
||||
case Interpolate.OutMode.VidMkv: supported = formatsMkv.Contains(alias); break;
|
||||
case Interpolate.OutMode.VidWebm: supported = formatsWebm.Contains(alias); break;
|
||||
case Interpolate.OutMode.VidProRes: supported = formatsProres.Contains(alias); break;
|
||||
case Interpolate.OutMode.VidAvi: supported = formatsAvi.Contains(alias); break;
|
||||
}
|
||||
|
||||
Logger.Log($"Checking if {outMode} supports audio format '{format}' ({alias}): {supported}", true, false, "ffmpeg");
|
||||
return supported;
|
||||
}
|
||||
|
||||
public static string GetExt(Interpolate.OutMode outMode, bool dot = true)
|
||||
{
|
||||
string ext = dot ? "." : "";
|
||||
|
||||
switch (outMode)
|
||||
{
|
||||
case Interpolate.OutMode.VidMp4: ext += "mp4"; break;
|
||||
case Interpolate.OutMode.VidMkv: ext += "mkv"; break;
|
||||
case Interpolate.OutMode.VidWebm: ext += "webm"; break;
|
||||
case Interpolate.OutMode.VidProRes: ext += "mov"; break;
|
||||
case Interpolate.OutMode.VidAvi: ext += "avi"; break;
|
||||
case Interpolate.OutMode.VidGif: ext += "gif"; break;
|
||||
}
|
||||
|
||||
return ext;
|
||||
}
|
||||
|
||||
public static string GetAudioExt(string videoFile, int streamIndex = -1)
|
||||
{
|
||||
return GetAudioExt(FfmpegCommands.GetAudioCodec(videoFile, streamIndex));
|
||||
}
|
||||
|
||||
public static string GetAudioExt (string codec)
|
||||
{
|
||||
if (codec.StartsWith("pcm_"))
|
||||
return "wav";
|
||||
|
||||
switch (codec)
|
||||
{
|
||||
case "vorbis": return "ogg";
|
||||
case "opus": return "ogg";
|
||||
case "mp2": return "mp2";
|
||||
case "mp3": return "mp3";
|
||||
case "aac": return "m4a";
|
||||
case "ac3": return "ac3";
|
||||
case "eac3": return "ac3";
|
||||
case "dts": return "dts";
|
||||
case "alac": return "wav";
|
||||
case "flac": return "wav";
|
||||
case "wmav1": return "wma";
|
||||
case "wmav2": return "wma";
|
||||
}
|
||||
|
||||
return "unsupported";
|
||||
}
|
||||
|
||||
public static string GetAudioFallbackArgs (Interpolate.OutMode outMode)
|
||||
{
|
||||
string codec = "aac";
|
||||
string bitrate = $"{Config.GetInt(Config.Key.aacBitrate, 160)}";
|
||||
|
||||
if(outMode == Interpolate.OutMode.VidMkv || outMode == Interpolate.OutMode.VidWebm)
|
||||
{
|
||||
codec = "libopus";
|
||||
bitrate = $"{Config.GetInt(Config.Key.opusBitrate, 128)}";
|
||||
}
|
||||
|
||||
return $"-c:a {codec} -b:a {bitrate}k -ac 2";
|
||||
}
|
||||
|
||||
public static string GetAudioExtForContainer(string containerExt)
|
||||
{
|
||||
containerExt = containerExt.Remove(".");
|
||||
string ext = "m4a";
|
||||
|
||||
if (containerExt == "webm" || containerExt == "mkv")
|
||||
ext = "ogg";
|
||||
|
||||
return ext;
|
||||
}
|
||||
|
||||
public static string GetSubCodecForContainer(string containerExt)
|
||||
{
|
||||
containerExt = containerExt.Remove(".");
|
||||
|
||||
if (containerExt == "mp4") return "mov_text";
|
||||
if (containerExt == "webm") return "webvtt";
|
||||
|
||||
return "copy"; // Default: Copy SRT subs
|
||||
}
|
||||
|
||||
public static bool ContainerSupportsSubs(string containerExt, bool showWarningIfNotSupported = true)
|
||||
{
|
||||
containerExt = containerExt.Remove(".");
|
||||
bool supported = (containerExt == "mp4" || containerExt == "mkv" || containerExt == "webm" || containerExt == "mov");
|
||||
Logger.Log($"Subtitles {(supported ? "are supported" : "not supported")} by {containerExt.ToUpper()}", true);
|
||||
|
||||
if (showWarningIfNotSupported && Config.GetBool(Config.Key.keepSubs) && !supported)
|
||||
Logger.Log($"Warning: Subtitle transfer is enabled, but {containerExt.ToUpper()} does not support subtitles properly. MKV is recommended instead.");
|
||||
|
||||
return supported;
|
||||
}
|
||||
|
||||
public static void CreateConcatFile (string inputFilesDir, string outputPath, string[] validExtensions = null)
|
||||
{
|
||||
string concatFileContent = "";
|
||||
string[] files = IOUtils.GetFilesSorted(inputFilesDir);
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
if (validExtensions != null && !validExtensions.Contains(Path.GetExtension(file).ToLower()))
|
||||
continue;
|
||||
|
||||
concatFileContent += $"file '{file.Replace(@"\", "/")}'\n";
|
||||
}
|
||||
|
||||
File.WriteAllText(outputPath, concatFileContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
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 FfmpegAlpha : FfmpegCommands
|
||||
{
|
||||
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,{GetPadFilter()}\" {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 outPath = Path.Combine(outputDir, "_" + file.Name);
|
||||
Size s = IOUtils.GetImage(file.FullName).Size;
|
||||
string args = $" -f lavfi -i color={fillColor}:s={s.Width}x{s.Height} -i {file.FullName.Wrap()} -filter_complex overlay=0:0:shortest=1 {outPath.Wrap()}";
|
||||
await RunFfmpeg(args, LogMode.Hidden);
|
||||
file.Delete();
|
||||
File.Move(outPath, 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)
|
||||
await IOUtils.TryDeleteIfExistsAsync(alphaDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
using Flowframes.IO;
|
||||
using Flowframes.UI;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using static Flowframes.AvProcess;
|
||||
using Utils = Flowframes.Media.FFmpegUtils;
|
||||
using I = Flowframes.Interpolate;
|
||||
|
||||
namespace Flowframes.Media
|
||||
{
|
||||
partial class FfmpegAudioAndMetadata : FfmpegCommands
|
||||
{
|
||||
#region Mux From Input
|
||||
|
||||
public static async Task MergeStreamsFromInput (string inputVideo, string interpVideo, string tempFolder, bool shortest)
|
||||
{
|
||||
if (!File.Exists(inputVideo) && !I.current.inputIsFrames)
|
||||
{
|
||||
Logger.Log("Warning: Input video file not found, can't copy audio/subtitle streams to output video!");
|
||||
return;
|
||||
}
|
||||
|
||||
string containerExt = Path.GetExtension(interpVideo);
|
||||
string tempPath = Path.Combine(tempFolder, $"vid{containerExt}");
|
||||
string outPath = Path.Combine(tempFolder, $"muxed{containerExt}");
|
||||
IOUtils.TryDeleteIfExists(tempPath);
|
||||
File.Move(interpVideo, tempPath);
|
||||
string inName = Path.GetFileName(tempPath);
|
||||
string outName = Path.GetFileName(outPath);
|
||||
|
||||
string subArgs = "-c:s " + Utils.GetSubCodecForContainer(containerExt);
|
||||
|
||||
bool audioCompat = Utils.ContainerSupportsAllAudioFormats(I.current.outMode, GetAudioCodecs(inputVideo));
|
||||
string audioArgs = audioCompat ? "" : Utils.GetAudioFallbackArgs(I.current.outMode);
|
||||
|
||||
if (!audioCompat)
|
||||
Logger.Log("Warning: Input audio format(s) not fully supported in output container - Will re-encode.", true, false, "ffmpeg");
|
||||
|
||||
bool audio = Config.GetBool(Config.Key.keepAudio);
|
||||
bool subs = Config.GetBool(Config.Key.keepSubs);
|
||||
bool meta = Config.GetBool(Config.Key.keepMeta);
|
||||
|
||||
if (!audio)
|
||||
audioArgs = "-an";
|
||||
|
||||
if (!subs || (subs && !Utils.ContainerSupportsSubs(containerExt)))
|
||||
subArgs = "-sn";
|
||||
|
||||
bool isMkv = I.current.outMode == I.OutMode.VidMkv;
|
||||
string mkvFix = isMkv ? "-max_interleave_delta 0" : ""; // https://reddit.com/r/ffmpeg/comments/efddfs/starting_new_cluster_due_to_timestamp/
|
||||
string metaArg = (isMkv && meta) ? "-map 1:t?" : ""; // https://reddit.com/r/ffmpeg/comments/fw4jnh/how_to_make_ffmpeg_keep_attached_images_in_mkv_as/
|
||||
string shortestArg = shortest ? "-shortest" : "";
|
||||
|
||||
if (QuickSettingsTab.trimEnabled)
|
||||
{
|
||||
string otherStreamsName = $"otherStreams{containerExt}";
|
||||
|
||||
string[] trim = FfmpegExtract.GetTrimArgs();
|
||||
string args1 = $"{trim[0]} -i {inputVideo.Wrap()} {trim[1]} -map 0 -map -0:v -map -0:d -c copy {audioArgs} {subArgs} {otherStreamsName}"; // Extract trimmed
|
||||
await RunFfmpeg(args1, tempFolder, LogMode.Hidden);
|
||||
|
||||
string args2 = $"-i {inName} -i {otherStreamsName} -map 0:v:0 -map 1:a:? -map 1:s:? {metaArg} -c copy {audioArgs} {subArgs} {mkvFix} {shortestArg} {outName}"; // Merge interp + trimmed original
|
||||
await RunFfmpeg(args2, tempFolder, LogMode.Hidden);
|
||||
|
||||
IOUtils.TryDeleteIfExists(Path.Combine(tempFolder, otherStreamsName));
|
||||
}
|
||||
else // If trimming is disabled we can pull the streams directly from the input file
|
||||
{
|
||||
string args = $"-i {inName} -i {inputVideo.Wrap()} -map 0:v:0 -map 1:a:? -map 1:s:? {metaArg} -c copy {audioArgs} {subArgs} {mkvFix} {shortestArg} {outName}";
|
||||
await RunFfmpeg(args, tempFolder, LogMode.Hidden);
|
||||
}
|
||||
|
||||
if (File.Exists(outPath) && IOUtils.GetFilesize(outPath) > 512)
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
File.Move(outPath, interpVideo);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Move(tempPath, interpVideo); // Muxing failed, move unmuxed video file back
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,282 +0,0 @@
|
||||
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 padFilter = "pad=width=ceil(iw/2)*2:height=ceil(ih/2)*2:color=black@0";
|
||||
public static string hdrFilter = @"-vf zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p";
|
||||
public static string pngCompr = "-compression_level 3";
|
||||
public static string mpDecDef = "\"mpdecimate\"";
|
||||
public static string mpDecAggr = "\"mpdecimate=hi=64*32:lo=64*32:frac=0.1\"";
|
||||
|
||||
public static int GetPadding ()
|
||||
{
|
||||
return (Interpolate.current.ai.aiName == Implementations.flavrCuda.aiName) ? 8 : 2; // FLAVR input needs to be divisible by 8
|
||||
}
|
||||
|
||||
public static string GetPadFilter ()
|
||||
{
|
||||
int padPixels = GetPadding();
|
||||
return $"pad=width=ceil(iw/{padPixels})*{padPixels}:height=ceil(ih/{padPixels})*{padPixels}:color=black@0";
|
||||
}
|
||||
|
||||
public static async Task ConcatVideos(string concatFile, string outPath, int looptimes = -1, bool showLog = true)
|
||||
{
|
||||
Logger.Log($"ConcatVideos('{Path.GetFileName(concatFile)}', '{outPath}', {looptimes})", true, false, "ffmpeg");
|
||||
|
||||
if(showLog)
|
||||
Logger.Log($"Merging videos...", false, Logger.GetLastLine().Contains("frame"));
|
||||
|
||||
IOUtils.RenameExistingFile(outPath);
|
||||
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 -fflags +genpts {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 loopSuffix = Config.Get(Config.Key.exportNamePatternLoop).Replace("[LOOPS]", $"{times}").Replace("[PLAYS]", $"{times + 1}");
|
||||
string outpath = $"{pathNoExt}{loopSuffix}{ext}";
|
||||
IOUtils.RenameExistingFile(outpath);
|
||||
string args = $" -stream_loop {times} -i {inputFile.Wrap()} -c copy {outpath.Wrap()}";
|
||||
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 output = GetFfprobeOutput(args);
|
||||
return FormatUtils.TimestampToMs(output);
|
||||
}
|
||||
|
||||
public static async Task<Fraction> GetFramerate(string inputFile, bool preferFfmpeg = false)
|
||||
{
|
||||
Logger.Log($"GetFramerate(inputFile = '{inputFile}', preferFfmpeg = {preferFfmpeg})", true, false, "ffmpeg");
|
||||
Fraction ffprobeFps = new Fraction(0, 1);
|
||||
Fraction ffmpegFps = new Fraction(0, 1);
|
||||
|
||||
try
|
||||
{
|
||||
string ffprobeOutput = await GetVideoInfoCached.GetFfprobeInfoAsync(inputFile, "r_frame_rate");
|
||||
string fpsStr = ffprobeOutput.SplitIntoLines().First();
|
||||
string[] numbers = fpsStr.Split('=')[1].Split('/');
|
||||
Logger.Log($"Fractional FPS from ffprobe: {numbers[0]}/{numbers[1]} = {((float)numbers[0].GetInt() / numbers[1].GetInt())}", true, false, "ffmpeg");
|
||||
ffprobeFps = new Fraction(numbers[0].GetInt(), numbers[1].GetInt());
|
||||
}
|
||||
catch (Exception ffprobeEx)
|
||||
{
|
||||
Logger.Log("GetFramerate ffprobe Error: " + ffprobeEx.Message, true, false);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string ffmpegOutput = await GetVideoInfoCached.GetFfmpegInfoAsync(inputFile);
|
||||
string[] entries = ffmpegOutput.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(",", ".");
|
||||
Logger.Log($"Float FPS from ffmpeg: {num.GetFloat()}", true, false, "ffmpeg");
|
||||
ffmpegFps = new Fraction(num.GetFloat());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception ffmpegEx)
|
||||
{
|
||||
Logger.Log("GetFramerate ffmpeg Error: " + ffmpegEx.Message, true, false);
|
||||
}
|
||||
|
||||
if (preferFfmpeg)
|
||||
{
|
||||
if (ffmpegFps.GetFloat() > 0)
|
||||
return ffmpegFps;
|
||||
else
|
||||
return ffprobeFps;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ffprobeFps.GetFloat() > 0)
|
||||
return ffprobeFps;
|
||||
else
|
||||
return ffmpegFps;
|
||||
}
|
||||
}
|
||||
|
||||
public static Size GetSize(string inputFile)
|
||||
{
|
||||
Logger.Log($"GetSize('{inputFile}')", true, false, "ffmpeg");
|
||||
string args = $" -v panic -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 {inputFile.Wrap()}";
|
||||
string[] outputLines = GetFfprobeOutput(args).SplitIntoLines();
|
||||
|
||||
foreach(string line in outputLines)
|
||||
{
|
||||
if (!line.Contains("x") || line.Trim().Length < 3)
|
||||
continue;
|
||||
|
||||
string[] numbers = line.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 = await ReadFrameCountFfprobeAsync(inputFile, Config.GetBool(Config.Key.ffprobeFrameCount)); // Try reading frame count with ffprobe
|
||||
if (frames > 0) return frames;
|
||||
|
||||
Logger.Log($"Failed to get frame count using ffprobe (frames = {frames}). Trying to read with 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 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 -threads 0 -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 -threads 0 -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 -stats -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 async Task<VidExtraData> GetVidExtraInfo(string inputFile)
|
||||
{
|
||||
string ffprobeOutput = await GetVideoInfoCached.GetFfprobeInfoAsync(inputFile);
|
||||
VidExtraData data = new VidExtraData(ffprobeOutput);
|
||||
return data;
|
||||
}
|
||||
|
||||
public static async Task<bool> IsEncoderCompatible(string enc)
|
||||
{
|
||||
Logger.Log($"IsEncoderCompatible('{enc}')", true, false, "ffmpeg");
|
||||
string args = $"-loglevel error -f lavfi -i color=black:s=540x540 -vframes 1 -an -c:v {enc} -f null -";
|
||||
string output = await GetFfmpegOutputAsync(args);
|
||||
return !output.ToLower().Contains("error");
|
||||
}
|
||||
|
||||
public static string GetAudioCodec(string path, int streamIndex = -1)
|
||||
{
|
||||
Logger.Log($"GetAudioCodec('{Path.GetFileName(path)}', {streamIndex})", true, false, "ffmpeg");
|
||||
string stream = (streamIndex < 0) ? "a" : $"{streamIndex}";
|
||||
string args = $"-v panic -show_streams -select_streams {stream} -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 List<string> GetAudioCodecs(string path, int streamIndex = -1)
|
||||
{
|
||||
Logger.Log($"GetAudioCodecs('{Path.GetFileName(path)}', {streamIndex})", true, false, "ffmpeg");
|
||||
List<string> codecNames = new List<string>();
|
||||
string args = $"-loglevel panic -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="))
|
||||
codecNames.Add(entry.Remove("codec_name=").Trim());
|
||||
}
|
||||
|
||||
return codecNames;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.MiscUtils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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 FramesToVideo(string framesFile, string outPath, Interpolate.OutMode outMode, Fraction fps, Fraction resampleFps, VidExtraData extraData, LogMode logMode = LogMode.OnlyLastLine, bool isChunk = false)
|
||||
{
|
||||
if (logMode != LogMode.Hidden)
|
||||
Logger.Log((resampleFps.GetFloat() <= 0) ? "Encoding video..." : $"Encoding video resampled to {resampleFps.GetString()} FPS...");
|
||||
|
||||
IOUtils.RenameExistingFile(outPath);
|
||||
Directory.CreateDirectory(outPath.GetParentDir());
|
||||
string encArgs = Utils.GetEncArgs(Utils.GetCodec(outMode));
|
||||
if (!isChunk && outMode == Interpolate.OutMode.VidMp4) encArgs += $" -movflags +faststart";
|
||||
string inArg = $"-f concat -i {Path.GetFileName(framesFile)}";
|
||||
string linksDir = Path.Combine(framesFile + Paths.symlinksSuffix);
|
||||
|
||||
if (Config.GetBool(Config.Key.allowSymlinkEncoding, true) && Symlinks.SymlinksAllowed())
|
||||
{
|
||||
if (await Symlinks.MakeSymlinksForEncode(framesFile, linksDir, Padding.interpFrames))
|
||||
inArg = $"-i \"{linksDir}/%{Padding.interpFrames}d{GetConcatFileExt(framesFile)}\"";
|
||||
}
|
||||
|
||||
string extraArgs = Config.Get(Config.Key.ffEncArgs);
|
||||
string rate = fps.ToString().Replace(",", ".");
|
||||
|
||||
List<string> filters = new List<string>();
|
||||
|
||||
if (resampleFps.GetFloat() >= 0.1f)
|
||||
filters.Add($"fps=fps={resampleFps}");
|
||||
|
||||
if (Config.GetBool(Config.Key.keepColorSpace) && extraData.HasAllValues())
|
||||
{
|
||||
Logger.Log($"Applying color transfer ({extraData.colorSpace}).", true, false, "ffmpeg");
|
||||
filters.Add($"scale=out_color_matrix={extraData.colorSpace}");
|
||||
extraArgs += $" -colorspace {extraData.colorSpace} -color_primaries {extraData.colorPrimaries} -color_trc {extraData.colorTransfer} -color_range:v \"{extraData.colorRange}\"";
|
||||
}
|
||||
|
||||
string vf = filters.Count > 0 ? $"-vf {string.Join(",", filters)}" : "";
|
||||
string args = $"-vsync 0 -r {rate} {inArg} {encArgs} {vf} {GetAspectArg(extraData)} {extraArgs} -threads {Config.GetInt(Config.Key.ffEncThreads)} {outPath.Wrap()}";
|
||||
await RunFfmpeg(args, framesFile.GetParentDir(), logMode, "error", TaskType.Encode, !isChunk);
|
||||
IOUtils.TryDeleteIfExists(linksDir);
|
||||
}
|
||||
|
||||
public static string GetConcatFileExt (string concatFilePath)
|
||||
{
|
||||
return Path.GetExtension(File.ReadAllLines(concatFilePath).FirstOrDefault().Split('\'')[1]);
|
||||
}
|
||||
|
||||
static string GetAspectArg (VidExtraData extraData)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(extraData.displayRatio) && !extraData.displayRatio.MatchesWildcard("*N/A*"))
|
||||
return $"-aspect {extraData.displayRatio}";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
public static async Task FramesToFrames(string framesFile, string outDir, Fraction fps, Fraction resampleFps, string format = "png", LogMode logMode = LogMode.OnlyLastLine)
|
||||
{
|
||||
Directory.CreateDirectory(outDir);
|
||||
string inArg = $"-f concat -i {Path.GetFileName(framesFile)}";
|
||||
string linksDir = Path.Combine(framesFile + Paths.symlinksSuffix);
|
||||
|
||||
if (Config.GetBool(Config.Key.allowSymlinkEncoding, true) && Symlinks.SymlinksAllowed())
|
||||
{
|
||||
if (await Symlinks.MakeSymlinksForEncode(framesFile, linksDir, Padding.interpFrames))
|
||||
inArg = $"-i {Path.GetFileName(framesFile) + Paths.symlinksSuffix}/%{Padding.interpFrames}d{GetConcatFileExt(framesFile)}";
|
||||
}
|
||||
|
||||
string rate = fps.ToString().Replace(",", ".");
|
||||
string vf = (resampleFps.GetFloat() < 0.1f) ? "" : $"-vf fps=fps={resampleFps}";
|
||||
string compression = format == "png" ? pngCompr : "-q:v 1";
|
||||
string args = $"-vsync 0 -r {rate} {inArg} {compression} {vf} \"{outDir}/%{Padding.interpFrames}d.{format}\"";
|
||||
await RunFfmpeg(args, framesFile.GetParentDir(), logMode, "error", TaskType.Encode, true);
|
||||
IOUtils.TryDeleteIfExists(linksDir);
|
||||
}
|
||||
|
||||
public static async Task FramesToGifConcat(string framesFile, string outPath, Fraction rate, bool palette, int colors, Fraction resampleFps, LogMode logMode = LogMode.OnlyLastLine)
|
||||
{
|
||||
if (rate.GetFloat() > 50f && (resampleFps.GetFloat() > 50f || resampleFps.GetFloat() < 1))
|
||||
resampleFps = new Fraction(50, 1); // Force limit framerate as encoding above 50 will cause problems
|
||||
|
||||
if (logMode != LogMode.Hidden)
|
||||
Logger.Log((resampleFps.GetFloat() <= 0) ? $"Encoding GIF..." : $"Encoding GIF resampled to {resampleFps.GetFloat().ToString().Replace(",", ".")} FPS...");
|
||||
|
||||
string framesFilename = Path.GetFileName(framesFile);
|
||||
string dither = Config.Get(Config.Key.gifDitherType).Split(' ').First();
|
||||
string paletteFilter = palette ? $"-vf \"split[s0][s1];[s0]palettegen={colors}[p];[s1][p]paletteuse=dither={dither}\"" : "";
|
||||
string fpsFilter = (resampleFps.GetFloat() <= 0) ? "" : $"fps=fps={resampleFps}";
|
||||
string vf = FormatUtils.ConcatStrings(new string[] { paletteFilter, fpsFilter });
|
||||
string extraArgs = Config.Get(Config.Key.ffEncArgs);
|
||||
string args = $"-f concat -r {rate} -i {framesFilename.Wrap()} -gifflags -offsetting {vf} {extraArgs} {outPath.Wrap()}";
|
||||
await RunFfmpeg(args, framesFile.GetParentDir(), LogMode.OnlyLastLine, "error", TaskType.Encode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,312 +0,0 @@
|
||||
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.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 inPath, string outDir, Fraction rate, bool inputIsFrames, string format)
|
||||
{
|
||||
Logger.Log("Extracting scene changes...");
|
||||
Directory.CreateDirectory(outDir);
|
||||
|
||||
string inArg = $"-i {inPath.Wrap()}";
|
||||
|
||||
if (inputIsFrames)
|
||||
{
|
||||
string concatFile = Path.Combine(Paths.GetDataPath(), "png-scndetect-concat-temp.ini");
|
||||
FFmpegUtils.CreateConcatFile(inPath, concatFile, Filetypes.imagesInterpCompat);
|
||||
inArg = $"-f concat -safe 0 -i {concatFile.Wrap()}";
|
||||
}
|
||||
|
||||
string scnDetect = $"-vf \"select='gt(scene,{Config.GetFloatString(Config.Key.scnDetectValue)})'\"";
|
||||
string rateArg = (rate.GetFloat() > 0) ? $"-r {rate}" : "";
|
||||
string args = $"-vsync 0 {GetTrimArg(true)} {inArg} {GetImgArgs(format)} {rateArg} {scnDetect} -frame_pts 1 -s 256x144 {GetTrimArg(false)} \"{outDir}/%{Padding.inputFrames}d{format}\"";
|
||||
|
||||
LogMode logMode = await Interpolate.GetCurrentInputFrameCount() > 50 ? LogMode.OnlyLastLine : LogMode.Hidden;
|
||||
await RunFfmpeg(args, logMode, inputIsFrames ? "panic" : "warning", TaskType.ExtractFrames, true);
|
||||
|
||||
bool hiddenLog = await Interpolate.GetCurrentInputFrameCount() <= 50;
|
||||
int amount = IOUtils.GetAmountOfFiles(outDir, false);
|
||||
Logger.Log($"Detected {amount} scene {(amount == 1 ? "change" : "changes")}.".Replace(" 0 ", " no "), false, !hiddenLog);
|
||||
}
|
||||
|
||||
static string GetImgArgs(string extension, bool includePixFmt = true, bool alpha = false)
|
||||
{
|
||||
extension = extension.ToLower().Remove(".").Replace("jpeg", "jpg");
|
||||
string pixFmt = "-pix_fmt rgb24";
|
||||
string args = "";
|
||||
|
||||
if (extension.Contains("png"))
|
||||
{
|
||||
pixFmt = alpha ? "rgba" : "rgb24";
|
||||
args = $"{pngCompr}";
|
||||
}
|
||||
|
||||
if (extension.Contains("jpg"))
|
||||
{
|
||||
pixFmt = "yuv420p";
|
||||
args = $"-q:v 1";
|
||||
}
|
||||
|
||||
if (extension.Contains("webp"))
|
||||
{
|
||||
pixFmt = "yuv420p";
|
||||
args = $"-q:v 100";
|
||||
}
|
||||
|
||||
if (includePixFmt)
|
||||
args += $" -pix_fmt {pixFmt}";
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
public static async Task VideoToFrames(string inputFile, string framesDir, bool alpha, Fraction rate, bool deDupe, bool delSrc, Size size, string format)
|
||||
{
|
||||
Logger.Log("Extracting video frames from input video...");
|
||||
Logger.Log($"VideoToFrames() - Alpha: {alpha} - Rate: {rate} - Size: {size} - Format: {format}", true, false, "ffmpeg");
|
||||
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
|
||||
IOUtils.CreateDir(framesDir);
|
||||
string mpStr = deDupe ? ((Config.GetInt(Config.Key.mpdecimateMode) == 0) ? mpDecDef : mpDecAggr) : "";
|
||||
string filters = FormatUtils.ConcatStrings(new[] { GetPadFilter(), mpStr });
|
||||
string vf = filters.Length > 2 ? $"-vf {filters}" : "";
|
||||
string rateArg = (rate.GetFloat() > 0) ? $" -r {rate}" : "";
|
||||
string args = $"{GetTrimArg(true)} -i {inputFile.Wrap()} {GetImgArgs(format, true, alpha)} -vsync 0 {rateArg} -frame_pts 1 {vf} {sizeStr} {GetTrimArg(false)} \"{framesDir}/%{Padding.inputFrames}d{format}\"";
|
||||
LogMode logMode = await Interpolate.GetCurrentInputFrameCount() > 50 ? LogMode.OnlyLastLine : LogMode.Hidden;
|
||||
await RunFfmpeg(args, logMode, TaskType.ExtractFrames, true);
|
||||
int amount = IOUtils.GetAmountOfFiles(framesDir, false, "*" + format);
|
||||
Logger.Log($"Extracted {amount} {(amount == 1 ? "frame" : "frames")} from input.", false, true);
|
||||
await Task.Delay(1);
|
||||
if (delSrc)
|
||||
DeleteSource(inputFile);
|
||||
}
|
||||
|
||||
public static async Task ImportImagesCheckCompat(string inPath, string outPath, bool alpha, Size size, bool showLog, string format)
|
||||
{
|
||||
bool compatible = await Task.Run(async () => { return AreImagesCompatible(inPath, Config.GetInt(Config.Key.maxVidHeight)); });
|
||||
|
||||
if (!alpha && compatible)
|
||||
{
|
||||
await CopyImages(inPath, outPath, showLog);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
await ImportImages(inPath, outPath, alpha, size, showLog, format);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task CopyImages (string inpath, string outpath, bool showLog)
|
||||
{
|
||||
if (showLog) Logger.Log($"Copying images from {new DirectoryInfo(inpath).Name}...");
|
||||
Directory.CreateDirectory(outpath);
|
||||
|
||||
Dictionary<string, string> moveFromTo = new Dictionary<string, string>();
|
||||
int counter = 0;
|
||||
|
||||
foreach (FileInfo file in IOUtils.GetFileInfosSorted(inpath))
|
||||
{
|
||||
string newFilename = counter.ToString().PadLeft(Padding.inputFrames, '0') + file.Extension;
|
||||
moveFromTo.Add(file.FullName, Path.Combine(outpath, newFilename));
|
||||
counter++;
|
||||
}
|
||||
|
||||
if (Config.GetBool(Config.Key.allowSymlinkEncoding) && Config.GetBool(Config.Key.allowSymlinkImport, true))
|
||||
{
|
||||
Dictionary<string, string> moveFromToSwapped = moveFromTo.ToDictionary(x => x.Value, x => x.Key); // From/To => To/From (Link/Target)
|
||||
await Symlinks.CreateSymlinksParallel(moveFromToSwapped);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Run(async () => {
|
||||
foreach (KeyValuePair<string, string> moveFromToPair in moveFromTo)
|
||||
File.Copy(moveFromToPair.Key, moveFromToPair.Value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static bool AreImagesCompatible (string inpath, int maxHeight)
|
||||
{
|
||||
NmkdStopwatch sw = new NmkdStopwatch();
|
||||
string[] validExtensions = Filetypes.imagesInterpCompat; // = new string[] { ".jpg", ".jpeg", ".png" };
|
||||
FileInfo[] files = IOUtils.GetFileInfosSorted(inpath);
|
||||
|
||||
if (files.Length < 1)
|
||||
{
|
||||
Logger.Log("[AreImagesCompatible] Sequence not compatible: No files found.", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool allSameExtension = files.All(x => x.Extension == files.First().Extension);
|
||||
|
||||
if (!allSameExtension)
|
||||
{
|
||||
Logger.Log($"Sequence not compatible: Not all files have the same extension.", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool allValidExtension = files.All(x => validExtensions.Contains(x.Extension));
|
||||
|
||||
if (!allValidExtension)
|
||||
{
|
||||
Logger.Log($"Sequence not compatible: Not all files have a valid extension ({string.Join(", ", validExtensions)}).", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
Image[] randomSamples = files.OrderBy(arg => Guid.NewGuid()).Take(10).Select(x => IOUtils.GetImage(x.FullName)).ToArray();
|
||||
|
||||
bool allSameSize = randomSamples.All(i => i.Size == randomSamples.First().Size);
|
||||
|
||||
if (!allSameSize)
|
||||
{
|
||||
Logger.Log($"Sequence not compatible: Not all images have the same dimensions [{sw.GetElapsedStr()}].", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
int div = GetPadding();
|
||||
bool allDivBy2 = randomSamples.All(i => (i.Width % div == 0) && (i.Height % div == 0));
|
||||
|
||||
if (!allDivBy2)
|
||||
{
|
||||
Logger.Log($"Sequence not compatible: Not all image dimensions are divisible by {div} [{sw.GetElapsedStr()}].", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool allSmallEnough = randomSamples.All(i => (i.Height <= maxHeight));
|
||||
|
||||
if (!allSmallEnough)
|
||||
{
|
||||
Logger.Log($"Sequence not compatible: Image dimensions above max size [{sw.GetElapsedStr()}].", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool all24Bit = randomSamples.All(i => (i.PixelFormat == System.Drawing.Imaging.PixelFormat.Format24bppRgb));
|
||||
|
||||
if (!all24Bit)
|
||||
{
|
||||
Logger.Log($"Sequence not compatible: Some images are not 24-bit (8bpp) [{sw.GetElapsedStr()}].", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
Interpolate.current.framesExt = files.First().Extension;
|
||||
Logger.Log($"Sequence compatible! [{sw.GetElapsedStr()}]", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static async Task ImportImages(string inPath, string outPath, bool alpha, Size size, bool showLog, string format)
|
||||
{
|
||||
if (showLog) Logger.Log($"Importing images from {new DirectoryInfo(inPath).Name}...");
|
||||
Logger.Log($"ImportImages() - Alpha: {alpha} - Size: {size} - Format: {format}", true, false, "ffmpeg");
|
||||
IOUtils.CreateDir(outPath);
|
||||
string concatFile = Path.Combine(Paths.GetDataPath(), "import-concat-temp.ini");
|
||||
FFmpegUtils.CreateConcatFile(inPath, concatFile, Filetypes.imagesInterpCompat);
|
||||
|
||||
string inArg = $"-f concat -safe 0 -i {concatFile.Wrap()}";
|
||||
string linksDir = Path.Combine(concatFile + Paths.symlinksSuffix);
|
||||
|
||||
if (Config.GetBool(Config.Key.allowSymlinkEncoding, true) && Symlinks.SymlinksAllowed())
|
||||
{
|
||||
if (await Symlinks.MakeSymlinksForEncode(concatFile, linksDir, Padding.interpFrames))
|
||||
inArg = $"-i \"{linksDir}/%{Padding.interpFrames}d{FfmpegEncode.GetConcatFileExt(concatFile)}\"";
|
||||
}
|
||||
|
||||
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
|
||||
string vf = $"-vf {GetPadFilter()}";
|
||||
string args = $"-r 25 {inArg} {GetImgArgs(format, true, alpha)} {sizeStr} -vsync 0 -start_number 0 {vf} \"{outPath}/%{Padding.inputFrames}d{format}\"";
|
||||
LogMode logMode = IOUtils.GetAmountOfFiles(inPath, false) > 50 ? LogMode.OnlyLastLine : LogMode.Hidden;
|
||||
await RunFfmpeg(args, logMode, "panic", TaskType.ExtractFrames);
|
||||
}
|
||||
|
||||
public static string[] GetTrimArgs()
|
||||
{
|
||||
return new string[] { GetTrimArg(true), GetTrimArg(false) };
|
||||
}
|
||||
|
||||
public static string GetTrimArg(bool input)
|
||||
{
|
||||
if (!QuickSettingsTab.trimEnabled)
|
||||
return "";
|
||||
|
||||
int fastSeekThresh = 180;
|
||||
bool fastSeek = QuickSettingsTab.trimStartSecs > fastSeekThresh;
|
||||
string arg = "";
|
||||
|
||||
if (input)
|
||||
{
|
||||
if (fastSeek)
|
||||
arg += $"-ss {QuickSettingsTab.trimStartSecs - fastSeekThresh}";
|
||||
else
|
||||
return arg;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fastSeek)
|
||||
{
|
||||
arg += $"-ss {fastSeekThresh}";
|
||||
|
||||
long trimTimeSecs = QuickSettingsTab.trimEndSecs - QuickSettingsTab.trimStartSecs;
|
||||
|
||||
if (QuickSettingsTab.doTrimEnd)
|
||||
arg += $" -to {fastSeekThresh + trimTimeSecs}";
|
||||
}
|
||||
else
|
||||
{
|
||||
arg += $"-ss {QuickSettingsTab.trimStart}";
|
||||
|
||||
if (QuickSettingsTab.doTrimEnd)
|
||||
arg += $" -to {QuickSettingsTab.trimEnd}";
|
||||
}
|
||||
}
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
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 ? pngCompr : "";
|
||||
string pixFmt = "-pix_fmt " + (isPng ? $"rgb24 {comprArg}" : "yuvj420p");
|
||||
string args = $"-i {inputFile.Wrap()} {comprArg} {sizeStr} {pixFmt} -vf {GetPadFilter()} {outPath.Wrap()}";
|
||||
await 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 ? pngCompr : "";
|
||||
string pixFmt = "-pix_fmt " + (isPng ? $"rgb24 {comprArg}" : "yuvj420p");
|
||||
string args = $"-i {inputFile.Wrap()} -vf \"select=eq(n\\,{frameNum})\" -vframes 1 {pixFmt} {outputPath.Wrap()}";
|
||||
await RunFfmpeg(args, LogMode.Hidden, TaskType.ExtractFrames);
|
||||
}
|
||||
|
||||
public static async Task ExtractLastFrame(string inputFile, string outputPath, Size size)
|
||||
{
|
||||
if (QuickSettingsTab.trimEnabled)
|
||||
return;
|
||||
|
||||
if (IOUtils.IsPathDirectory(outputPath))
|
||||
outputPath = Path.Combine(outputPath, "last.png");
|
||||
|
||||
bool isPng = (Path.GetExtension(outputPath).ToLower() == ".png");
|
||||
string comprArg = isPng ? pngCompr : "";
|
||||
string pixFmt = "-pix_fmt " + (isPng ? $"rgb24 {comprArg}" : "yuvj420p");
|
||||
string sizeStr = (size.Width > 1 && size.Height > 1) ? $"-s {size.Width}x{size.Height}" : "";
|
||||
string trim = QuickSettingsTab.trimEnabled ? $"-ss {QuickSettingsTab.GetTrimEndMinusOne()} -to {QuickSettingsTab.trimEnd}" : "";
|
||||
string sseof = string.IsNullOrWhiteSpace(trim) ? "-sseof -1" : "";
|
||||
string args = $"{sseof} -i {inputFile.Wrap()} -update 1 {pixFmt} {sizeStr} {trim} {outputPath.Wrap()}";
|
||||
await RunFfmpeg(args, LogMode.Hidden, TaskType.ExtractFrames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Threading.Tasks;
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
|
||||
namespace Flowframes.Media
|
||||
{
|
||||
class GetFrameCountCached
|
||||
{
|
||||
public static Dictionary<PseudoUniqueFile, int> cache = new Dictionary<PseudoUniqueFile, int>();
|
||||
|
||||
public static async Task<int> GetFrameCountAsync(string path)
|
||||
{
|
||||
Logger.Log($"Getting frame count ({path})", true);
|
||||
|
||||
long filesize = IOUtils.GetFilesize(path);
|
||||
PseudoUniqueFile hash = new PseudoUniqueFile(path, filesize);
|
||||
|
||||
if (filesize > 0 && CacheContains(hash))
|
||||
{
|
||||
Logger.Log($"Cache contains this hash, using cached value.", true);
|
||||
return GetFromCache(hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log($"Hash not cached, reading frame count.", true);
|
||||
}
|
||||
|
||||
int frameCount;
|
||||
|
||||
if (IOUtils.IsPathDirectory(path))
|
||||
frameCount = IOUtils.GetAmountOfFiles(path, false);
|
||||
else
|
||||
frameCount = await FfmpegCommands.GetFrameCountAsync(path);
|
||||
|
||||
Logger.Log($"Adding hash with value {frameCount} to cache.", true);
|
||||
cache.Add(hash, frameCount);
|
||||
|
||||
return frameCount;
|
||||
}
|
||||
|
||||
private static bool CacheContains (PseudoUniqueFile hash)
|
||||
{
|
||||
foreach(KeyValuePair<PseudoUniqueFile, int> entry in cache)
|
||||
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int GetFromCache(PseudoUniqueFile hash)
|
||||
{
|
||||
foreach (KeyValuePair<PseudoUniqueFile, int> entry in cache)
|
||||
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
|
||||
return entry.Value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Threading.Tasks;
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
|
||||
namespace Flowframes.Media
|
||||
{
|
||||
class GetMediaResolutionCached
|
||||
{
|
||||
public static Dictionary<PseudoUniqueFile, Size> cache = new Dictionary<PseudoUniqueFile, Size>();
|
||||
|
||||
public static async Task<Size> GetSizeAsync(string path)
|
||||
{
|
||||
Logger.Log($"Getting media resolution ({path})", true);
|
||||
|
||||
long filesize = IOUtils.GetFilesize(path);
|
||||
PseudoUniqueFile hash = new PseudoUniqueFile(path, filesize);
|
||||
|
||||
if (filesize > 0 && CacheContains(hash))
|
||||
{
|
||||
Logger.Log($"Cache contains this hash, using cached value.", true);
|
||||
return GetFromCache(hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log($"Hash not cached, reading resolution.", true);
|
||||
}
|
||||
|
||||
Size size;
|
||||
size = await IOUtils.GetVideoOrFramesRes(path);
|
||||
|
||||
Logger.Log($"Adding hash with value {size} to cache.", true);
|
||||
cache.Add(hash, size);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private static bool CacheContains(PseudoUniqueFile hash)
|
||||
{
|
||||
foreach (KeyValuePair<PseudoUniqueFile, Size> entry in cache)
|
||||
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Size GetFromCache(PseudoUniqueFile hash)
|
||||
{
|
||||
foreach (KeyValuePair<PseudoUniqueFile, Size> entry in cache)
|
||||
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
|
||||
return entry.Value;
|
||||
|
||||
return new Size();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Flowframes.Data;
|
||||
using Flowframes.IO;
|
||||
using Flowframes.OS;
|
||||
|
||||
namespace Flowframes.Media
|
||||
{
|
||||
class GetVideoInfoCached
|
||||
{
|
||||
enum InfoType { Ffmpeg, Ffprobe };
|
||||
|
||||
static Dictionary<PseudoUniqueFile, string> ffmpegCache = new Dictionary<PseudoUniqueFile, string>();
|
||||
static Dictionary<PseudoUniqueFile, string> ffprobeCache = new Dictionary<PseudoUniqueFile, string>();
|
||||
|
||||
public static async Task<string> GetFfmpegInfoAsync(string path, string lineFilter = "")
|
||||
{
|
||||
return await GetInfoAsync(path, InfoType.Ffmpeg, lineFilter);
|
||||
}
|
||||
|
||||
public static async Task<string> GetFfprobeInfoAsync(string path, string lineFilter = "")
|
||||
{
|
||||
return await GetInfoAsync(path, InfoType.Ffprobe, lineFilter);
|
||||
}
|
||||
|
||||
static async Task<string> GetInfoAsync(string path, InfoType type, string lineFilter)
|
||||
{
|
||||
Logger.Log($"Get{type}InfoAsync({path})", true);
|
||||
Dictionary<PseudoUniqueFile, string> cacheDict = new Dictionary<PseudoUniqueFile, string>(type == InfoType.Ffmpeg ? ffmpegCache : ffprobeCache);
|
||||
long filesize = IOUtils.GetFilesize(path);
|
||||
PseudoUniqueFile hash = new PseudoUniqueFile(path, filesize);
|
||||
|
||||
if (filesize > 0 && CacheContains(hash, ref cacheDict))
|
||||
{
|
||||
Logger.Log($"Returning cached {type} info.", true);
|
||||
return GetFromCache(hash, ref cacheDict);
|
||||
}
|
||||
|
||||
Process process = OSUtils.NewProcess(true);
|
||||
string avPath = Path.Combine(Paths.GetPkgPath(), Paths.audioVideoDir);
|
||||
|
||||
if(type == InfoType.Ffmpeg)
|
||||
process.StartInfo.Arguments = $"/C cd /D {avPath.Wrap()} & ffmpeg.exe -hide_banner -y -stats -i {path.Wrap()}";
|
||||
|
||||
if (type == InfoType.Ffprobe)
|
||||
process.StartInfo.Arguments = $"/C cd /D {avPath.Wrap()} & ffprobe -v quiet -show_format -show_streams {path.Wrap()}";
|
||||
|
||||
string output = await OSUtils.GetOutputAsync(process);
|
||||
|
||||
if (type == InfoType.Ffmpeg)
|
||||
ffmpegCache.Add(hash, output);
|
||||
|
||||
if (type == InfoType.Ffprobe)
|
||||
ffprobeCache.Add(hash, output);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(lineFilter.Trim()))
|
||||
output = string.Join("\n", output.SplitIntoLines().Where(x => x.Contains(lineFilter)).ToArray());
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private static bool CacheContains(PseudoUniqueFile hash, ref Dictionary<PseudoUniqueFile, string> cacheDict)
|
||||
{
|
||||
foreach (KeyValuePair<PseudoUniqueFile, string> entry in cacheDict)
|
||||
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string GetFromCache(PseudoUniqueFile hash, ref Dictionary<PseudoUniqueFile, string> cacheDict)
|
||||
{
|
||||
foreach (KeyValuePair<PseudoUniqueFile, string> entry in cacheDict)
|
||||
if (entry.Key.path == hash.path && entry.Key.filesize == hash.filesize)
|
||||
return entry.Value;
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user