mirror of
https://github.com/n00mkrad/flowframes.git
synced 2025-12-22 19:29:24 +01:00
Completely revamped output settings (quality settings WIP)
This commit is contained in:
@@ -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}";
|
||||
|
||||
Reference in New Issue
Block a user