From 5ce7ee8738c7cc2705a21038c83ebc3f7c432937 Mon Sep 17 00:00:00 2001 From: John Andrews Date: Fri, 26 Apr 2024 17:46:54 +1200 Subject: [PATCH] FF-1165 - new flow element for bitrate encoding videos --- FileFlows.Plugin.dll | Bin 135680 -> 136192 bytes FileFlows.Plugin.pdb | Bin 33036 -> 33140 bytes .../FFmpegBuilderVideoBitrateEncode/AV1.cs | 79 +++++ .../FfmpegBuilderVideoBitrateEncode.cs | 319 ++++++++++++++++++ .../FFmpegBuilderVideoBitrateEncode/VP9.cs | 40 +++ .../FFmpegBuilderVideoBitrateEncode/h26x.cs | 106 ++++++ .../Video/FfmpegBuilderVideoBitrate.cs | 2 +- .../FfmpegBuilderVideoEncode.cs | 69 +--- .../Video/_VideoEncodeBase.cs | 78 +++++ VideoNodes/i18n/en.json | 16 + 10 files changed, 640 insertions(+), 69 deletions(-) create mode 100644 VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/AV1.cs create mode 100644 VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/FfmpegBuilderVideoBitrateEncode.cs create mode 100644 VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/VP9.cs create mode 100644 VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/h26x.cs create mode 100644 VideoNodes/FfmpegBuilderNodes/Video/_VideoEncodeBase.cs diff --git a/FileFlows.Plugin.dll b/FileFlows.Plugin.dll index 057f8fc210a39ff5854926b1eb6d6ab64898a586..f3a1262f1ce8fa380d600034dc66c2a9ffa0c501 100644 GIT binary patch delta 44782 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 delta 44621 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 diff --git a/FileFlows.Plugin.pdb b/FileFlows.Plugin.pdb index a40c5e633810df5a8d488133a061c1c886160e0c..acb05c04c37eefbfe86678b5fa0a5cbaf98f07b1 100644 GIT binary patch delta 4603 zcmZwK30PF+8VB(AJ2NaZEQ5l$fCvqSIwJ!E%&;oUV6w`vWomW>M>0t>^`fMXikJbe zAK^kSxa6Mc2!h*1p)9pjG;@74HMP2yn%yQ#c^lGg8_M6orgO79nRM_amE;50Oui!48Zf z9Rw;wPC;%*)*^2r?;?F18TED)Xav%NtU|6qevSMVDN|q{g+MV#lfo*{But8NpcZ)u z`2hI@>91s@Q3^B?X+f?)u0MD>IJq#m0$KKre%#;3(Q7iNBhP0V1B%P{tR6(epwtvq`ed)HT zFZ~RUpcQ0^eMt$Opd0jpm!Us|LKMV95@f)uFfy^@sr4gEVm~T`Nl*;4VLmK^s8csA=_JHEn~Va066HYSO?Im;viy9}G@b zlPwuf(ZkxCic-W!=S?4{cvExRyeADx?MZ2=;&C6!PW7Q_ZS!8VBefSDZ=V~|d?^M@ zY2vz$A3sV-^P{r1W4K+W31owPa2URTlW-2Mz)dTLpTH?yAPuCzI4Dn-(Kgr(4e4?^ zmTpJq;0n~m+2Lieqnnuj1P{{f$z_N=d4Lxr!5c6IDqubAgBJJ^gbaJ?0s0IFvSwj$ z8R9_0G8}1KhJuPRlvDy`P-#@+#;c?y8BWv;pBkNT19qb0n76*r*vCcHj>~5tpvpZG8Iv% zPx{8(70r-Q)RO?8~*Z zF;jda`0DV*Lq{42&+0f+%n<6CgPUNE&0BfS)9OKmB($}a80Rz3ztkV#D@I4(0sS&b z?TETuQj7fxNiFslux`)K?}B=vq%W>tWR)BcPgpFe#RW?wwb)-Osl`5*)MCGq{rQA^ zQGY0@6{xHJR$G@zE+AfTxug~^xI$8k{gskh>{m-_vHy{z7W=Ct8~UPN{ny{HmDWfW zh%dp~zb>E}$pys?)=6q{gY}a2Zm2g%uGb0mM#*~d{EsEIBUyK&5x;uHKtT4R6ODcL~W@KZ@GZn#TQi~Ze_TI|~-wbRn%Z8!jgMt9=6b%szn8T$PCLO|X?**K^(*^7>!Ij9v-_i-fc`<$;RN6TK|0L~K<+qI`ZDSM0y{a2+V+oU!O?3m~E|pA>(@vF4CZ`=L zb=#Pau~yZDu|ZX3FQX#oI*f@f=P>5D&T@-zq)XYeA{6v6+c(mYhS_TSw8{r~3>sT# z$r@X1iJOf7*7BxIwgl!+a?la8bDsT~@7pVLXFOh>p1v{I)pxOQ^5ag0&mN`c4s;Q+ z2TrcIawvoE>fc?MBjYFf4`2hiQ(z36$&&(O*#TY_IDkFnUk1hrCDGhj7sISPSr^3) z@_D*J>@oie^H{D3iel4wVo)rr=ktPMaJ)GvS|~MgAvl6f;nBfyY$u{s4k2olP*{3k=KY-D)>4>S&t)s%1I6O57UI^Sx{ zbgbxE;aAbOB8bbwKkq2+*jmCJne~s>LO0*S4A;1?FPV>Aj1PQOZ*Bg%Yo^=kVaJAA zTRbSb? zDEZI~V_n%x{eATBUHspPXSWU)F1`HEx6Df$WA3M4t=fFb?fur8Z-!mUJ|bApc#lfa zdbh@U`Ci*oHF5rBhm)Fh$$R1)zCEOwYucP7tC(8u>v86D-$}8%UV0TzyJvumIR9?PiOh}&IkY_REg+~O1 z8}h^RB0}>cLPv+|LySg#JF1w4+VY~0_V#?j>I7Ej%If;Ex)4^E!0NJD-8eolFXY9o z8a_Yoj{6JFvX$g#vfeLfu(}#nznRrHvihs6{vK;k*nS_KrBFR#jTAq>ZToT9)iz@M ze;BK=WxX?$b$`)iaDloSTl2(~4sx_zZF$oU2##uJje?jwjQ5ysQiz9f(Tq*Dg6X~G WPA8gi`??CVPw-{4Cd*IbX7+!0CGh0{ delta 4519 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 A@c;k- diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/AV1.cs b/VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/AV1.cs new file mode 100644 index 00000000..fe0ffc01 --- /dev/null +++ b/VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/AV1.cs @@ -0,0 +1,79 @@ +using FileFlows.VideoNodes.Helpers; + +namespace FileFlows.VideoNodes.FfmpegBuilderNodes; + +public partial class FfmpegBuilderVideoBitrateEncode +{ + /// + /// AV1 CPU encoding + /// + /// the bitrate in Kbps + /// the encoding parameters + private static IEnumerable AV1_CPU(int bitrate) + { + return new [] + { + //"libaom-av1", + "libsvtav1", + "-b:v:{index}", bitrate + "k", + "-minrate", bitrate + "k", + "-maxrate", bitrate + "k", + "-bufsize", (bitrate * 2) + "k" + }; + } + + /// + /// AV1 AMD encoding + /// + /// the bitrate in Kbps + /// the encoding parameters + private static IEnumerable AV1_Amd(int bitrate) + { + return new[] + { + "av1_amf", + "-b:v:{index}", bitrate + "k", + "-minrate", bitrate + "k", + "-maxrate", bitrate + "k", + "-bufsize", (bitrate * 2) + "k" + }; + } + + /// + /// AV1 NVIDIA encoding + /// + /// the bitrate in Kbps + /// the encoding parameters + private static IEnumerable AV1_Nvidia(int bitrate) + { + return new [] + { + "av1_nvenc", + "-b:v:{index}", bitrate + "k", + "-minrate", bitrate + "k", + "-maxrate", bitrate + "k", + "-bufsize", (bitrate * 2) + "k" + }; + } + + /// + /// AV1 QSV encoding + /// + /// the bitrate in Kbps + /// the encoding parameters + private static IEnumerable AV1_Qsv(int bitrate) + { + var args = new List + { + "av1_qsv", + "-b:v:{index}", bitrate + "k", + "-minrate", bitrate + "k", + "-maxrate", bitrate + "k", + "-bufsize", (bitrate * 2) + "k" + }; + if(VaapiHelper.VaapiLinux) + args.AddRange(new [] { "-qsv_device", VaapiHelper.VaapiRenderDevice}); + + return args.ToArray(); + } +} \ No newline at end of file diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/FfmpegBuilderVideoBitrateEncode.cs b/VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/FfmpegBuilderVideoBitrateEncode.cs new file mode 100644 index 00000000..8136fc9b --- /dev/null +++ b/VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/FfmpegBuilderVideoBitrateEncode.cs @@ -0,0 +1,319 @@ +using System.Runtime.InteropServices; +using FileFlows.VideoNodes.FfmpegBuilderNodes.Models; + +namespace FileFlows.VideoNodes.FfmpegBuilderNodes; + +/// +/// Set a video codec encoding by bitrate for a video stream based on users settings +/// +public partial class FfmpegBuilderVideoBitrateEncode:VideoEncodeBase +{ + /// + /// The number of outputs for this flow element + /// + public override int Outputs => 1; + + /// + /// The Help URL for this flow element + /// + public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/ffmpeg-builder/bitrate-encode"; + + /// + /// Gets or sets the codec used to encode + /// + [DefaultValue(CODEC_H265)] + [Select(nameof(CodecOptions), 1)] + public string Codec { get; set; } = string.Empty; + + /// + /// Gets or sets the encoder to use + /// + [Select(nameof(Encoders), 2)] + [ConditionEquals(nameof(Codec), "/av1/", inverse: true)] + public string Encoder { get; set; } = string.Empty; + + /// + /// Gets or sets the bitrate to use in Kbps + /// + [NumberInt(3)] + [DefaultValue(5_000)] + public int Bitrate { get; set; } + + + + /// + /// Executes the node + /// + /// The node arguments + /// the output return + public override int Execute(NodeParameters args) + { + var stream = Model.VideoStreams.First(x => x.Deleted == false); + + stream.EncodingParameters.Clear(); + + // remove anything that may have been added before + stream.Filter = stream.Filter.Where(x => x?.StartsWith("scale_qsv") != true).ToList(); + + string encoder = Environment.GetEnvironmentVariable("HW_OFF") == "1" || + ( + args.Variables?.TryGetValue("HW_OFF", out object oHwOff) == true && (oHwOff as bool? == true || oHwOff?.ToString() == "1") + ) ? ENCODER_CPU : this.Encoder; + + args.Logger?.ILog("Bitrate: " + Bitrate); + args.Logger?.ILog("Codec: " + Codec); + if (Codec == CODEC_H264) + { + var encodingParameters = H264(args, false, encoder, Bitrate).ToArray(); + args.Logger?.ILog("Encoding Parameters: " + + string.Join(" ", encodingParameters.Select(x => x.Contains(" ") ? "\"" + x + "\"" : x))); + stream.EncodingParameters.AddRange(encodingParameters); + stream.Codec = CODEC_H264; + } + else if (Codec is CODEC_H265 or CODEC_H265_10BIT or CODEC_H265_8BIT) + { + bool tenBit = (Codec == CODEC_H265_10BIT || stream.Stream.Is10Bit) && (Codec != CODEC_H265_8BIT); + bool forceBitSetting = Codec is CODEC_H265_10BIT or CODEC_H265_8BIT; + args.Logger?.ILog("10 Bit: " + tenBit); + var encodingParameters = H265(stream, args, tenBit, Bitrate, encoder, stream.Stream.FramesPerSecond, forceBitSetting: forceBitSetting).ToArray(); + + + args.Logger?.ILog("Encoding Parameters: " + + string.Join(" ", encodingParameters.Select(x => x.Contains(" ") ? "\"" + x + "\"" : x))); + stream.EncodingParameters.AddRange(encodingParameters); + stream.Codec = "hevc"; + } + else if (Codec is CODEC_AV1 or CODEC_AV1_10BIT) + { + bool tenBit = Codec == CODEC_AV1_10BIT || stream.Stream.Is10Bit; + args.Logger?.ILog("10 Bit: " + tenBit); + var encodingParameters = AV1(args, tenBit, Bitrate, encoder).ToArray(); + + args.Logger?.ILog("Encoding Parameters: " + + string.Join(" ", encodingParameters.Select(x => x.Contains(" ") ? "\"" + x + "\"" : x))); + stream.EncodingParameters.AddRange(encodingParameters); + stream.Codec = "av1"; + } + else if (Codec == CODEC_VP9) + { + var encodingParameters = VP9(args, Bitrate, encoder).ToArray(); + + args.Logger?.ILog("Encoding Parameters: " + + string.Join(" ", encodingParameters.Select(x => x.Contains(" ") ? "\"" + x + "\"" : x))); + stream.EncodingParameters.AddRange(encodingParameters); + stream.Codec = "vp9"; + } + else + { + args.Logger?.ILog("Unknown codec: " + Codec); + return 2; + } + + stream.ForcedChange = true; + return 1; + } + + /// + /// Adjust the parameters to use a constant bitrate + /// + /// the node parameters + /// the parameters to alter + /// the adjusted parmaters + private string[] AdjustForBitrate(NodeParameters args, string[] parameters) + { + var toRemove = new [] { "-rc", "-qp", "-preset", "-spatial-aq", "-g", "-global_quality:v" }; + int index = Array.FindIndex(parameters, p => toRemove.Contains(p)); + var modified = new List(); + for (int i = 0; i < parameters.Length - 1; i++) + { + if (toRemove.Contains(parameters[i])) + { + i++; + continue; + } + modified.Add(parameters[i]); + } + modified.Insert(index, "-b:v:{index}"); + modified.Insert(index + 1, Bitrate + "k"); + return modified.ToArray(); + } + + internal static IEnumerable GetEncodingParameters(NodeParameters args, string codec, int bitrate, string encoder, float fps) + { + if (codec == CODEC_H264) + return H264(args, false, encoder, bitrate).Select(x => x.Replace("{index}", "0")); + if (codec == CODEC_H265 || codec == CODEC_H265_10BIT) + return H265(null, args, codec == CODEC_H265_10BIT, bitrate, encoder, fps).Select(x => x.Replace("{index}", "0")); + if(codec == CODEC_AV1) + return AV1(args, codec == CODEC_AV1_10BIT, bitrate, encoder).Select(x => x.Replace("{index}", "0")); + + throw new Exception("Unsupported codec: " + codec); + } + + private static readonly bool IsMac = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + + private static IEnumerable H264(NodeParameters args, bool tenBit, string encoder, int bitrate) + { + List parameters = new List(); + string[] bit10Filters = null; + string[] non10BitFilters = null; + if (encoder == ENCODER_CPU) + parameters.AddRange(H26x_CPU(false, bitrate, out bit10Filters)); + else if(IsMac && encoder == ENCODER_MAC) + parameters.AddRange(H26x_VideoToolbox(false, bitrate)); + else if (encoder == ENCODER_NVIDIA) + parameters.AddRange(H26x_Nvidia(false, bitrate, out non10BitFilters)); + else if (encoder == ENCODER_QSV) + parameters.AddRange(H26x_Qsv(false, bitrate, 0)); + else if (encoder == ENCODER_AMF) + parameters.AddRange(H26x_Amd(false, bitrate)); + else if (encoder == ENCODER_VAAPI) + parameters.AddRange(H26x_Vaapi(false, bitrate)); + else if(IsMac && CanUseHardwareEncoding.CanProcess_VideoToolbox_H264(args)) + parameters.AddRange(H26x_VideoToolbox(false, bitrate)); + else if (CanUseHardwareEncoding.CanProcess_Nvidia_H264(args)) + parameters.AddRange(H26x_Nvidia(false,bitrate, out non10BitFilters)); + else if (CanUseHardwareEncoding.CanProcess_Qsv_H264(args)) + { + parameters.AddRange(H26x_Qsv(false, bitrate, 0)); + encoder = ENCODER_QSV; + } + else if (CanUseHardwareEncoding.CanProcess_Amd_H264(args)) + parameters.AddRange(H26x_Amd(false, bitrate)); + else if (CanUseHardwareEncoding.CanProcess_Vaapi_H264(args)) + parameters.AddRange(H26x_Vaapi(false, bitrate)); + else + parameters.AddRange(H26x_CPU(false, bitrate, out bit10Filters)); + + if (tenBit) + { + parameters.AddRange(bit10Filters ?? new string[] + { "-pix_fmt:v:{index}", "p010le", "-profile:v:{index}", "main10" }); + } + + return parameters; + } + + private static IEnumerable H265(FfmpegVideoStream stream, NodeParameters args, bool tenBit, int bitrate, + string encoder, float fps, bool forceBitSetting = false) + { + // hevc_qsv -load_plugin hevc_hw -pix_fmt p010le -profile:v main10 -global_quality 21 -g 24 -look_ahead 1 -look_ahead_depth 60 + List parameters = new List(); + string[] bit10Filters = null; + string[] non10BitFilters = null; + bool qsv = false; + if (encoder == ENCODER_CPU) + parameters.AddRange(H26x_CPU(true, bitrate, out bit10Filters)); + else if (IsMac && encoder == ENCODER_MAC) + parameters.AddRange(H26x_VideoToolbox(true, bitrate)); + else if (encoder == ENCODER_NVIDIA) + parameters.AddRange(H26x_Nvidia(true, bitrate, out non10BitFilters)); + else if (encoder == ENCODER_QSV) + { + parameters.AddRange(H26x_Qsv(true, bitrate, fps)); + qsv = true; + } + else if (encoder == ENCODER_AMF) + parameters.AddRange(H26x_Amd(true, bitrate)); + else if (encoder == ENCODER_VAAPI) + parameters.AddRange(H26x_Vaapi(true, bitrate)); + + else if (IsMac && CanUseHardwareEncoding.CanProcess_VideoToolbox_Hevc(args)) + parameters.AddRange(H26x_VideoToolbox(true, bitrate)); + else if (CanUseHardwareEncoding.CanProcess_Nvidia_Hevc(args)) + parameters.AddRange(H26x_Nvidia(true, bitrate, out non10BitFilters)); + else if (CanUseHardwareEncoding.CanProcess_Qsv_Hevc(args)) + { + parameters.AddRange(H26x_Qsv(true, bitrate, fps)); + qsv = true; + } + else if (CanUseHardwareEncoding.CanProcess_Amd_Hevc(args)) + parameters.AddRange(H26x_Amd(true, bitrate)); + else if (CanUseHardwareEncoding.CanProcess_Vaapi_Hevc(args)) + parameters.AddRange(H26x_Vaapi(true, bitrate)); + else + parameters.AddRange(H26x_CPU(true, bitrate, out bit10Filters)); + + if (tenBit) + { + if (qsv) + { + parameters.AddRange(new [] + { + "-profile:v:{index}", "main10", "-pix_fmt", "p010le" //, "-vf", "scale_qsv=format=p010le" + }); + // if the stream is passed in, we add it to the filter + // if(stream != null) + // stream.Filter.Add("scale_qsv=format=p010le"); + // else // if there is no stream, we specify the -vf directly, this is called by audio to video flow elements + // parameters.AddRange(new [] { "-vf", "scale_qsv=format=p010le" }); + + } + else + { + parameters.AddRange(bit10Filters ?? new [] + { "-pix_fmt:v:{index}", "p010le", "-profile:v:{index}", "main10" }); + } + } + else if(non10BitFilters?.Any() == true) + parameters.AddRange(non10BitFilters); + else if (forceBitSetting) + { + parameters.AddRange(new[] { "-pix_fmt:v:{index}", "yuv420p" }); + } + return parameters; + } + + + private static IEnumerable AV1(NodeParameters args, bool tenBit, int bitrate, string encoder) + { + // hevc_qsv -load_plugin hevc_hw -pix_fmt p010le -profile:v main10 -global_quality 21 -g 24 -look_ahead 1 -look_ahead_depth 60 + List parameters = new List(); + + if (encoder == ENCODER_CPU) + parameters.AddRange(AV1_CPU(bitrate)); + else if(encoder == ENCODER_NVIDIA) + parameters.AddRange(AV1_Nvidia(bitrate)); + else if(encoder == ENCODER_QSV) + parameters.AddRange(AV1_Qsv(bitrate)); + else if(encoder == ENCODER_AMF) + parameters.AddRange(AV1_Amd(bitrate)); + + else if (CanUseHardwareEncoding.CanProcess_Nvidia_AV1(args)) + parameters.AddRange(AV1_Nvidia(bitrate)); + else if (CanUseHardwareEncoding.CanProcess_Qsv_AV1(args)) + parameters.AddRange(AV1_Qsv(bitrate)); + else if (CanUseHardwareEncoding.CanProcess_Amd_AV1(args)) + parameters.AddRange(AV1_Amd(bitrate)); + else + parameters.AddRange(AV1_CPU(bitrate)); + + if (tenBit) + { + bool qsv = parameters.Any(x => x.ToLowerInvariant().Contains("qsv")); + bool av1 = parameters.Any(x => x.ToLowerInvariant().Contains("av1")); + if(qsv && av1) + parameters.AddRange(new[] { "-pix_fmt", "p010le" }); + else if(qsv) + parameters.AddRange(new[] { "-vf", "scale_qsv=format=p010le" }); + else + parameters.AddRange(new[] { "-pix_fmt:v:{index}", "yuv420p10le" }); + } + + return parameters; + } + + private static IEnumerable VP9(NodeParameters args, int bitrate, string encoder) + { + List parameters = new List(); + if (encoder == ENCODER_CPU) + parameters.AddRange(VP9_CPU(bitrate)); + else if (encoder == ENCODER_QSV) // if can use hevc they can use vp9 + parameters.AddRange(VP9_Qsv(bitrate)); + else if (CanUseHardwareEncoding.CanProcess_Qsv_Hevc(args)) // if can use hevc they can use vp9 + parameters.AddRange(VP9_Qsv(bitrate)); + else + parameters.AddRange(VP9_CPU(bitrate)); + return parameters; + } +} diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/VP9.cs b/VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/VP9.cs new file mode 100644 index 00000000..1f8506bf --- /dev/null +++ b/VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/VP9.cs @@ -0,0 +1,40 @@ +namespace FileFlows.VideoNodes.FfmpegBuilderNodes; + +public partial class FfmpegBuilderVideoBitrateEncode +{ + /// + /// Gets FFmpeg arguments for encoding VP9 using the CPU + /// + /// the bitrate in Kbps + /// the FFmpeg arguments + private static IEnumerable VP9_CPU(int bitrate) + { + return new [] + { + "libvpx-vp9", + "-b:v:{index}", bitrate + "k", + "-minrate", bitrate + "k", + "-maxrate", bitrate + "k", + "-bufsize", (bitrate * 2) + "k" + }; + } + + /// + /// Gets FFmpeg arguments for encoding VP9 using the Intel's QSV hardware encoder + /// + /// the bitrate in Kbps + /// the FFmpeg arguments + private static IEnumerable VP9_Qsv(int bitrate) + { + var parameters = new List(); + parameters.AddRange(new[] + { + "vp9_qsv", + "-b:v:{index}", bitrate + "k", + "-minrate", bitrate + "k", + "-maxrate", bitrate + "k", + "-bufsize", (bitrate * 2) + "k" + }); + return parameters; + } +} \ No newline at end of file diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/h26x.cs b/VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/h26x.cs new file mode 100644 index 00000000..d03bb61e --- /dev/null +++ b/VideoNodes/FfmpegBuilderNodes/Video/FFmpegBuilderVideoBitrateEncode/h26x.cs @@ -0,0 +1,106 @@ +using System.Globalization; + +namespace FileFlows.VideoNodes.FfmpegBuilderNodes; + +public partial class FfmpegBuilderVideoBitrateEncode +{ + + private static IEnumerable H26x_CPU(bool h265, int bitrate, out string[] bit10Filters) + { + bit10Filters = new[] + { + "-pix_fmt:v:{index}", "yuv420p10le", "-profile:v:{index}", "main10" + }; + return new [] + { + h265 ? "libx265" : "libx264", + "-b:v:{index}", bitrate + "k", + "-minrate", bitrate + "k", + "-maxrate", bitrate + "k", + "-bufsize", (bitrate * 2) + "k" + }; + } + + private static IEnumerable H26x_Nvidia(bool h265, int bitrate, out string[] non10BitFilters) + { + if (h265 == false) + non10BitFilters = new[] { "-pix_fmt:v:{index}", "yuv420p" }; + else + non10BitFilters = null; + + return new [] + { + h265 ? "hevc_nvenc" : "h264_nvenc", + "-b:v:{index}", bitrate + "k", + "-minrate", bitrate + "k", + "-maxrate", bitrate + "k", + "-bufsize", (bitrate * 2) + "k" + }; + } + + private static IEnumerable H26x_Qsv(bool h265, int bitrate, float fps) + { + //hevc_qsv -load_plugin hevc_hw -pix_fmt p010le -profile:v main10 -global_quality 21 -g 24 -look_ahead 1 -look_ahead_depth 60 + var parameters = new List(); + if (h265) + { + parameters.AddRange(new[] + { + "hevc_qsv", + "-load_plugin", "hevc_hw", + "-g", (fps < 1 ? 29.97 : fps).ToString(CultureInfo.InvariantCulture) + }); + + } + else + { + parameters.AddRange(new[] + { + "h264_qsv" + }); + + } + parameters.AddRange(new[] + { + "-b:v:{index}", bitrate + "k", + "-minrate", bitrate + "k", + "-maxrate", bitrate + "k", + "-bufsize", (bitrate * 2) + "k" + }); + return parameters; + } + + private static IEnumerable H26x_Amd(bool h265, int bitrate) + { + return new[] + { + h265 ? "hevc_amf" : "h264_amf", + "-b:v:{index}", bitrate + "k", + "-minrate", bitrate + "k", + "-maxrate", bitrate + "k", + "-bufsize", (bitrate * 2) + "k" + }; + } + private static IEnumerable H26x_Vaapi(bool h265, int bitrate) + { + return new[] + { + h265 ? "hevc_vaapi" : "h264_vaapi", + "-b:v:{index}", bitrate + "k", + "-minrate", bitrate + "k", + "-maxrate", bitrate + "k", + "-bufsize", (bitrate * 2) + "k" + }; + } + private static IEnumerable H26x_VideoToolbox(bool h265, int bitrate) + { + return new[] + { + h265 ? "hevc_videotoolbox" : "h264_videotoolbox", + "-b:v:{index}", bitrate + "k", + "-minrate", bitrate + "k", + "-maxrate", bitrate + "k", + "-bufsize", (bitrate * 2) + "k" + }; + } +} \ No newline at end of file diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoBitrate.cs b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoBitrate.cs index 3ee39155..323c21a4 100644 --- a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoBitrate.cs +++ b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoBitrate.cs @@ -25,7 +25,7 @@ public class FfmpegBuilderVideoBitrate : FfmpegBuilderNode } if(Bitrate < 0) { - args.Logger?.ELog("Minimum birate not set"); + args.Logger?.ELog("Minimum bitrate not set"); return -1; } float currentBitrate = (int)(video.Stream.Bitrate / 1000f); diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoEncode/FfmpegBuilderVideoEncode.cs b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoEncode/FfmpegBuilderVideoEncode.cs index 28268b53..e1a411ba 100644 --- a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoEncode/FfmpegBuilderVideoEncode.cs +++ b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderVideoEncode/FfmpegBuilderVideoEncode.cs @@ -6,27 +6,13 @@ namespace FileFlows.VideoNodes.FfmpegBuilderNodes; /// /// Set a video codec encoding for a video stream based on users settings /// -public partial class FfmpegBuilderVideoEncode:FfmpegBuilderNode +public partial class FfmpegBuilderVideoEncode:VideoEncodeBase { /// /// The number of outputs for this flow element /// public override int Outputs => 1; - internal const string CODEC_H264 = "h264"; - internal const string CODEC_H265 = "h265"; - internal const string CODEC_H265_8BIT = "h265 8BIT"; - internal const string CODEC_H265_10BIT = "h265 10BIT"; - internal const string CODEC_AV1 = "av1"; - internal const string CODEC_AV1_10BIT = "av1 10BIT"; - internal const string CODEC_VP9 = "vp9"; - - internal const string ENCODER_CPU = "CPU"; - internal const string ENCODER_NVIDIA = "NVIDIA"; - internal const string ENCODER_QSV = "Intel QSV"; - internal const string ENCODER_VAAPI = "VAAPI"; - internal const string ENCODER_AMF = "AMD AMF"; - internal const string ENCODER_MAC = "Mac Video Toolbox"; /// /// The Help URL for this flow element @@ -45,65 +31,12 @@ public partial class FfmpegBuilderVideoEncode:FfmpegBuilderNode [ChangeValue(nameof(Quality), 28, CODEC_VP9)] [Select(nameof(CodecOptions), 1)] public string Codec { get; set; } - - private static List _CodecOptions; - /// - /// Gets or sets the codec options - /// - public static List CodecOptions - { - get - { - if (_CodecOptions == null) - { - _CodecOptions = new List - { - new () { Label = "H.264", Value = CODEC_H264 }, - // new () { Label = "H.264 (10-Bit)", Value = CODEC_H264_10BIT }, - new () { Label = "HEVC (Automatic)", Value = CODEC_H265 }, - new () { Label = "HEVC (8-Bit)", Value = CODEC_H265_8BIT }, - new () { Label = "HEVC (10-Bit)", Value = CODEC_H265_10BIT }, - new () { Label = "AV1", Value = CODEC_AV1 }, - new () { Label = "AV1 (10-Bit)", Value = CODEC_AV1_10BIT }, - new () { Label = "VP9", Value = CODEC_VP9 }, - }; - } - return _CodecOptions; - } - } - /// /// Gets or sets the encoder to use /// [Select(nameof(Encoders), 2)] [ConditionEquals(nameof(Codec), "/av1/", inverse: true)] public string Encoder { get; set; } - - private static List _Encoders; - /// - /// Gets or sets the encoders options - /// - public static List Encoders - { - get - { - if (_Encoders == null) - { - _Encoders = new List - { - new () { Label = "Automatic", Value = "" }, - new () { Label = "CPU", Value = "CPU" }, - new () { Label = "NVIDIA", Value = "NVIDIA" }, - new () { Label = "Intel QSV", Value = "Intel QSV" }, - new () { Label = "VAAPI", Value = "VAAPI" }, - new () { Label = "AMD AMF", Value = "AMD AMF" }, - new () { Label = "Mac Video Toolbox", Value = "Mac Video Toolbox" }, - }; - } - return _Encoders; - } - } - /// /// Gets or sets the quality of the video encode /// diff --git a/VideoNodes/FfmpegBuilderNodes/Video/_VideoEncodeBase.cs b/VideoNodes/FfmpegBuilderNodes/Video/_VideoEncodeBase.cs new file mode 100644 index 00000000..c012cc75 --- /dev/null +++ b/VideoNodes/FfmpegBuilderNodes/Video/_VideoEncodeBase.cs @@ -0,0 +1,78 @@ +namespace FileFlows.VideoNodes.FfmpegBuilderNodes; + +/// +/// Base for the common Video Encode flow elements +/// +public abstract class VideoEncodeBase : FfmpegBuilderNode +{ + + internal const string CODEC_H264 = "h264"; + internal const string CODEC_H265 = "h265"; + internal const string CODEC_H265_8BIT = "h265 8BIT"; + internal const string CODEC_H265_10BIT = "h265 10BIT"; + internal const string CODEC_AV1 = "av1"; + internal const string CODEC_AV1_10BIT = "av1 10BIT"; + internal const string CODEC_VP9 = "vp9"; + + internal const string ENCODER_CPU = "CPU"; + internal const string ENCODER_NVIDIA = "NVIDIA"; + internal const string ENCODER_QSV = "Intel QSV"; + internal const string ENCODER_VAAPI = "VAAPI"; + internal const string ENCODER_AMF = "AMD AMF"; + internal const string ENCODER_MAC = "Mac Video Toolbox"; + + + + private static List _CodecOptions; + /// + /// Gets or sets the codec options + /// + public static List CodecOptions + { + get + { + if (_CodecOptions == null) + { + _CodecOptions = new List + { + new () { Label = "H.264", Value = CODEC_H264 }, + // new () { Label = "H.264 (10-Bit)", Value = CODEC_H264_10BIT }, + new () { Label = "HEVC (Automatic)", Value = CODEC_H265 }, + new () { Label = "HEVC (8-Bit)", Value = CODEC_H265_8BIT }, + new () { Label = "HEVC (10-Bit)", Value = CODEC_H265_10BIT }, + new () { Label = "AV1", Value = CODEC_AV1 }, + new () { Label = "AV1 (10-Bit)", Value = CODEC_AV1_10BIT }, + new () { Label = "VP9", Value = CODEC_VP9 }, + }; + } + return _CodecOptions; + } + } + + + private static List _Encoders; + /// + /// Gets or sets the encoders options + /// + public static List Encoders + { + get + { + if (_Encoders == null) + { + _Encoders = new List + { + new () { Label = "Automatic", Value = "" }, + new () { Label = "CPU", Value = "CPU" }, + new () { Label = "NVIDIA", Value = "NVIDIA" }, + new () { Label = "Intel QSV", Value = "Intel QSV" }, + new () { Label = "VAAPI", Value = "VAAPI" }, + new () { Label = "AMD AMF", Value = "AMD AMF" }, + new () { Label = "Mac Video Toolbox", Value = "Mac Video Toolbox" }, + }; + } + return _Encoders; + } + } + +} \ No newline at end of file diff --git a/VideoNodes/i18n/en.json b/VideoNodes/i18n/en.json index 6a73caaf..1c33f05c 100644 --- a/VideoNodes/i18n/en.json +++ b/VideoNodes/i18n/en.json @@ -703,6 +703,22 @@ "Quality-Suffix": "(Higher Quality, larger file)" } }, + "FfmpegBuilderVideoBitrateEncode": { + "Label": "FFMPEG Builder: Bitrate Encode", + "Description": "Sets the FFMPEG Builder to encode the video with simple to use presets based on Bitrate", + "Outputs": { + "1": "FFMPEG Builder video streams set to encode" + }, + "Fields": { + "Codec": "Codec", + "Bitrate": "Bitrate", + "Bitrate-Suffix": "Kbps", + "Encoder": "Encoder", + "Encoder-Help": "The encoder to force to use. If you specify a hardware encoder that is unavailable, the encode will fail.", + "HardwareEncoding": "Hardware Encode", + "HardwareEncoding-Help": "When checked, will test to see if hardware encoders are found on the Processing Node, and if found will use hardware encoding, otherwise will fallback to CPU encoding." + } + }, "FfmpegBuilderVideoTag": { "Label": "FFMPEG Builder: Video Tag", "Description": "Sets a video tag on the file",