diff --git a/BasicNodes/Tools/Executor.cs b/BasicNodes/Tools/Executor.cs index ffe7ea3d..92fdc937 100644 --- a/BasicNodes/Tools/Executor.cs +++ b/BasicNodes/Tools/Executor.cs @@ -51,12 +51,14 @@ public class Executor : Node private NodeParameters args; - /// - public override Task Cancel() - { - args?.Process?.Cancel(); - return Task.CompletedTask; - } + // /// + // public override Task Cancel() + // { + // if (args.Process is ProcessHelper process) + // process.Kill(); + // + // return Task.CompletedTask; + // } /// public override int Execute(NodeParameters args) diff --git a/FileFlows.Common.dll b/FileFlows.Common.dll index 3025f7ac..bb13948a 100644 Binary files a/FileFlows.Common.dll and b/FileFlows.Common.dll differ diff --git a/FileFlows.Common.pdb b/FileFlows.Common.pdb index 4b6ca13b..1989cec1 100644 Binary files a/FileFlows.Common.pdb and b/FileFlows.Common.pdb differ diff --git a/FileFlows.Plugin.dll b/FileFlows.Plugin.dll index 69202592..c1c78199 100644 Binary files a/FileFlows.Plugin.dll and b/FileFlows.Plugin.dll differ diff --git a/FileFlows.Plugin.pdb b/FileFlows.Plugin.pdb index c26bd11e..cf6c9f24 100644 Binary files a/FileFlows.Plugin.pdb and b/FileFlows.Plugin.pdb differ diff --git a/VideoNodes/FFMpegEncoder.cs b/VideoNodes/FFMpegEncoder.cs index d451fe59..5f602647 100644 --- a/VideoNodes/FFMpegEncoder.cs +++ b/VideoNodes/FFMpegEncoder.cs @@ -23,11 +23,17 @@ public class FFMpegEncoder private Process process; DateTime startedAt; private string? AbortReason; + private CancellationToken _cancellationToken; + private ProcessHelper _processHelper; - public FFMpegEncoder(string ffMpegExe, ILogger logger) + public FFMpegEncoder(string ffMpegExe, ILogger logger, CancellationToken cancellationToken) { this.ffMpegExe = ffMpegExe; this.Logger = logger; + _processHelper = new(logger, cancellationToken, false); + _processHelper.OnStandardOutputReceived += OnOutputDataReceived; + _processHelper.OnErrorOutputReceived += OnErrorDataReceived; + _cancellationToken = cancellationToken; } /// @@ -99,20 +105,13 @@ public class FFMpegEncoder { try { - if (this.process != null) - { - this.process.Kill(); - this.process = null; - } - + _processHelper.Kill(); } catch (Exception) { } } public async Task ExecuteShellCommand(string command, List arguments, int timeout = 0) { - var result = new ProcessResult(); - var hwDecoderIndex = arguments.FindIndex(x => x == "-hwaccel"); string? decoder = null; if (hwDecoderIndex >= 0 && hwDecoderIndex < arguments.Count - 2) @@ -151,18 +150,19 @@ public class FFMpegEncoder string? encoder = null; if (arguments.Any(x => - x.ToLowerInvariant().Contains("hevc_qsv") || x.ToLowerInvariant().Contains("h264_qsv") || - x.ToLowerInvariant().Contains("av1_qsv"))) + x.Contains("hevc_qsv", StringComparison.InvariantCultureIgnoreCase) || + x.Contains("h264_qsv", StringComparison.InvariantCultureIgnoreCase) || + x.Contains("av1_qsv", StringComparison.InvariantCultureIgnoreCase))) encoder = "QSV"; - else if (arguments.Any(x => x.ToLowerInvariant().Contains("_nvenc"))) + else if (arguments.Any(x => x.Contains("_nvenc", StringComparison.InvariantCultureIgnoreCase))) encoder = "NVIDIA"; - else if (arguments.Any(x => x.ToLowerInvariant().Contains("_amf"))) + else if (arguments.Any(x => x.Contains("_amf", StringComparison.InvariantCultureIgnoreCase))) encoder = "AMF"; - else if (arguments.Any(x => x.ToLowerInvariant().Contains("_vaapi"))) + else if (arguments.Any(x => x.Contains("_vaapi", StringComparison.InvariantCultureIgnoreCase))) encoder = "VAAPI"; - else if (arguments.Any(x => x.ToLowerInvariant().Contains("_videotoolbox"))) + else if (arguments.Any(x => x.Contains("_videotoolbox", StringComparison.InvariantCultureIgnoreCase))) encoder = "VideoToolbox"; - else if (arguments.Any(x => x.ToLowerInvariant().Contains("libx") || x.ToLowerInvariant().Contains("libvpx"))) + else if (arguments.Any(x => x.Contains("libx", StringComparison.InvariantCultureIgnoreCase) || x.Contains("libvpx", StringComparison.InvariantCultureIgnoreCase))) encoder = "CPU"; if (encoder != null) @@ -171,123 +171,52 @@ public class FFMpegEncoder OnStatChange?.Invoke("Encoder", encoder, recordStatistic: true); } - using (var process = new Process()) + var processHelper = new ProcessHelper(Logger, _cancellationToken, false); + processHelper.OnStandardOutputReceived += OnOutputDataReceived; + processHelper.OnErrorOutputReceived += OnErrorDataReceived; + var result = processHelper.ExecuteShellCommand(new() { - this.process = process; - - process.StartInfo.FileName = command; - if (arguments?.Any() == true) - { - foreach (string arg in arguments) - process.StartInfo.ArgumentList.Add(arg); - } - process.StartInfo.UseShellExecute = false; - process.StartInfo.RedirectStandardInput = true; - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.RedirectStandardError = true; - process.StartInfo.CreateNoWindow = true; - - outputBuilder = new StringBuilder(); - outputCloseEvent = new TaskCompletionSource(); - - process.OutputDataReceived += OnOutputDataReceived; - - errorBuilder = new StringBuilder(); - errorCloseEvent = new TaskCompletionSource(); - - process.ErrorDataReceived += OnErrorDataReceived; - - bool isStarted; - - startedAt = DateTime.Now; - try - { - isStarted = process.Start(); - } - catch (Exception error) - { - // Usually it occurs when an executable file is not found or is not executable - - result.Completed = true; - result.ExitCode = -1; - result.Output = error.Message; - - isStarted = false; - } - - if (isStarted) - { - // Reads the output stream first and then waits because deadlocks are possible - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - - // Creates task to wait for process exit using timeout - var waitForExit = WaitForExitAsync(process, timeout); - - // Create task to wait for process exit and closing all output streams - var processTask = Task.WhenAll(waitForExit, outputCloseEvent.Task, errorCloseEvent.Task); - - // Waits process completion and then checks it was not completed by timeout - if ( - ( - (timeout > 0 && await Task.WhenAny(Task.Delay(timeout), processTask) == processTask) || - (timeout == 0 && await Task.WhenAny(processTask) == processTask) - ) - && waitForExit.Result) - { - result.Completed = true; - result.ExitCode = process.ExitCode; - result.Output = $"{outputBuilder}{errorBuilder}"; - } - else - { - try - { - // Kill hung process - process.Kill(); - } - catch - { - } - } - } - } - process = null; - - if (this.AbortReason != null) - result.AbortReason = this.AbortReason; - - return result; + Command = command, + ArgumentList = arguments.ToArray() + }).Result; + + return new() + { + Completed = result.Completed, + ExitCode = result.ExitCode, + Output = result.StandardOutput, + AbortReason = AbortReason?.EmptyAsNull() + }; } - public void OnOutputDataReceived(object sender, DataReceivedEventArgs e) + private void OnOutputDataReceived(string data) { // The output stream has been closed i.e. the process has terminated - if (e.Data == null) + if (data == null) { outputCloseEvent.SetResult(true); } else { - CheckOutputLine(e.Data); + CheckOutputLine(data); } } - public void OnErrorDataReceived(object sender, DataReceivedEventArgs e) + private void OnErrorDataReceived(string data) { // The error stream has been closed i.e. the process has terminated - if (e.Data == null) + if (data == null) { errorCloseEvent.SetResult(true); } - else if (e.Data.ToLower().Contains("failed") || e.Data.Contains("No capable devices found") || e.Data.ToLower().Contains("error")) + else if (data.ToLower().Contains("failed") || data.Contains("No capable devices found") || data.ToLower().Contains("error")) { - Logger.ELog(e.Data); - errorBuilder.AppendLine(e.Data); + Logger.ELog(data); + errorBuilder.AppendLine(data); } else { - CheckOutputLine(e.Data); + CheckOutputLine(data); } } @@ -337,17 +266,6 @@ public class FFMpegEncoder outputBuilder.AppendLine(line); } - - private static Task WaitForExitAsync(Process process, int timeout) - { - if (timeout > 0) - return Task.Run(() => process.WaitForExit(timeout)); - return Task.Run(() => - { - process.WaitForExit(); - return Task.FromResult(true); - }); - } /// /// Represents the result of a process execution. /// diff --git a/VideoNodes/VideoNodes/EncodingNode.cs b/VideoNodes/VideoNodes/EncodingNode.cs index 1e444bd7..83f97367 100644 --- a/VideoNodes/VideoNodes/EncodingNode.cs +++ b/VideoNodes/VideoNodes/EncodingNode.cs @@ -63,7 +63,7 @@ namespace FileFlows.VideoNodes if (string.IsNullOrEmpty(extension)) extension = "mkv"; - Encoder = new FFMpegEncoder(ffmpegExe, args.Logger); + Encoder = new FFMpegEncoder(ffmpegExe, args.Logger, args.CancellationToken); Encoder.AtTime += AtTimeEvent; Encoder.OnStatChange += EncoderOnOnStatChange; @@ -151,13 +151,6 @@ namespace FileFlows.VideoNodes } } - public override Task Cancel() - { - if (Encoder != null) - Encoder.Cancel(); - return base.Cancel(); - } - void AtTimeEvent(TimeSpan time, DateTime startedAt) { if (TotalTime.TotalMilliseconds == 0) @@ -219,133 +212,6 @@ namespace FileFlows.VideoNodes } return vidparams; - - // no longer do this - //if (vidparams.ToLower().Contains("hevc_nvenc")) - //{ - // // nvidia h265 encoding, check can - // bool canProcess = CanUseHardwareEncoding.CanProcess(Args, ffmpeg, vidparams); - // if (canProcess == false) - // { - // // change to cpu encoding - // Args.Logger?.ILog("Can't encode using hevc_nvenc, falling back to CPU encoding H265 (libx265)"); - // return "libx265"; - // } - // return vidparams; - //} - //else if (vidparams.ToLower().Contains("h264_nvenc")) - //{ - // // nvidia h264 encoding, check can - // bool canProcess = CanUseHardwareEncoding.CanProcess(Args, ffmpeg, vidparams); - // if (canProcess == false) - // { - // // change to cpu encoding - // Args.Logger?.ILog("Can't encode using h264_nvenc, falling back to CPU encoding H264 (libx264)"); - // return "libx264"; - // } - // return vidparams; - //} - //else if (vidparams.ToLower().Contains("hevc_qsv")) - //{ - // // nvidia h265 encoding, check can - // bool canProcess = CanUseHardwareEncoding.CanProcess(Args, ffmpeg, vidparams); - // if (canProcess == false) - // { - // // change to cpu encoding - // Args.Logger?.ILog("Can't encode using hevc_qsv, falling back to CPU encoding H265 (libx265)"); - // return "libx265"; - // } - // return vidparams; - //} - //else if (vidparams.ToLower().Contains("h264_qsv")) - //{ - // // nvidia h264 encoding, check can - // bool canProcess = CanUseHardwareEncoding.CanProcess(Args, ffmpeg, vidparams); - // if (canProcess == false) - // { - // // change to cpu encoding - // Args.Logger?.ILog("Can't encode using h264_qsv, falling back to CPU encoding H264 (libx264)"); - // return "libx264"; - // } - // return vidparams; - //} - //return vidparams; - } - - public bool HasNvidiaCard(string ffmpeg) - { - try - { - if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) - { - var cmd = Args.Process.ExecuteShellCommand(new ExecuteArgs - { - Command = "wmic", - Arguments = "path win32_VideoController get name" - }).Result; - if (cmd.ExitCode == 0) - { - // it worked - if (cmd.Output?.ToLower()?.Contains("nvidia") == false) - return false; - } - } - else - { - // linux, crude method, look for nvidia in the /dev dir - var dir = new System.IO.DirectoryInfo("/dev"); - if (dir.Exists == false) - return false; - - bool dev = dir.GetDirectories().Any(x => x.Name.ToLower().Contains("nvidia")); - if (dev == false) - return false; - } - - // check cuda in ffmpeg itself - var result = Args.Process.ExecuteShellCommand(new ExecuteArgs - { - Command = ffmpeg, - Arguments = "-hide_banner -init_hw_device list" - }).Result; - return result.Output?.Contains("cuda") == true; - } - catch (Exception ex) - { - Args.Logger?.ELog("Failed to detect NVIDIA card: " + ex.Message + Environment.NewLine + ex.StackTrace); - return false; - } - } - - protected bool IsSameVideoCodec(string current, string wanted) - { - wanted = ReplaceCommon(wanted); - current = ReplaceCommon(current); - - return wanted == current; - - string ReplaceCommon(string input) - { - input = input.ToLower(); - input = Regex.Replace(input, "^(divx|xvid|m(-)?peg(-)4)$", "mpeg4", RegexOptions.IgnoreCase); - input = Regex.Replace(input, "^(hevc|h[\\.x\\-]?265)$", "h265", RegexOptions.IgnoreCase); - input = Regex.Replace(input, "^(h[\\.x\\-]?264)$", "h264", RegexOptions.IgnoreCase); - return input; - } - } - - protected bool SupportsSubtitles(NodeParameters args, VideoInfo videoInfo, string extension) - { - if (videoInfo?.SubtitleStreams?.Any() != true) - return false; - bool mov_text = videoInfo.SubtitleStreams.Any(x => x.Codec == "mov_text"); - // if mov_text and going to mkv, we can't convert these subtitles - if (mov_text && extension?.ToLower()?.EndsWith("mkv") == true) - return false; - return true; - //if (Regex.IsMatch(container ?? string.Empty, "(mp(e)?(g)?4)|avi|divx|xvid", RegexOptions.IgnoreCase)) - // return false; - //return true; } } } \ No newline at end of file