using cancelation token

This commit is contained in:
John Andrews
2025-03-17 12:28:51 +13:00
parent 02b95d629e
commit b3f7f11211
7 changed files with 49 additions and 263 deletions

View File

@@ -51,12 +51,14 @@ public class Executor : Node
private NodeParameters args;
/// <inheritdoc />
public override Task Cancel()
{
args?.Process?.Cancel();
return Task.CompletedTask;
}
// /// <inheritdoc />
// public override Task Cancel()
// {
// if (args.Process is ProcessHelper process)
// process.Kill();
//
// return Task.CompletedTask;
// }
/// <inheritdoc />
public override int Execute(NodeParameters args)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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;
}
/// <summary>
@@ -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<ProcessResult> ExecuteShellCommand(string command, List<string> 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<bool>();
process.OutputDataReceived += OnOutputDataReceived;
errorBuilder = new StringBuilder();
errorCloseEvent = new TaskCompletionSource<bool>();
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<bool> WaitForExitAsync(Process process, int timeout)
{
if (timeout > 0)
return Task.Run(() => process.WaitForExit(timeout));
return Task.Run(() =>
{
process.WaitForExit();
return Task.FromResult<bool>(true);
});
}
/// <summary>
/// Represents the result of a process execution.
/// </summary>

View File

@@ -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;
}
}
}