mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2025-12-30 15:09:45 -06:00
using cancelation token
This commit is contained in:
@@ -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.
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user