Completely revamped output settings (quality settings WIP)

This commit is contained in:
n00mkrad
2023-01-15 17:23:49 +01:00
parent f5536e07f3
commit 3fa47a70a4
25 changed files with 801 additions and 261 deletions

View File

@@ -8,6 +8,7 @@ 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;
@@ -170,127 +171,88 @@ namespace Flowframes.Media
return false;
}
public enum Codec { H264, H265, H264Nvenc, H265Nvenc, Av1, Vp9, ProRes, AviRaw, Gif }
public static Codec GetCodec(Interpolate.OutMode mode)
{
if (mode == Interpolate.OutMode.VidMp4 || mode == Interpolate.OutMode.VidMkv)
{
int mp4MkvEnc = Config.GetInt(Config.Key.mp4Enc);
if (mp4MkvEnc == 0) return Codec.H264;
if (mp4MkvEnc == 1) return Codec.H265;
if (mp4MkvEnc == 2) return Codec.H264Nvenc;
if (mp4MkvEnc == 3) return Codec.H265Nvenc;
if (mp4MkvEnc == 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;
if (mode == Interpolate.OutMode.VidGif)
return Codec.Gif;
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);
case Codec.Gif: return "gif";
}
return "libx264";
}
public static string[] GetEncArgs(Codec codec, Size res, float fps, bool realtime = false) // Array contains as many entries as there are encoding passes. If "realtime" is true, force single pass.
public static string[] GetEncArgs(Encoder enc, PixelFormat pixFmt, Size res, float fps, bool realtime = false) // Array contains as many entries as there are encoding passes. If "realtime" is true, force single pass.
{
int keyint = 10;
if (codec == Codec.H264)
var args = new List<string>();
EncoderInfoVideo info = OutputUtils.GetEncoderInfoVideo(enc);
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 (pixFmt != (PixelFormat)(-1))
args.Add($"-pix_fmt {pixFmt.ToString().Lower()}");
if (enc == Encoder.X264)
{
string preset = Config.Get(Config.Key.ffEncPreset).ToLowerInvariant().Remove(" ");
string g = GetKeyIntArg(fps, keyint);
return new string[] { $"-c:v {GetEnc(codec)} -crf {Config.GetInt(Config.Key.h264Crf)} -preset {preset} {g} -pix_fmt {GetPixFmt()}" };
string preset = Config.Get(Config.Key.ffEncPreset).ToLowerInvariant().Remove(" "); // TODO: Replace this ugly stuff with enums
args.Add($"-crf {Config.GetInt(Config.Key.h264Crf)} -preset {preset}");
}
if (codec == Codec.H265)
if (enc == Encoder.X265)
{
string preset = Config.Get(Config.Key.ffEncPreset).ToLowerInvariant().Remove(" ");
string preset = Config.Get(Config.Key.ffEncPreset).ToLowerInvariant().Remove(" "); // TODO: Replace this ugly stuff with enums
int crf = Config.GetInt(Config.Key.h265Crf);
string g = GetKeyIntArg(fps, keyint);
return new string[] { $"-c:v {GetEnc(codec)} {(crf > 0 ? $"-crf {crf}" : "-x265-params lossless=1")} -preset {preset} {g} -pix_fmt {GetPixFmt()}" };
args.Add($"{(crf > 0 ? $"-crf {crf}" : "-x265-params lossless=1")} -preset {preset}");
}
if (codec == Codec.H264Nvenc)
{
int cq = (Config.GetInt(Config.Key.h264Crf) * 1.1f).RoundToInt();
return new string[] { $"-c:v {GetEnc(codec)} -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();
return new string[] { $"-c:v {GetEnc(codec)} -b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")} -pix_fmt {GetPixFmt()}" };
}
if (codec == Codec.Av1)
if (enc == Encoder.SvtAv1)
{
int cq = Config.GetInt(Config.Key.av1Crf);
string g = GetKeyIntArg(fps, keyint);
return new string[] { $"-c:v {GetEnc(codec)} -b:v 0 -qp {cq} {GetSvtAv1Speed()} {g} -svtav1-params enable-qm=1:enable-overlays=1:enable-tf=0:scd=0 -pix_fmt {GetPixFmt()}" };
args.Add($"-b:v 0 -qp {cq} {GetSvtAv1Speed()} -svtav1-params enable-qm=1:enable-overlays=1:enable-tf=0:scd=0");
}
if (codec == Codec.Vp9)
if (enc == Encoder.VpxVp9)
{
int crf = Config.GetInt(Config.Key.vp9Crf);
string qualityStr = (crf > 0) ? $"-crf {crf}" : "-lossless 1";
string g = GetKeyIntArg(fps, keyint);
string t = GetTilingArgs(res, "-tile-columns ", "-tile-rows ");
if (realtime) // Force 1-pass
{
return new string[] { $"-c:v {GetEnc(codec)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 {g} -pix_fmt {GetPixFmt()}" };
args.Add($"-b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1");
}
else
{
return new string[] {
$"-c:v {GetEnc(codec)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 {g} -pass 1 -pix_fmt {GetPixFmt()} -an",
$"-c:v {GetEnc(codec)} -b:v 0 {qualityStr} {GetVp9Speed()} {t} -row-mt 1 {g} -pass 2 -pix_fmt {GetPixFmt()}"
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"
};
}
}
if (codec == Codec.ProRes)
if (enc == Encoder.Nvenc264)
{
return new string[] { $"-c:v {GetEnc(codec)} -profile:v {Config.GetInt(Config.Key.proResProfile)} -pix_fmt {GetPixFmt()}" };
int cq = (Config.GetInt(Config.Key.h264Crf) * 1.1f).RoundToInt();
args.Add($"-b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")}");
}
if (codec == Codec.AviRaw)
if (enc == Encoder.Nvenc265)
{
return new string[] { $"-c:v {GetEnc(codec)} -pix_fmt {Config.Get(Config.Key.aviColors)}" };
int cq = (Config.GetInt(Config.Key.h265Crf) * 1.1f).RoundToInt();
args.Add($"-b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")}");
}
if (codec == Codec.Gif)
if (enc == Encoder.NvencAv1)
{
return new string[] { $"-c:v {GetEnc(codec)} -gifflags -offsetting" };
int cq = (Config.GetInt(Config.Key.av1Crf) * 1.1f).RoundToInt();
args.Add($"-b:v 0 {(cq > 0 ? $"-cq {cq} -preset p7" : "-preset lossless")}");
}
return new string[0];
if (enc == Encoder.ProResKs)
{
args.Add($"-profile:v {Config.GetInt(Config.Key.proResProfile)}");
}
if (enc == Encoder.Gif)
{
args.Add("-gifflags -offsetting");
}
return new string[] { string.Join(" ", args) };
}
public static string GetTilingArgs(Size resolution, string colArg, string rowArg)
@@ -307,12 +269,12 @@ namespace Flowframes.Media
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 : "")}";
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(20, 300);
int keyInt = (fps * intervalSeconds).RoundToInt().Clamp(30, 600);
return $"{arg}{keyInt}";
}
@@ -348,34 +310,21 @@ namespace Flowframes.Media
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)
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(outMode, format))
if (!ContainerSupportsAudioFormat(outFormat, format))
return false;
}
return true;
}
public static bool ContainerSupportsAudioFormat(Interpolate.OutMode outMode, string format)
public static bool ContainerSupportsAudioFormat(Enums.Output.Format outFormat, string format)
{
bool supported = false;
string alias = GetAudioExt(format);
@@ -383,35 +332,31 @@ namespace Flowframes.Media
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[] formatsProres = new string[] { "m4a", "ac3", "dts", "wav" };
string[] formatsMov = new string[] { "m4a", "ac3", "dts", "wav" };
string[] formatsAvi = new string[] { "m4a", "ac3", "dts" };
switch (outMode)
switch (outFormat)
{
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;
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 {outMode} supports audio format '{format}' ({alias}): {supported}", true, false, "ffmpeg");
Logger.Log($"Checking if {outFormat} supports audio format '{format}' ({alias}): {supported}", true, false, "ffmpeg");
return supported;
}
public static string GetExt(Interpolate.OutMode outMode, bool dot = true)
public static string GetExt(ExportSettings settings, bool dot = true)
{
string ext = dot ? "." : "";
EncoderInfoVideo info = settings.Encoder.GetInfo();
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;
}
if (string.IsNullOrWhiteSpace(info.OverideExtension))
ext += settings.Format.ToString().Lower();
else
ext += info.OverideExtension;
return ext;
}
@@ -440,7 +385,7 @@ namespace Flowframes.Media
return "unsupported";
}
public static async Task<string> GetAudioFallbackArgs(string videoPath, Interpolate.OutMode outMode, float itsScale)
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);
@@ -448,7 +393,7 @@ namespace Flowframes.Media
int ac = (await GetVideoInfo.GetFfprobeInfoAsync(videoPath, GetVideoInfo.FfprobeMode.ShowStreams, "channels", 0)).GetInt();
string af = GetAudioFilters(itsScale);
if (outMode == Interpolate.OutMode.VidMkv || outMode == Interpolate.OutMode.VidWebm || (outMode == Interpolate.OutMode.VidMp4 && opusMp4))
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}";