2021-02-02 12:56:48 +01:00
|
|
|
|
using Flowframes.Media;
|
2020-12-23 18:21:31 +01:00
|
|
|
|
using Flowframes.Data;
|
2020-12-02 14:27:41 +01:00
|
|
|
|
using Flowframes.IO;
|
2020-12-23 00:07:06 +01:00
|
|
|
|
using Flowframes.Main;
|
2020-12-29 16:01:24 +01:00
|
|
|
|
using Flowframes.MiscUtils;
|
2020-12-07 12:34:12 +01:00
|
|
|
|
using System;
|
2021-01-16 02:30:46 +01:00
|
|
|
|
using System.Collections.Generic;
|
2020-11-23 16:51:05 +01:00
|
|
|
|
using System.Drawing;
|
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
|
using System.IO;
|
2020-12-22 19:52:37 +01:00
|
|
|
|
using System.Linq;
|
2020-11-23 16:51:05 +01:00
|
|
|
|
using System.Threading.Tasks;
|
2021-02-01 16:23:35 +01:00
|
|
|
|
using static Flowframes.AvProcess;
|
2021-02-02 12:56:48 +01:00
|
|
|
|
using Utils = Flowframes.Media.FFmpegUtils;
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
|
|
|
|
|
namespace Flowframes
|
|
|
|
|
|
{
|
2021-02-02 12:56:48 +01:00
|
|
|
|
class FfmpegCommands
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
2021-03-11 23:15:43 +01:00
|
|
|
|
//public static string padFilter = "pad=width=ceil(iw/2)*2:height=ceil(ih/2)*2:color=black@0";
|
2021-04-18 18:11:47 +02:00
|
|
|
|
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";
|
2021-04-22 16:15:17 +02:00
|
|
|
|
public static string pngCompr = "-compression_level 3";
|
2021-02-01 16:23:35 +01:00
|
|
|
|
public static string mpDecDef = "\"mpdecimate\"";
|
|
|
|
|
|
public static string mpDecAggr = "\"mpdecimate=hi=64*32:lo=64*32:frac=0.1\"";
|
2020-11-25 12:40:17 +01:00
|
|
|
|
|
2021-03-13 00:42:41 +01:00
|
|
|
|
public static int GetPadding ()
|
|
|
|
|
|
{
|
|
|
|
|
|
return (Interpolate.current.ai.aiName == Networks.flavrCuda.aiName) ? 8 : 2; // FLAVR input needs to be divisible by 8
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-11 23:15:43 +01:00
|
|
|
|
public static string GetPadFilter ()
|
|
|
|
|
|
{
|
2021-03-13 00:42:41 +01:00
|
|
|
|
int padPixels = GetPadding();
|
|
|
|
|
|
return $"pad=width=ceil(iw/{padPixels})*{padPixels}:height=ceil(ih/{padPixels})*{padPixels}:color=black@0";
|
2021-03-11 23:15:43 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-06 17:41:18 +01:00
|
|
|
|
public static async Task ConcatVideos(string concatFile, string outPath, int looptimes = -1)
|
2020-11-30 02:14:04 +01:00
|
|
|
|
{
|
2021-03-25 18:57:29 +01:00
|
|
|
|
Logger.Log($"ConcatVideos('{Path.GetFileName(concatFile)}', '{outPath}', {looptimes})", true, false, "ffmpeg");
|
2021-01-06 21:44:09 +01:00
|
|
|
|
Logger.Log($"Merging videos...", false, Logger.GetLastLine().Contains("frame"));
|
2020-11-30 02:14:04 +01:00
|
|
|
|
string loopStr = (looptimes > 0) ? $"-stream_loop {looptimes}" : "";
|
2020-11-30 20:32:33 +01:00
|
|
|
|
string vfrFilename = Path.GetFileName(concatFile);
|
2021-04-02 14:36:08 +02:00
|
|
|
|
string args = $" {loopStr} -vsync 1 -f concat -i {vfrFilename} -c copy -movflags +faststart -fflags +genpts {outPath.Wrap()}";
|
2021-02-01 16:23:35 +01:00
|
|
|
|
await RunFfmpeg(args, concatFile.GetParentDir(), LogMode.Hidden, TaskType.Merge);
|
2020-11-30 02:14:04 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-18 15:32:45 +01:00
|
|
|
|
public static async Task LoopVideo(string inputFile, int times, bool delSrc = false)
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
|
|
|
|
|
string pathNoExt = Path.ChangeExtension(inputFile, null);
|
|
|
|
|
|
string ext = Path.GetExtension(inputFile);
|
2021-03-03 21:53:17 +01:00
|
|
|
|
string loopSuffix = Config.Get("exportNamePatternLoop").Replace("[LOOPS]", $"{times}").Replace("[PLAYS]", $"{times + 1}");
|
|
|
|
|
|
string args = $" -stream_loop {times} -i {inputFile.Wrap()} -c copy \"{pathNoExt}{loopSuffix}{ext}\"";
|
2021-02-01 16:23:35 +01:00
|
|
|
|
await RunFfmpeg(args, LogMode.Hidden);
|
2021-03-03 21:53:17 +01:00
|
|
|
|
|
2020-11-23 16:51:05 +01:00
|
|
|
|
if (delSrc)
|
|
|
|
|
|
DeleteSource(inputFile);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-12-15 14:46:33 +01:00
|
|
|
|
public static async Task ChangeSpeed(string inputFile, float newSpeedPercent, bool delSrc = false)
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
|
|
|
|
|
string pathNoExt = Path.ChangeExtension(inputFile, null);
|
|
|
|
|
|
string ext = Path.GetExtension(inputFile);
|
|
|
|
|
|
float val = newSpeedPercent / 100f;
|
|
|
|
|
|
string speedVal = (1f / val).ToString("0.0000").Replace(",", ".");
|
2021-03-02 15:06:44 +01:00
|
|
|
|
string args = " -itsscale " + speedVal + " -i \"" + inputFile + "\" -c copy \"" + pathNoExt + "-" + newSpeedPercent + "pcSpeed" + ext + "\"";
|
2021-02-01 16:23:35 +01:00
|
|
|
|
await RunFfmpeg(args, LogMode.OnlyLastLine);
|
2020-11-23 16:51:05 +01:00
|
|
|
|
if (delSrc)
|
|
|
|
|
|
DeleteSource(inputFile);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-01-30 13:09:59 +01:00
|
|
|
|
public static long GetDuration(string inputFile)
|
|
|
|
|
|
{
|
2021-02-08 10:21:26 +01:00
|
|
|
|
Logger.Log($"GetDuration({inputFile}) - Reading Duration using ffprobe.", true, false, "ffmpeg");
|
2021-01-30 13:09:59 +01:00
|
|
|
|
string args = $" -v panic -select_streams v:0 -show_entries format=duration -of csv=s=x:p=0 -sexagesimal {inputFile.Wrap()}";
|
2021-03-30 11:56:36 +02:00
|
|
|
|
string output = GetFfprobeOutput(args);
|
|
|
|
|
|
return FormatUtils.TimestampToMs(output);
|
2021-01-30 13:09:59 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-02 14:36:08 +02:00
|
|
|
|
public static async Task<Fraction> GetFramerate(string inputFile)
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
2021-02-07 17:47:12 +01:00
|
|
|
|
Logger.Log($"GetFramerate('{inputFile}')", true, false, "ffmpeg");
|
|
|
|
|
|
|
|
|
|
|
|
try
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
2021-04-02 14:36:08 +02:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2021-04-06 14:46:23 +02:00
|
|
|
|
string ffprobeArgs = $"-v panic -select_streams v:0 -show_entries stream=r_frame_rate {inputFile.Wrap()}";
|
2021-04-02 14:36:08 +02:00
|
|
|
|
string ffprobeOutput = GetFfprobeOutput(ffprobeArgs);
|
|
|
|
|
|
string fpsStr = ffprobeOutput.SplitIntoLines().Where(x => x.Contains("r_frame_rate")).First();
|
|
|
|
|
|
string[] numbers = fpsStr.Split('=')[1].Split('/');
|
|
|
|
|
|
Logger.Log($"Accurate FPS: {numbers[0]}/{numbers[1]} = {((float)numbers[0].GetInt() / numbers[1].GetInt())}", true, false, "ffmpeg");
|
|
|
|
|
|
return new Fraction(numbers[0].GetInt(), numbers[1].GetInt());
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ffprobeEx)
|
|
|
|
|
|
{
|
|
|
|
|
|
Logger.Log("GetFramerate ffprobe Error: " + ffprobeEx.Message, true, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-06 12:08:03 +02:00
|
|
|
|
string ffmpegOutput = await GetVideoInfoCached.GetFfmpegInfoAsync(inputFile);
|
2021-04-02 14:36:08 +02:00
|
|
|
|
string[] entries = ffmpegOutput.Split(',');
|
2021-02-07 17:47:12 +01:00
|
|
|
|
|
|
|
|
|
|
foreach (string entry in entries)
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
2021-02-07 17:47:12 +01:00
|
|
|
|
if (entry.Contains(" fps") && !entry.Contains("Input ")) // Avoid reading FPS from the filename, in case filename contains "fps"
|
|
|
|
|
|
{
|
|
|
|
|
|
string num = entry.Replace(" fps", "").Trim().Replace(",", ".");
|
|
|
|
|
|
float value;
|
|
|
|
|
|
float.TryParse(num, NumberStyles.Any, CultureInfo.InvariantCulture, out value);
|
2021-04-02 14:36:08 +02:00
|
|
|
|
return new Fraction(value);
|
2021-02-07 17:47:12 +01:00
|
|
|
|
}
|
2020-11-23 16:51:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-04-02 14:36:08 +02:00
|
|
|
|
catch(Exception ffmpegEx)
|
2021-02-07 17:47:12 +01:00
|
|
|
|
{
|
2021-04-02 14:36:08 +02:00
|
|
|
|
Logger.Log("GetFramerate ffmpeg Error: " + ffmpegEx.Message, true, false);
|
2021-02-07 17:47:12 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-04-02 14:36:08 +02:00
|
|
|
|
return new Fraction(0, 1);
|
2020-11-23 16:51:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-12-15 14:46:33 +01:00
|
|
|
|
public static Size GetSize(string inputFile)
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
2021-03-22 21:21:55 +01:00
|
|
|
|
Logger.Log($"GetSize('{inputFile}')", true, false, "ffmpeg");
|
2021-01-08 20:16:40 +01:00
|
|
|
|
string args = $" -v panic -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 {inputFile.Wrap()}";
|
2021-03-22 21:21:55 +01:00
|
|
|
|
string[] outputLines = GetFfprobeOutput(args).SplitIntoLines();
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
2021-03-22 21:21:55 +01:00
|
|
|
|
foreach(string line in outputLines)
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
2021-03-22 21:21:55 +01:00
|
|
|
|
if (!line.Contains("x") || line.Trim().Length < 3)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
string[] numbers = line.Split('x');
|
2020-11-23 16:51:05 +01:00
|
|
|
|
return new Size(numbers[0].GetInt(), numbers[1].GetInt());
|
|
|
|
|
|
}
|
2021-03-22 21:21:55 +01:00
|
|
|
|
|
2020-11-23 16:51:05 +01:00
|
|
|
|
return new Size(0, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-12-27 22:52:14 +01:00
|
|
|
|
public static async Task<int> GetFrameCountAsync(string inputFile)
|
|
|
|
|
|
{
|
2021-02-07 18:12:41 +01:00
|
|
|
|
Logger.Log($"GetFrameCountAsync('{inputFile}') - Trying ffprobe first.", true, false, "ffmpeg");
|
2020-12-27 22:52:14 +01:00
|
|
|
|
|
2021-04-22 16:58:37 +02:00
|
|
|
|
int frames = await ReadFrameCountFfprobeAsync(inputFile, Config.GetBool("ffprobeFrameCount")); // Try reading frame count with ffprobe
|
2021-02-08 10:21:26 +01:00
|
|
|
|
if (frames > 0) return frames;
|
2020-12-27 22:52:14 +01:00
|
|
|
|
|
2021-02-12 00:44:37 +01:00
|
|
|
|
Logger.Log($"Failed to get frame count using ffprobe (frames = {frames}). Trying to read with ffmpeg.", true, false, "ffmpeg");
|
2020-12-27 22:52:14 +01:00
|
|
|
|
frames = await ReadFrameCountFfmpegAsync(inputFile); // Try reading frame count with ffmpeg
|
2021-02-08 10:21:26 +01:00
|
|
|
|
if (frames > 0) return frames;
|
2020-12-27 22:52:14 +01:00
|
|
|
|
|
|
|
|
|
|
Logger.Log("Failed to get total frame count of video.");
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-08 10:21:26 +01:00
|
|
|
|
static int ReadFrameCountFromDuration (string inputFile, long durationMs, float fps)
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
2021-02-08 10:21:26 +01:00
|
|
|
|
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;
|
2020-12-27 22:52:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static async Task<int> ReadFrameCountFfprobeAsync(string inputFile, bool readFramesSlow)
|
|
|
|
|
|
{
|
2021-04-02 22:08:29 +02:00
|
|
|
|
string args = $" -v panic -threads 0 -select_streams v:0 -show_entries stream=nb_frames -of default=noprint_wrappers=1 {inputFile.Wrap()}";
|
2020-12-27 22:52:14 +01:00
|
|
|
|
if (readFramesSlow)
|
|
|
|
|
|
{
|
|
|
|
|
|
Logger.Log("Counting total frames using FFprobe. This can take a moment...");
|
|
|
|
|
|
await Task.Delay(10);
|
2021-04-02 22:08:29 +02:00
|
|
|
|
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()}";
|
2020-12-27 22:52:14 +01:00
|
|
|
|
}
|
2021-02-01 16:23:35 +01:00
|
|
|
|
string info = GetFfprobeOutput(args);
|
2020-12-27 22:52:14 +01:00
|
|
|
|
string[] entries = info.SplitIntoLines();
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (readFramesSlow)
|
|
|
|
|
|
return info.GetInt();
|
|
|
|
|
|
foreach (string entry in entries)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (entry.Contains("nb_frames="))
|
|
|
|
|
|
return entry.GetInt();
|
2020-11-23 16:51:05 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch { }
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-12-27 22:52:14 +01:00
|
|
|
|
static async Task<int> ReadFrameCountFfmpegAsync (string inputFile)
|
|
|
|
|
|
{
|
2021-05-06 12:08:03 +02:00
|
|
|
|
string args = $" -loglevel panic -stats -i {inputFile.Wrap()} -map 0:v:0 -c copy -f null - ";
|
2021-02-07 18:12:41 +01:00
|
|
|
|
string info = await GetFfmpegOutputAsync(args, true, true);
|
2020-12-27 22:52:14 +01:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
string[] lines = info.SplitIntoLines();
|
|
|
|
|
|
string lastLine = lines.Last();
|
|
|
|
|
|
return lastLine.Substring(0, lastLine.IndexOf("fps")).GetInt();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-06 12:08:03 +02:00
|
|
|
|
public static async Task<ColorInfo> GetColorInfo(string inputFile)
|
|
|
|
|
|
{
|
|
|
|
|
|
string ffprobeOutput = await GetVideoInfoCached.GetFfprobeInfoAsync(inputFile, "color");
|
|
|
|
|
|
ColorInfo colorInfo = new ColorInfo(ffprobeOutput);
|
|
|
|
|
|
Logger.Log($"Created ColorInfo - Range: {colorInfo.colorRange} - Space: {colorInfo.colorSpace} - Transer: {colorInfo.colorTransfer} - Primaries: {colorInfo.colorPrimaries}", true, false, "ffmpeg");
|
|
|
|
|
|
return colorInfo;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-21 21:18:31 +01:00
|
|
|
|
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");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-23 22:26:13 +01:00
|
|
|
|
public static string GetAudioCodec(string path, int streamIndex = -1)
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
2021-03-24 14:40:01 +01:00
|
|
|
|
Logger.Log($"GetAudioCodec('{Path.GetFileName(path)}', {streamIndex})", true, false, "ffmpeg");
|
2021-02-23 22:26:13 +01:00
|
|
|
|
string stream = (streamIndex < 0) ? "a" : $"{streamIndex}";
|
2021-03-03 16:00:48 +01:00
|
|
|
|
string args = $"-v panic -show_streams -select_streams {stream} -show_entries stream=codec_name {path.Wrap()}";
|
2021-02-01 16:23:35 +01:00
|
|
|
|
string info = GetFfprobeOutput(args);
|
2020-11-23 16:51:05 +01:00
|
|
|
|
string[] entries = info.SplitIntoLines();
|
2021-03-03 16:00:48 +01:00
|
|
|
|
|
2020-11-23 16:51:05 +01:00
|
|
|
|
foreach (string entry in entries)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (entry.Contains("codec_name="))
|
|
|
|
|
|
return entry.Split('=')[1];
|
|
|
|
|
|
}
|
|
|
|
|
|
return "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-03 16:00:48 +01:00
|
|
|
|
public static List<string> GetAudioCodecs(string path, int streamIndex = -1)
|
|
|
|
|
|
{
|
2021-03-24 14:40:01 +01:00
|
|
|
|
Logger.Log($"GetAudioCodecs('{Path.GetFileName(path)}', {streamIndex})", true, false, "ffmpeg");
|
2021-03-03 16:00:48 +01:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-02-01 16:23:35 +01:00
|
|
|
|
public static void DeleteSource(string path)
|
2020-11-23 16:51:05 +01:00
|
|
|
|
{
|
2020-11-30 02:14:04 +01:00
|
|
|
|
Logger.Log("[FFCmds] Deleting input file/dir: " + path, true);
|
2020-11-23 16:51:05 +01:00
|
|
|
|
|
|
|
|
|
|
if (IOUtils.IsPathDirectory(path) && Directory.Exists(path))
|
|
|
|
|
|
Directory.Delete(path, true);
|
|
|
|
|
|
|
|
|
|
|
|
if (!IOUtils.IsPathDirectory(path) && File.Exists(path))
|
|
|
|
|
|
File.Delete(path);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|