From 4f38eed753767d7989212828f40f00479e793f17 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Fri, 15 Apr 2022 15:08:30 +0200 Subject: [PATCH] Throw OperationCanceledException when processing is cancelled --- FFMpegCore.Test/VideoTest.cs | 94 +++++++++++++++---- .../FFMpeg/Arguments/InputPipeArgument.cs | 5 +- FFMpegCore/FFMpeg/Arguments/PipeArgument.cs | 5 +- FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs | 32 +++---- 4 files changed, 96 insertions(+), 40 deletions(-) diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index 802f83e..262bd75 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -113,13 +113,11 @@ public void Video_ToMP4_Args_Pipe_DifferentImageSizes() }; var videoFramesSource = new RawVideoPipeSource(frames); - var ex = Assert.ThrowsException(() => FFMpegArguments + var ex = Assert.ThrowsException(() => FFMpegArguments .FromPipeInput(videoFramesSource) .OutputToFile(outputFile, false, opt => opt .WithVideoCodec(VideoCodec.LibX264)) .ProcessSynchronously()); - - Assert.IsInstanceOfType(ex.GetBaseException(), typeof(FFMpegStreamFormatException)); } @@ -135,13 +133,11 @@ public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Async() }; var videoFramesSource = new RawVideoPipeSource(frames); - var ex = await Assert.ThrowsExceptionAsync(() => FFMpegArguments + var ex = await Assert.ThrowsExceptionAsync(() => FFMpegArguments .FromPipeInput(videoFramesSource) .OutputToFile(outputFile, false, opt => opt .WithVideoCodec(VideoCodec.LibX264)) .ProcessAsynchronously()); - - Assert.IsInstanceOfType(ex.GetBaseException(), typeof(FFMpegStreamFormatException)); } [TestMethod, Timeout(10000)] @@ -156,13 +152,11 @@ public void Video_ToMP4_Args_Pipe_DifferentPixelFormats() }; var videoFramesSource = new RawVideoPipeSource(frames); - var ex = Assert.ThrowsException(() => FFMpegArguments + var ex = Assert.ThrowsException(() => FFMpegArguments .FromPipeInput(videoFramesSource) .OutputToFile(outputFile, false, opt => opt .WithVideoCodec(VideoCodec.LibX264)) .ProcessSynchronously()); - - Assert.IsInstanceOfType(ex.GetBaseException(), typeof(FFMpegStreamFormatException)); } @@ -178,13 +172,11 @@ public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_Async() }; var videoFramesSource = new RawVideoPipeSource(frames); - var ex = await Assert.ThrowsExceptionAsync(() => FFMpegArguments + var ex = await Assert.ThrowsExceptionAsync(() => FFMpegArguments .FromPipeInput(videoFramesSource) .OutputToFile(outputFile, false, opt => opt .WithVideoCodec(VideoCodec.LibX264)) .ProcessAsynchronously()); - - Assert.IsInstanceOfType(ex.GetBaseException(), typeof(FFMpegStreamFormatException)); } [TestMethod, Timeout(10000)] @@ -596,6 +588,27 @@ public async Task Video_Cancel_Async() Assert.IsFalse(result); } + [TestMethod, Timeout(10000)] + public void Video_Cancel() + { + var outputFile = new TemporaryFile("out.mp4"); + var task = FFMpegArguments + .FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args + .WithCustomArgument("-re") + .ForceFormat("lavfi")) + .OutputToFile(outputFile, false, opt => opt + .WithAudioCodec(AudioCodec.Aac) + .WithVideoCodec(VideoCodec.LibX264) + .WithSpeedPreset(Speed.VeryFast)) + .CancellableThrough(out var cancel); + + Task.Delay(300).ContinueWith((_) => cancel()); + + var result = task.ProcessSynchronously(false); + + Assert.IsFalse(result); + } + [TestMethod, Timeout(10000)] public async Task Video_Cancel_Async_With_Timeout() { @@ -615,11 +628,10 @@ public async Task Video_Cancel_Async_With_Timeout() await Task.Delay(300); cancel(); - var result = await task; + await task; var outputInfo = await FFProbe.AnalyseAsync(outputFile); - Assert.IsTrue(result); Assert.IsNotNull(outputInfo); Assert.AreEqual(320, outputInfo.PrimaryVideoStream!.Width); Assert.AreEqual(240, outputInfo.PrimaryVideoStream.Height); @@ -645,14 +657,58 @@ public async Task Video_Cancel_CancellationToken_Async() .CancellableThrough(cts.Token) .ProcessAsynchronously(false); - await Task.Delay(300); - cts.Cancel(); + cts.CancelAfter(300); var result = await task; Assert.IsFalse(result); } + [TestMethod, Timeout(10000)] + public async Task Video_Cancel_CancellationToken_Async_Throws() + { + var outputFile = new TemporaryFile("out.mp4"); + + var cts = new CancellationTokenSource(); + + var task = FFMpegArguments + .FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args + .WithCustomArgument("-re") + .ForceFormat("lavfi")) + .OutputToFile(outputFile, false, opt => opt + .WithAudioCodec(AudioCodec.Aac) + .WithVideoCodec(VideoCodec.LibX264) + .WithSpeedPreset(Speed.VeryFast)) + .CancellableThrough(cts.Token) + .ProcessAsynchronously(); + + cts.CancelAfter(300); + + await Assert.ThrowsExceptionAsync(() => task); + } + + [TestMethod, Timeout(10000)] + public void Video_Cancel_CancellationToken_Throws() + { + var outputFile = new TemporaryFile("out.mp4"); + + var cts = new CancellationTokenSource(); + + var task = FFMpegArguments + .FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args + .WithCustomArgument("-re") + .ForceFormat("lavfi")) + .OutputToFile(outputFile, false, opt => opt + .WithAudioCodec(AudioCodec.Aac) + .WithVideoCodec(VideoCodec.LibX264) + .WithSpeedPreset(Speed.VeryFast)) + .CancellableThrough(cts.Token); + + cts.CancelAfter(300); + + Assert.ThrowsException(() => task.ProcessSynchronously()); + } + [TestMethod, Timeout(10000)] public async Task Video_Cancel_CancellationToken_Async_With_Timeout() { @@ -671,14 +727,12 @@ public async Task Video_Cancel_CancellationToken_Async_With_Timeout() .CancellableThrough(cts.Token, 8000) .ProcessAsynchronously(false); - await Task.Delay(300); - cts.Cancel(); + cts.CancelAfter(300); - var result = await task; + await task; var outputInfo = await FFProbe.AnalyseAsync(outputFile); - Assert.IsTrue(result); Assert.IsNotNull(outputInfo); Assert.AreEqual(320, outputInfo.PrimaryVideoStream!.Width); Assert.AreEqual(240, outputInfo.PrimaryVideoStream.Height); diff --git a/FFMpegCore/FFMpeg/Arguments/InputPipeArgument.cs b/FFMpegCore/FFMpeg/Arguments/InputPipeArgument.cs index 199d324..d5ba44c 100644 --- a/FFMpegCore/FFMpeg/Arguments/InputPipeArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/InputPipeArgument.cs @@ -1,4 +1,5 @@ -using System.IO.Pipes; +using System; +using System.IO.Pipes; using System.Threading; using System.Threading.Tasks; using FFMpegCore.Pipes; @@ -23,7 +24,7 @@ protected override async Task ProcessDataAsync(CancellationToken token) { await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false); if (!Pipe.IsConnected) - throw new TaskCanceledException(); + throw new OperationCanceledException(); await Writer.WriteAsync(Pipe, token).ConfigureAwait(false); } } diff --git a/FFMpegCore/FFMpeg/Arguments/PipeArgument.cs b/FFMpegCore/FFMpeg/Arguments/PipeArgument.cs index c25df04..ddaab82 100644 --- a/FFMpegCore/FFMpeg/Arguments/PipeArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/PipeArgument.cs @@ -42,14 +42,15 @@ public async Task During(CancellationToken cancellationToken = default) { await ProcessDataAsync(cancellationToken).ConfigureAwait(false); } - catch (TaskCanceledException) + catch (OperationCanceledException) { Debug.WriteLine($"ProcessDataAsync on {GetType().Name} cancelled"); } finally { Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}"); - Pipe?.Disconnect(); + if (Pipe is { IsConnected: true }) + Pipe.Disconnect(); } } diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs index 34ccc59..43ace4d 100644 --- a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs +++ b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs @@ -87,16 +87,17 @@ public bool ProcessSynchronously(bool throwOnError = true, FFOptions? ffMpegOpti { var options = GetConfiguredOptions(ffMpegOptions); var processArguments = PrepareProcessArguments(options, out var cancellationTokenSource); - processArguments.Exited += delegate { cancellationTokenSource.Cancel(); }; + IProcessResult? processResult = null; try { processResult = Process(processArguments, cancellationTokenSource).ConfigureAwait(false).GetAwaiter().GetResult(); } - catch (Exception e) + catch (OperationCanceledException) { - if (!HandleException(throwOnError, e, processResult?.ErrorData ?? Array.Empty())) return false; + if (throwOnError) + throw; } return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty()); @@ -106,17 +107,18 @@ public async Task ProcessAsynchronously(bool throwOnError = true, FFOption { var options = GetConfiguredOptions(ffMpegOptions); var processArguments = PrepareProcessArguments(options, out var cancellationTokenSource); - + IProcessResult? processResult = null; try { processResult = await Process(processArguments, cancellationTokenSource).ConfigureAwait(false); } - catch (Exception e) + catch (OperationCanceledException) { - if (!HandleException(throwOnError, e, processResult?.ErrorData ?? Array.Empty())) return false; + if (throwOnError) + throw; } - + return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty()); } @@ -127,8 +129,10 @@ private async Task Process(ProcessArguments processArguments, Ca _ffMpegArguments.Pre(); using var instance = processArguments.Start(); + var cancelled = false; void OnCancelEvent(object sender, int timeout) { + cancelled = true; instance.SendInput("q"); if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true)) @@ -148,6 +152,11 @@ await Task.WhenAll(instance.WaitForExitAsync().ContinueWith(t => _ffMpegArguments.Post(); }), _ffMpegArguments.During(cancellationTokenSource.Token)).ConfigureAwait(false); + if (cancelled) + { + throw new OperationCanceledException("ffmpeg processing was cancelled"); + } + return processResult; } finally @@ -209,15 +218,6 @@ private void ErrorData(object sender, string msg) _onError?.Invoke(msg); } - - private static bool HandleException(bool throwOnError, Exception e, IEnumerable errorData) - { - if (!throwOnError) - return false; - - throw new FFMpegException(FFMpegExceptionType.Process, "Exception thrown during processing", e, string.Join("\n", errorData)); - } - private void OutputData(object sender, string msg) { Debug.WriteLine(msg);