using FileFlows.VideoNodes.Helpers; namespace FileFlows.VideoNodes; /// /// Node for checking if Flow Runner has access to hardware /// public class CanUseHardwareEncoding:Node { public override int Inputs => 1; public override int Outputs => 2; public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/logical-nodes/can-use-hardware-encoding"; public override string Icon => "fas fa-eye"; public override FlowElementType Type => FlowElementType.Logic; [Select(nameof(EncoderOptions), 1)] public HardwareEncoder Encoder { get; set; } private static List _EncoderOptions; public static List EncoderOptions { get { if (_EncoderOptions == null) { _EncoderOptions = new List { new ListOption { Label = "NVIDIA", Value = "###GROUP###" }, new ListOption { Label = "NVIDIA H.264", Value = HardwareEncoder.Nvidia_H264 }, new ListOption { Label = "NVIDIA H.265", Value = HardwareEncoder.Nvidia_Hevc }, new ListOption { Label = "NVIDIA AV1", Value = HardwareEncoder.Nvidia_Hevc }, new ListOption { Label = "AMD", Value = "###GROUP###" }, new ListOption { Label = "AMD H.264", Value = HardwareEncoder.Amd_H264 }, new ListOption { Label = "AMD H.265", Value = HardwareEncoder.Amd_Hevc }, new ListOption { Label = "Intel QSV", Value = "###GROUP###" }, new ListOption { Label = "Intel QSV H.264", Value = HardwareEncoder.Qsv_H264 }, new ListOption { Label = "Intel QSV H.265", Value = HardwareEncoder.Qsv_Hevc }, new ListOption { Label = "VAAPI", Value = "###GROUP###" }, new ListOption { Label = "VAAPI H.264", Value = HardwareEncoder.Vaapi_H264 }, new ListOption { Label = "VAAPI H.265", Value = HardwareEncoder.Vaapi_Hevc }, new ListOption { Label = "VideoToolbox (MacOS)", Value = "###GROUP###" }, new ListOption { Label = "VideoToolbox H.264", Value = HardwareEncoder.VideoToolbox_H264}, new ListOption { Label = "VideoToolbox H.265", Value = HardwareEncoder.VideoToolbox_Hevc }, }; } return _EncoderOptions; } } public enum HardwareEncoder { Nvidia_H264 = 1, Amd_H264 = 2, Qsv_H264 = 3, Vaapi_H264 = 4, VideoToolbox_H264 = 5, Nvidia_Hevc = 11, Amd_Hevc = 12, Qsv_Hevc = 13, Vaapi_Hevc = 14, VideoToolbox_Hevc = 15, Nvidia_AV1 = 21, Amd_AV1 = 22, Qsv_AV1 = 23, } public override int Execute(NodeParameters args) { bool canProcess = false; switch (Encoder) { case HardwareEncoder.Nvidia_H264: canProcess = CanProcess_Nvidia_H264(args); break; case HardwareEncoder.Nvidia_Hevc: canProcess = CanProcess_Nvidia_Hevc(args); break; case HardwareEncoder.Nvidia_AV1: canProcess = CanProcess_Nvidia_AV1(args); break; case HardwareEncoder.Amd_H264: canProcess = CanProcess_Amd_H264(args); break; case HardwareEncoder.Amd_Hevc: canProcess = CanProcess_Amd_Hevc(args); break; case HardwareEncoder.Amd_AV1: canProcess = CanProcess_Amd_AV1(args); break; case HardwareEncoder.Qsv_H264: canProcess = CanProcess_Qsv_H264(args); break; case HardwareEncoder.Qsv_Hevc: canProcess = CanProcess_Qsv_Hevc(args); break; case HardwareEncoder.Vaapi_H264: canProcess = CanProcess_Vaapi_H264(args); break; case HardwareEncoder.Vaapi_Hevc: canProcess = CanProcess_Vaapi_Hevc(args); break; case HardwareEncoder.VideoToolbox_H264: canProcess = CanProcess_VideoToolbox_H264(args); break; case HardwareEncoder.VideoToolbox_Hevc: canProcess = CanProcess_VideoToolbox_Hevc(args); break; } return canProcess ? 1 : 2; } /// /// Checks if this flow runner can use NVIDIA HEVC encoder /// /// the node parameters /// true if can use it, otherwise false internal static bool CanProcess_Nvidia_Hevc(NodeParameters args) => CanProcess(args, "hevc_nvenc"); /// /// Checks if this flow runner can use NVIDIA AV1 encoder /// /// the node parameters /// true if can use it, otherwise false internal static bool CanProcess_Nvidia_AV1(NodeParameters args) => CanProcess(args, "av1_nvenc"); /// /// Checks if this flow runner can use QSV AV1 encoder /// /// the node parameters /// true if can use it, otherwise false internal static bool CanProcess_Qsv_AV1(NodeParameters args) => CanProcess(args, "av1_qsv"); /// /// Checks if this flow runner can use NVIDIA H.264 encoder /// /// the node parameters /// true if can use it, otherwise false internal static bool CanProcess_Nvidia_H264(NodeParameters args) => CanProcess(args, "h264_nvenc"); /// /// Checks if this flow runner can use VideoToolbox HEVC encoder /// /// the node parameters /// true if can use it, otherwise false internal static bool CanProcess_VideoToolbox_Hevc(NodeParameters args) => CanProcess(args, "hevc_videotoolbox"); /// /// Checks if this flow runner can use VideoToolbox H.264 encoder /// /// the node parameters /// true if can use it, otherwise false internal static bool CanProcess_VideoToolbox_H264(NodeParameters args) => CanProcess(args, "h264_videotoolbox"); /// /// Checks if this flow runner can use AND AV1 encoder /// /// the node parameters /// true if can use it, otherwise false internal static bool CanProcess_Amd_AV1(NodeParameters args) => CanProcess(args, "hevc_av1"); /// /// Checks if this flow runner can use AND HEVC encoder /// /// the node parameters /// true if can use it, otherwise false internal static bool CanProcess_Amd_Hevc(NodeParameters args) => CanProcess(args, "hevc_amf"); /// /// Checks if this flow runner can use AND H.264 encoder /// /// the node parameters /// true if can use it, otherwise false internal static bool CanProcess_Amd_H264(NodeParameters args) => CanProcess(args, "h264_amf"); /// /// Checks if this flow runner can use Intels QSV HEVC encoder /// /// the node parameters /// true if can use it, otherwise false internal static bool CanProcess_Qsv_Hevc(NodeParameters args) => CanProcess(args, "hevc_qsv", "-global_quality", "28", "-load_plugin", "hevc_hw"); /// /// Checks if this flow runner can use Intels QSV H.264 encoder /// /// the node parameters /// true if can use it, otherwise false internal static bool CanProcess_Qsv_H264(NodeParameters args) => CanProcess(args, "h264_qsv"); /// /// Checks if this flow runner can use VAAPI HEVC encoder /// /// the node parameters /// true if can use it, otherwise false internal static bool CanProcess_Vaapi_Hevc(NodeParameters args) => CanProcess(args, "hevc_vaapi"); /// /// Checks if this flow runner can use VAAPI H.264 encoder /// /// the node parameters /// true if can use it, otherwise false internal static bool CanProcess_Vaapi_H264(NodeParameters args) => CanProcess(args, "h264_vaapi"); /// /// Gets if a encoder/decoder has been disabled by a variable /// /// the node parameters /// the parameters to check /// if a encoder/decoder has been disabled by a variable internal static bool DisabledByVariables(NodeParameters args, string[] parameters) { if (parameters.Any(x => x.ToLower().Contains("nvenc"))) { if (args.Variables.FirstOrDefault(x => x.Key.ToLowerInvariant() == "nonvidia").Value as bool? == true) return true; } else if (parameters.Any(x => x.ToLower().Contains("qsv"))) { if (args.Variables.FirstOrDefault(x => x.Key.ToLowerInvariant() == "noqsv").Value as bool? == true) return true; } else if (parameters.Any(x => x.ToLower().Contains("vaapi"))) { if (args.Variables.FirstOrDefault(x => x.Key.ToLowerInvariant() == "novaapi").Value as bool? == true) return true; } else if (parameters.Any(x => x.ToLower().Contains("amf"))) { if (args.Variables.FirstOrDefault(x => x.Key.ToLowerInvariant() == "noamf" || x.Key.ToLowerInvariant() == "noamd").Value as bool? == true) return true; } else if (parameters.Any(x => x.ToLower().Contains("videotoolbox"))) { if (args.Variables.FirstOrDefault(x => x.Key.ToLowerInvariant() == "novideotoolbox").Value as bool? == true) return true; } return false; } private static bool CanProcess(NodeParameters args, params string[] encodingParams) { if (encodingParams.Any(x => x.Contains("vaapi") && OperatingSystem.IsWindows())) return false; // dont bother checking vaapi on windows, unlikely the user has added support for it if (DisabledByVariables(args, encodingParams)) return false; string ffmpeg = args.GetToolPath("FFmpeg"); if (string.IsNullOrEmpty(ffmpeg)) { args.Logger.ELog("FFmpeg tool not found."); return false; } return CanProcess(args, ffmpeg, encodingParams); } /// /// Tests if the encoding parameters can be executed /// /// the node paramterse /// the location of ffmpeg /// the encoding parameter to test /// true if can be processed internal static bool CanProcess(NodeParameters args, string ffmpeg, string[] encodingParams) { bool can = CanExecute(); if (can == false && encodingParams?.Contains("amf") == true) { // AMD/AMF has a issue where it reports false at first but then passes // https://github.com/revenz/FileFlows/issues/106 Thread.Sleep(2000); can = CanExecute(); } return can; bool CanExecute() { bool vaapi = encodingParams.Any(x => x.Contains("vaapi")) && VaapiHelper.VaapiLinux; List arguments = encodingParams.ToList(); if (vaapi) arguments.AddRange(new [] { "-vf", "format=nv12,hwupload", "-strict", "-2"}); arguments.InsertRange(0, new [] { "-loglevel", "error", "-f", "lavfi", "-i", "color=black:s=1080x1080", "-vframes", "1", "-an", "-c:v" }); if (vaapi) { arguments.InsertRange(0, new[] { "-fflags", "+genpts", "-vaapi_device", VaapiHelper.VaapiRenderDevice }); arguments.Add(FileHelper.Combine(args.TempPath, Guid.NewGuid() + ".mkv")); } else { arguments.AddRange(new [] { "-f", "null", "-" }); } var cmd = args.Process.ExecuteShellCommand(new ExecuteArgs { Command = ffmpeg, ArgumentList = arguments.ToArray(), Silent = true }).Result; string? output = cmd.Output?.Contains("va_openDriver() returns 0") == true ? null : cmd.Output; if (cmd.ExitCode != 0 || string.IsNullOrWhiteSpace(output) == false) { string asStr = string.Join(" ", arguments.Select(x => x.Contains(" ") ? "\"" + x + "\"" : x)); args.Logger?.WLog($"Cant process '{ffmpeg} {asStr}': {cmd.Output ?? ""}"); return false; } return true; } } }