From e922f37cbe66ec4bad35a092d4d25a34dca9d24a Mon Sep 17 00:00:00 2001 From: dm413 Date: Fri, 26 Apr 2019 18:50:32 +0000 Subject: [PATCH] Cancel ffmpeg (#11) * Bug fix for issue #8: Cancel does not cancel ffmpeg.exe. See https://github.com/cmxl/FFmpeg.NET/issues/8. When the calling program cancels the task by invoking Cancel() on the CancellationToken, send "q" to standard input of the ffmpeg.exe process. This causes ffmpeg.exe to stop processing and shut down in an orderly way. --- .../Extensions/ProcessExtensions.cs | 91 ++++++++++++------- src/FFmpeg.NET/FFmpegProcess.cs | 29 ++++-- 2 files changed, 81 insertions(+), 39 deletions(-) diff --git a/src/FFmpeg.NET/Extensions/ProcessExtensions.cs b/src/FFmpeg.NET/Extensions/ProcessExtensions.cs index 53f8673..5a3ad41 100644 --- a/src/FFmpeg.NET/Extensions/ProcessExtensions.cs +++ b/src/FFmpeg.NET/Extensions/ProcessExtensions.cs @@ -1,33 +1,58 @@ -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; - -namespace FFmpeg.NET.Extensions -{ - public static class ProcessExtensions - { - public static Task WaitForExitAsync(this Process process, Action onException, CancellationToken cancellationToken = default) - { - var tcs = new TaskCompletionSource(); - if (cancellationToken != default) - cancellationToken.Register(tcs.SetCanceled); - - process.EnableRaisingEvents = true; - process.Exited += (sender, e) => - { - if (process.ExitCode != 0) - onException?.Invoke(process.ExitCode); - tcs.TrySetResult(process.ExitCode); - }; - - var started = process.Start(); - if (!started) - tcs.TrySetException(new InvalidOperationException($"Could not start process {process}")); - - process.BeginErrorReadLine(); - - return tcs.Task; - } - } -} +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace FFmpeg.NET.Extensions +{ + public static class ProcessExtensions + { + public static Task WaitForExitAsync(this Process process, Action onException, CancellationToken cancellationToken = default) + { + TaskCompletionSource tcs = new TaskCompletionSource(); + if (cancellationToken != default) + { + cancellationToken.Register(() => + { + try + { + // Send "q" to ffmpeg, which will force it to stop (closing files). + process.StandardInput.Write("q"); + } + catch (InvalidOperationException) + { + // If the process doesn't exist anymore, ignore it. + } + finally + { + // Cancel the task. This will throw an exception to the calling program. + // Exc.Message will be "A task was canceled." + try + { + tcs.SetCanceled(); + } + catch (Exception) + { + } + } + }); + } + + process.EnableRaisingEvents = true; + process.Exited += (sender, e) => + { + if (process.ExitCode != 0) + onException?.Invoke(process.ExitCode); + tcs.TrySetResult(process.ExitCode); + }; + + var started = process.Start(); + if (!started) + tcs.TrySetException(new InvalidOperationException($"Could not start process {process}")); + + process.BeginErrorReadLine(); + + return tcs.Task; + } + } +} diff --git a/src/FFmpeg.NET/FFmpegProcess.cs b/src/FFmpeg.NET/FFmpegProcess.cs index e156404..9836419 100644 --- a/src/FFmpeg.NET/FFmpegProcess.cs +++ b/src/FFmpeg.NET/FFmpegProcess.cs @@ -29,8 +29,24 @@ private async Task ExecuteAsync(ProcessStartInfo startInfo, FFmpegParameters par ffmpegProcess.ErrorDataReceived += (sender, e) => OnData(new ConversionDataEventArgs(e.Data, parameters.InputFile, parameters.OutputFile)); ffmpegProcess.ErrorDataReceived += (sender, e) => FFmpegProcessOnErrorDataReceived(e, parameters, ref caughtException, messages); - await ffmpegProcess.WaitForExitAsync((exitCode) => OnException(messages, parameters, exitCode, caughtException), cancellationToken); - + Task task = null; + try + { + task = ffmpegProcess.WaitForExitAsync((exitCode) => OnException(messages, parameters, exitCode, caughtException), cancellationToken); + await task; + } + catch (Exception) + { + // An exception occurs if the user cancels the operation by calling Cancel on the CancellationToken. + // Exc.Message will be "A task was canceled." (in English). + // task.IsCanceled will be true. + if (task.IsCanceled) + { + throw new TaskCanceledException(task); + } + // I don't think this can occur, but if some other exception, rethrow it. + throw; + } if (caughtException != null || ffmpegProcess.ExitCode != 0) { OnException(messages, parameters, ffmpegProcess.ExitCode, caughtException); @@ -49,7 +65,7 @@ private void OnException(List messages, FFmpegParameters parameters, int OnConversionError(new ConversionErrorEventArgs(exception, parameters.InputFile, parameters.OutputFile)); } - private string GetExceptionMessage(List messages) + private string GetExceptionMessage(List messages) => messages.Count > 1 ? messages[1] + messages[0] : string.Join(string.Empty, messages); @@ -93,10 +109,11 @@ private void FFmpegProcessOnErrorDataReceived(DataReceivedEventArgs e, FFmpegPar private ProcessStartInfo GenerateStartInfo(string ffmpegPath, string arguments) => new ProcessStartInfo { - Arguments = "-nostdin -y -loglevel info " + arguments, + // -y overwrite output files + Arguments = "-y " + arguments, FileName = ffmpegPath, CreateNoWindow = true, - RedirectStandardInput = false, + RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, @@ -116,4 +133,4 @@ private void FFmpegProcessOnErrorDataReceived(DataReceivedEventArgs e, FFmpegPar private void OnData(ConversionDataEventArgs eventArgs) => Data?.Invoke(eventArgs); } -} \ No newline at end of file +}