mirror of https://github.com/n00mkrad/flowframes
592 lines
27 KiB
C#
592 lines
27 KiB
C#
using Flowframes.Data;
|
|
using Flowframes.Data.Streams;
|
|
using Flowframes.IO;
|
|
using Flowframes.MiscUtils;
|
|
using Flowframes.Os;
|
|
using Flowframes.Properties;
|
|
using ImageMagick;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using static Flowframes.Data.Enums.Encoding;
|
|
using static Flowframes.Media.GetVideoInfo;
|
|
using Stream = Flowframes.Data.Streams.Stream;
|
|
|
|
namespace Flowframes.Media
|
|
{
|
|
class FfmpegUtils
|
|
{
|
|
private readonly static FfprobeMode showStreams = FfprobeMode.ShowStreams;
|
|
private readonly static FfprobeMode showFormat = FfprobeMode.ShowFormat;
|
|
|
|
public static List<Encoder> CompatibleHwEncoders = new List<Encoder>();
|
|
public static bool NvencSupportsBFrames = false;
|
|
|
|
public static async Task<int> GetStreamCount(string path)
|
|
{
|
|
Logger.Log($"GetStreamCount({path})", true);
|
|
string output = await GetFfmpegInfoAsync(path, "Stream #0:");
|
|
|
|
if (string.IsNullOrWhiteSpace(output.Trim()))
|
|
return 0;
|
|
|
|
return output.SplitIntoLines().Where(x => x.MatchesWildcard("*Stream #0:*: *: *")).Count();
|
|
}
|
|
|
|
public static async Task<List<Stream>> GetStreams(string path, bool progressBar, int streamCount, Fraction? defaultFps, bool countFrames)
|
|
{
|
|
List<Stream> streamList = new List<Stream>();
|
|
|
|
try
|
|
{
|
|
if (defaultFps == null)
|
|
defaultFps = new Fraction(30, 1);
|
|
|
|
string output = await GetFfmpegInfoAsync(path, "Stream #0:");
|
|
string[] streams = output.SplitIntoLines().Where(x => x.MatchesWildcard("*Stream #0:*: *: *")).ToArray();
|
|
|
|
foreach (string streamStr in streams)
|
|
{
|
|
try
|
|
{
|
|
int idx = streamStr.Split(':')[1].Split('[')[0].Split('(')[0].GetInt();
|
|
bool def = await GetFfprobeInfoAsync(path, showStreams, "DISPOSITION:default", idx) == "1";
|
|
|
|
if (progressBar)
|
|
Program.mainForm.SetProgress(FormatUtils.RatioInt(idx + 1, streamCount));
|
|
|
|
if (streamStr.Contains(": Video:"))
|
|
{
|
|
string lang = await GetFfprobeInfoAsync(path, showStreams, "TAG:language", idx);
|
|
string title = await GetFfprobeInfoAsync(path, showStreams, "TAG:title", idx);
|
|
string codec = await GetFfprobeInfoAsync(path, showStreams, "codec_name", idx);
|
|
string codecLong = await GetFfprobeInfoAsync(path, showStreams, "codec_long_name", idx);
|
|
string pixFmt = (await GetFfprobeInfoAsync(path, showStreams, "pix_fmt", idx)).ToUpper();
|
|
int kbits = (await GetFfprobeInfoAsync(path, showStreams, "bit_rate", idx)).GetInt() / 1024;
|
|
Size res = await GetMediaResolutionCached.GetSizeAsync(path);
|
|
Size sar = SizeFromString(await GetFfprobeInfoAsync(path, showStreams, "sample_aspect_ratio", idx));
|
|
Size dar = SizeFromString(await GetFfprobeInfoAsync(path, showStreams, "display_aspect_ratio", idx));
|
|
int frameCount = countFrames ? await GetFrameCountCached.GetFrameCountAsync(path) : 0;
|
|
FpsInfo fps = await GetFps(path, streamStr, idx, (Fraction)defaultFps, frameCount);
|
|
VideoStream vStream = new VideoStream(lang, title, codec, codecLong, pixFmt, kbits, res, sar, dar, fps, frameCount);
|
|
vStream.Index = idx;
|
|
vStream.IsDefault = def;
|
|
Logger.Log($"Added video stream: {vStream}", true);
|
|
streamList.Add(vStream);
|
|
continue;
|
|
}
|
|
|
|
if (streamStr.Contains(": Audio:"))
|
|
{
|
|
string lang = await GetFfprobeInfoAsync(path, showStreams, "TAG:language", idx);
|
|
string title = await GetFfprobeInfoAsync(path, showStreams, "TAG:title", idx);
|
|
string codec = await GetFfprobeInfoAsync(path, showStreams, "codec_name", idx);
|
|
string profile = await GetFfprobeInfoAsync(path, showStreams, "profile", idx);
|
|
if (codec.ToLowerInvariant() == "dts" && profile != "unknown") codec = profile;
|
|
string codecLong = await GetFfprobeInfoAsync(path, showStreams, "codec_long_name", idx);
|
|
int kbits = (await GetFfprobeInfoAsync(path, showStreams, "bit_rate", idx)).GetInt() / 1024;
|
|
int sampleRate = (await GetFfprobeInfoAsync(path, showStreams, "sample_rate", idx)).GetInt();
|
|
int channels = (await GetFfprobeInfoAsync(path, showStreams, "channels", idx)).GetInt();
|
|
string layout = (await GetFfprobeInfoAsync(path, showStreams, "channel_layout", idx));
|
|
AudioStream aStream = new AudioStream(lang, title, codec, codecLong, kbits, sampleRate, channels, layout);
|
|
aStream.Index = idx;
|
|
aStream.IsDefault = def;
|
|
Logger.Log($"Added audio stream: {aStream}", true);
|
|
streamList.Add(aStream);
|
|
continue;
|
|
}
|
|
|
|
if (streamStr.Contains(": Subtitle:"))
|
|
{
|
|
string lang = await GetFfprobeInfoAsync(path, showStreams, "TAG:language", idx);
|
|
string title = await GetFfprobeInfoAsync(path, showStreams, "TAG:title", idx);
|
|
string codec = await GetFfprobeInfoAsync(path, showStreams, "codec_name", idx);
|
|
string codecLong = await GetFfprobeInfoAsync(path, showStreams, "codec_long_name", idx);
|
|
bool bitmap = await IsSubtitleBitmapBased(path, idx, codec);
|
|
SubtitleStream sStream = new SubtitleStream(lang, title, codec, codecLong, bitmap);
|
|
sStream.Index = idx;
|
|
sStream.IsDefault = def;
|
|
Logger.Log($"Added subtitle stream: {sStream}", true);
|
|
streamList.Add(sStream);
|
|
continue;
|
|
}
|
|
|
|
if (streamStr.Contains(": Data:"))
|
|
{
|
|
string codec = await GetFfprobeInfoAsync(path, showStreams, "codec_name", idx);
|
|
string codecLong = await GetFfprobeInfoAsync(path, showStreams, "codec_long_name", idx);
|
|
DataStream dStream = new DataStream(codec, codecLong);
|
|
dStream.Index = idx;
|
|
dStream.IsDefault = def;
|
|
Logger.Log($"Added data stream: {dStream}", true);
|
|
streamList.Add(dStream);
|
|
continue;
|
|
}
|
|
|
|
if (streamStr.Contains(": Attachment:"))
|
|
{
|
|
string codec = await GetFfprobeInfoAsync(path, showStreams, "codec_name", idx);
|
|
string codecLong = await GetFfprobeInfoAsync(path, showStreams, "codec_long_name", idx);
|
|
string filename = await GetFfprobeInfoAsync(path, showStreams, "TAG:filename", idx);
|
|
string mimeType = await GetFfprobeInfoAsync(path, showStreams, "TAG:mimetype", idx);
|
|
AttachmentStream aStream = new AttachmentStream(codec, codecLong, filename, mimeType);
|
|
aStream.Index = idx;
|
|
aStream.IsDefault = def;
|
|
Logger.Log($"Added attachment stream: {aStream}", true);
|
|
streamList.Add(aStream);
|
|
continue;
|
|
}
|
|
|
|
Logger.Log($"Unknown stream (not vid/aud/sub/dat/att): {streamStr}", true);
|
|
Stream stream = new Stream { Codec = "Unknown", CodecLong = "Unknown", Index = idx, IsDefault = def, Type = Stream.StreamType.Unknown };
|
|
streamList.Add(stream);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.Log($"Error scanning stream: {e.Message}\n{e.StackTrace}");
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logger.Log($"GetStreams Exception: {e.Message}\n{e.StackTrace}", true);
|
|
}
|
|
|
|
Logger.Log($"Video Streams: {string.Join(", ", streamList.Where(x => x.Type == Stream.StreamType.Video).Select(x => string.IsNullOrWhiteSpace(x.Title) ? "No Title" : x.Title))}", true);
|
|
Logger.Log($"Audio Streams: {string.Join(", ", streamList.Where(x => x.Type == Stream.StreamType.Audio).Select(x => string.IsNullOrWhiteSpace(x.Title) ? "No Title" : x.Title))}", true);
|
|
Logger.Log($"Subtitle Streams: {string.Join(", ", streamList.Where(x => x.Type == Stream.StreamType.Subtitle).Select(x => string.IsNullOrWhiteSpace(x.Title) ? "No Title" : x.Title))}", true);
|
|
|
|
if (progressBar)
|
|
Program.mainForm.SetProgress(0);
|
|
|
|
return streamList;
|
|
}
|
|
|
|
private static async Task<FpsInfo> GetFps(string path, string streamStr, int streamIdx, Fraction defaultFps, int frameCount)
|
|
{
|
|
if (path.IsConcatFile())
|
|
return new FpsInfo(defaultFps);
|
|
|
|
if (streamStr.Contains("fps, ") && streamStr.Contains(" tbr"))
|
|
{
|
|
string fps = streamStr.Split(", ").Where(s => s.Contains(" fps")).First().Trim().Split(' ')[0];
|
|
string tbr = streamStr.Split("fps, ")[1].Split(" tbr")[0].Trim();
|
|
long durationMs = Interpolate.currentMediaFile.DurationMs;
|
|
float fpsCalc = (float)frameCount / (durationMs / 1000f);
|
|
fpsCalc = (float)Math.Round(fpsCalc, 5);
|
|
|
|
var info = new FpsInfo(new Fraction(fps.GetFloat())); // Set both true FPS and average FPS to this number for now
|
|
|
|
Logger.Log($"FPS: {fps} - TBR: {tbr} - Est. FPS: {fpsCalc.ToString("0.#####")}", true);
|
|
|
|
if (tbr != fps)
|
|
{
|
|
info.SpecifiedFps = new Fraction(tbr); // Change FPS to TBR if they mismatch
|
|
}
|
|
|
|
float fpsEstTolerance = GetFpsEstimationTolerance(durationMs);
|
|
|
|
if (Math.Abs(fps.GetFloat() - fpsCalc) > fpsEstTolerance)
|
|
{
|
|
Logger.Log($"Detected FPS {fps} is not within tolerance (+-{fpsEstTolerance}) of calculated FPS ({fpsCalc}), using estimated FPS.", true);
|
|
info.Fps = new Fraction(fpsCalc); // Change true FPS to the estimated FPS if the estimate does not match the specified FPS
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
return new FpsInfo(await IoUtils.GetVideoFramerate(path));
|
|
}
|
|
|
|
private static float GetFpsEstimationTolerance (long videoDurationMs)
|
|
{
|
|
if (videoDurationMs < 300) return 5.0f;
|
|
if (videoDurationMs < 1000) return 2.5f;
|
|
if (videoDurationMs < 2500) return 1.0f;
|
|
if (videoDurationMs < 5000) return 0.75f;
|
|
if (videoDurationMs < 10000) return 0.5f;
|
|
if (videoDurationMs < 20000) return 0.25f;
|
|
|
|
return 0.1f;
|
|
}
|
|
|
|
public static async Task<bool> IsSubtitleBitmapBased(string path, int streamIndex, string codec = "")
|
|
{
|
|
if (codec == "ssa" || codec == "ass" || codec == "mov_text" || codec == "srt" || codec == "subrip" || codec == "text" || codec == "webvtt")
|
|
return false;
|
|
|
|
if (codec == "dvdsub" || codec == "dvd_subtitle" || codec == "pgssub" || codec == "hdmv_pgs_subtitle" || codec.StartsWith("dvb_"))
|
|
return true;
|
|
|
|
// If codec was not listed above, manually check if it's compatible by trying to encode it:
|
|
//string ffmpegCheck = await GetFfmpegOutputAsync(path, $"-map 0:{streamIndex} -c:s srt -t 0 -f null -");
|
|
//return ffmpegCheck.Contains($"encoding currently only possible from text to text or bitmap to bitmap");
|
|
|
|
return false;
|
|
}
|
|
|
|
public static string[] GetEncArgs(OutputSettings settings, Size res, float fps, bool forceSinglePass = false) // Array contains as many entries as there are encoding passes.
|
|
{
|
|
Encoder enc = settings.Encoder;
|
|
int keyint = 10;
|
|
var args = new List<string>();
|
|
EncoderInfoVideo info = OutputUtils.GetEncoderInfoVideo(enc);
|
|
PixelFormat pixFmt = settings.PixelFormat;
|
|
|
|
if (settings.Format == Enums.Output.Format.Realtime)
|
|
pixFmt = PixelFormat.Yuv444P16Le;
|
|
|
|
if (pixFmt == (PixelFormat)(-1)) // No pixel format set in GUI
|
|
pixFmt = info.PixelFormatDefault != (PixelFormat)(-1) ? info.PixelFormatDefault : info.PixelFormats.First(); // Set default or fallback to first in list
|
|
|
|
args.Add($"-c:v {info.Name}");
|
|
|
|
if (enc == Encoder.X264 || enc == Encoder.X265 || enc == Encoder.SvtAv1 || enc == Encoder.VpxVp9 || enc == Encoder.Nvenc264 || enc == Encoder.Nvenc265 || enc == Encoder.NvencAv1)
|
|
args.Add(GetKeyIntArg(fps, keyint));
|
|
|
|
if (enc == Encoder.X264)
|
|
{
|
|
string preset = Config.Get(Config.Key.ffEncPreset).ToLowerInvariant().Remove(" "); // TODO: Replace this ugly stuff with enums
|
|
int crf = GetCrf(settings);
|
|
args.Add($"-crf {crf} -preset {preset}");
|
|
}
|
|
|
|
if (enc == Encoder.X265)
|
|
{
|
|
string preset = Config.Get(Config.Key.ffEncPreset).ToLowerInvariant().Remove(" "); // TODO: Replace this ugly stuff with enums
|
|
int crf = GetCrf(settings);
|
|
args.Add($"{(crf > 0 ? $"-crf {crf}" : "-x265-params lossless=1")} -preset {preset}");
|
|
}
|
|
|
|
if (enc == Encoder.SvtAv1)
|
|
{
|
|
int crf = GetCrf(settings);
|
|
args.Add($"-crf {crf} {GetSvtAv1Speed()} -svtav1-params enable-qm=1:enable-overlays=1:enable-tf=0:scd=0");
|
|
}
|
|
|
|
if (enc == Encoder.VpxVp9)
|
|
{
|
|
int crf = GetCrf(settings);
|
|
string qualityStr = (crf > 0) ? $"-crf {crf}" : "-lossless 1";
|
|
string t = GetTilingArgs(res, "-tile-columns ", "-tile-rows ");
|
|
|
|
if (forceSinglePass) // Force 1-pass
|
|
{
|
|
args.Add($"-b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1");
|
|
}
|
|
else
|
|
{
|
|
return new string[] {
|
|
$"{string.Join(" ", args)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 -pass 1 -an",
|
|
$"{string.Join(" ", args)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 -pass 2"
|
|
};
|
|
}
|
|
}
|
|
|
|
// Fix NVENC pixel formats
|
|
if (enc.ToString().StartsWith("Nvenc"))
|
|
{
|
|
if (pixFmt == PixelFormat.Yuv420P10Le) pixFmt = PixelFormat.P010Le;
|
|
}
|
|
|
|
if (enc == Encoder.Nvenc264)
|
|
{
|
|
int crf = GetCrf(settings);
|
|
args.Add($"-b:v 0 -preset p7 {(crf > 0 ? $"-cq {crf}" : "-tune lossless")}");
|
|
}
|
|
|
|
if (enc == Encoder.Nvenc265)
|
|
{
|
|
int crf = GetCrf(settings);
|
|
args.Add($"-b:v 0 -preset p7 {(crf > 0 ? $"-cq {crf}" : "-tune lossless")}");
|
|
}
|
|
|
|
if (enc == Encoder.NvencAv1)
|
|
{
|
|
int crf = GetCrf(settings);
|
|
args.Add($"-b:v 0 -preset p7 -cq {crf}");
|
|
}
|
|
|
|
if (enc == Encoder.Amf264)
|
|
{
|
|
int crf = GetCrf(settings);
|
|
args.Add($"-b:v 0 -rc cqp -qp_i {crf} -qp_p {crf} -quality 2");
|
|
}
|
|
|
|
if (enc == Encoder.Amf265)
|
|
{
|
|
int crf = GetCrf(settings);
|
|
args.Add($"-b:v 0 -rc cqp -qp_i {crf} -qp_p {crf} -quality 2");
|
|
}
|
|
|
|
if (enc == Encoder.Qsv264)
|
|
{
|
|
int crf = GetCrf(settings).Clamp(1, 51);
|
|
args.Add($"-preset veryslow -global_quality {crf}");
|
|
}
|
|
|
|
if (enc == Encoder.Qsv265)
|
|
{
|
|
int crf = GetCrf(settings).Clamp(1, 51);
|
|
args.Add($"-preset veryslow -global_quality {crf}");
|
|
}
|
|
|
|
if (enc == Encoder.ProResKs)
|
|
{
|
|
var profile = ParseUtils.GetEnum<Quality.ProResProfile>(settings.Quality, true, Strings.VideoQuality);
|
|
args.Add($"-profile:v {OutputUtils.ProresProfiles[profile]}");
|
|
}
|
|
|
|
if (enc == Encoder.Gif)
|
|
{
|
|
args.Add("-gifflags -offsetting");
|
|
}
|
|
|
|
if (enc == Encoder.Jpeg)
|
|
{
|
|
var qualityLevel = ParseUtils.GetEnum<Quality.JpegWebm>(settings.Quality, true, Strings.VideoQuality);
|
|
args.Add($"-q:v {OutputUtils.JpegQuality[qualityLevel]}");
|
|
}
|
|
|
|
if (enc == Encoder.Webp)
|
|
{
|
|
var qualityLevel = ParseUtils.GetEnum<Quality.JpegWebm>(settings.Quality, true, Strings.VideoQuality);
|
|
args.Add($"-q:v {OutputUtils.WebpQuality[qualityLevel]}");
|
|
}
|
|
|
|
if (enc == Encoder.Exr)
|
|
{
|
|
args.Add($"-format {settings.Quality.Lower()}");
|
|
}
|
|
|
|
if (pixFmt != (PixelFormat)(-1))
|
|
args.Add($"-pix_fmt {pixFmt.ToString().Lower()}");
|
|
|
|
return new string[] { string.Join(" ", args) };
|
|
}
|
|
|
|
private static int GetCrf(OutputSettings settings)
|
|
{
|
|
if (settings.CustomQuality.IsNotEmpty())
|
|
return settings.CustomQuality.GetInt();
|
|
else
|
|
return OutputUtils.GetCrf(ParseUtils.GetEnum<Quality.Common>(settings.Quality, true, Strings.VideoQuality), settings.Encoder);
|
|
}
|
|
|
|
public static string GetTilingArgs(Size resolution, string colArg, string rowArg)
|
|
{
|
|
int cols = 0;
|
|
if (resolution.Width >= 1920) cols = 1;
|
|
if (resolution.Width >= 3840) cols = 2;
|
|
if (resolution.Width >= 7680) cols = 3;
|
|
|
|
int rows = 0;
|
|
if (resolution.Height >= 1600) rows = 1;
|
|
if (resolution.Height >= 3200) rows = 2;
|
|
if (resolution.Height >= 6400) rows = 3;
|
|
|
|
Logger.Log($"GetTilingArgs: Video resolution is {resolution.Width}x{resolution.Height} - Using 2^{cols} columns, 2^{rows} rows (=> {Math.Pow(2, cols)}x{Math.Pow(2, rows)} = {Math.Pow(2, cols) * Math.Pow(2, rows)} Tiles)", true);
|
|
|
|
return $"{(cols > 0 ? colArg + cols : "")} {(rows > 0 ? rowArg + rows : "")}";
|
|
}
|
|
|
|
public static string GetKeyIntArg(float fps, int intervalSeconds, string arg = "-g ")
|
|
{
|
|
int keyInt = (fps * intervalSeconds).RoundToInt().Clamp(30, 600);
|
|
return $"{arg}{keyInt}";
|
|
}
|
|
|
|
static string GetVp9Speed()
|
|
{
|
|
string preset = Config.Get(Config.Key.ffEncPreset).ToLowerInvariant().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).ToLowerInvariant().Remove(" ");
|
|
string arg = "8";
|
|
|
|
if (preset == "veryslow") arg = "3";
|
|
if (preset == "slower") arg = "4";
|
|
if (preset == "slow") arg = "5";
|
|
if (preset == "medium") arg = "6";
|
|
if (preset == "fast") arg = "7";
|
|
if (preset == "faster") arg = "8";
|
|
if (preset == "veryfast") arg = "9";
|
|
|
|
return $"-preset {arg}";
|
|
}
|
|
|
|
public static bool ContainerSupportsAllAudioFormats(Enums.Output.Format outFormat, 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(outFormat, format))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static bool ContainerSupportsAudioFormat(Enums.Output.Format outFormat, string format)
|
|
{
|
|
bool supported = false;
|
|
string alias = GetAudioExt(format);
|
|
|
|
string[] formatsMp4 = new string[] { "m4a", "mp3", "ac3", "dts" };
|
|
string[] formatsMkv = new string[] { "m4a", "mp3", "ac3", "dts", "ogg", "mp2", "wav", "wma" };
|
|
string[] formatsWebm = new string[] { "ogg" };
|
|
string[] formatsMov = new string[] { "m4a", "ac3", "dts", "wav" };
|
|
string[] formatsAvi = new string[] { "m4a", "ac3", "dts" };
|
|
|
|
switch (outFormat)
|
|
{
|
|
case Enums.Output.Format.Mp4: supported = formatsMp4.Contains(alias); break;
|
|
case Enums.Output.Format.Mkv: supported = formatsMkv.Contains(alias); break;
|
|
case Enums.Output.Format.Webm: supported = formatsWebm.Contains(alias); break;
|
|
case Enums.Output.Format.Mov: supported = formatsMov.Contains(alias); break;
|
|
case Enums.Output.Format.Avi: supported = formatsAvi.Contains(alias); break;
|
|
}
|
|
|
|
Logger.Log($"Checking if {outFormat} supports audio format '{format}' ({alias}): {supported}", true, false, "ffmpeg");
|
|
return supported;
|
|
}
|
|
|
|
public static string GetExt(OutputSettings settings, bool dot = true)
|
|
{
|
|
string ext = dot ? "." : "";
|
|
EncoderInfoVideo info = settings.Encoder.GetInfo();
|
|
|
|
if (string.IsNullOrWhiteSpace(info.OverideExtension))
|
|
ext += settings.Format.ToString().Lower();
|
|
else
|
|
ext += info.OverideExtension;
|
|
|
|
return ext;
|
|
}
|
|
|
|
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 async Task<string> GetAudioFallbackArgs(string videoPath, Enums.Output.Format outFormat, float itsScale)
|
|
{
|
|
bool opusMp4 = Config.GetBool(Config.Key.allowOpusInMp4);
|
|
int opusBr = Config.GetInt(Config.Key.opusBitrate, 128);
|
|
int aacBr = Config.GetInt(Config.Key.aacBitrate, 160);
|
|
int ac = (await GetVideoInfo.GetFfprobeInfoAsync(videoPath, GetVideoInfo.FfprobeMode.ShowStreams, "channels", 0)).GetInt();
|
|
string af = GetAudioFilters(itsScale);
|
|
|
|
if (outFormat == Enums.Output.Format.Mkv || outFormat == Enums.Output.Format.Webm || (outFormat == Enums.Output.Format.Mp4 && opusMp4))
|
|
return $"-c:a libopus -b:a {(ac > 4 ? $"{opusBr * 2}" : $"{opusBr}")}k -ac {(ac > 0 ? $"{ac}" : "2")} {af}"; // Double bitrate if 5ch or more, ignore ac if <= 0
|
|
else
|
|
return $"-c:a aac -b:a {(ac > 4 ? $"{aacBr * 2}" : $"{aacBr}")}k -aac_coder twoloop -ac {(ac > 0 ? $"{ac}" : "2")} {af}";
|
|
}
|
|
|
|
private static string GetAudioFilters(float itsScale)
|
|
{
|
|
if (itsScale == 0 || itsScale == 1)
|
|
return "";
|
|
|
|
if (itsScale > 4)
|
|
return $"-af atempo=0.5,atempo=0.5,atempo={((1f / itsScale) * 4).ToStringDot()}";
|
|
else if (itsScale > 2)
|
|
return $"-af atempo=0.5,atempo={((1f / itsScale) * 2).ToStringDot()}";
|
|
else
|
|
return $"-af atempo={(1f / itsScale).ToStringDot()}";
|
|
}
|
|
|
|
public static string GetSubCodecForContainer(string containerExt)
|
|
{
|
|
containerExt = containerExt.Remove(".");
|
|
|
|
if (containerExt == "mp4" || containerExt == "mov") return "mov_text";
|
|
if (containerExt == "webm") return "webvtt";
|
|
|
|
return "copy"; // Default: Copy 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: {containerExt.ToUpper()} exports do not include subtitles.");
|
|
|
|
return supported;
|
|
}
|
|
|
|
public static int CreateConcatFile(string inputFilesDir, string outputPath, List<string> validExtensions = null)
|
|
{
|
|
if (IoUtils.GetAmountOfFiles(inputFilesDir, false) < 1)
|
|
return 0;
|
|
|
|
Directory.CreateDirectory(outputPath.GetParentDir());
|
|
validExtensions = validExtensions ?? new List<string>();
|
|
validExtensions = validExtensions.Select(x => x.Remove(".").Lower()).ToList(); // Ignore "." in extensions
|
|
var validFiles = IoUtils.GetFilesSorted(inputFilesDir).Where(f => validExtensions.Contains(Path.GetExtension(f).Replace(".", "").Lower()));
|
|
string fileContent = string.Join(Environment.NewLine, validFiles.Select(f => $"file '{f.Replace(@"\", "/")}'"));
|
|
IoUtils.TryDeleteIfExists(outputPath);
|
|
File.WriteAllText(outputPath, fileContent);
|
|
|
|
return validFiles.Count();
|
|
}
|
|
|
|
public static Size SizeFromString(string str, char delimiter = ':')
|
|
{
|
|
try
|
|
{
|
|
if (str.IsEmpty() || str.Length < 3 || !str.Contains(delimiter))
|
|
return new Size();
|
|
|
|
string[] nums = str.Remove(" ").Trim().Split(delimiter);
|
|
return new Size(nums[0].GetInt(), nums[1].GetInt());
|
|
}
|
|
catch
|
|
{
|
|
return new Size();
|
|
}
|
|
}
|
|
}
|
|
}
|