Throw OperationCanceledException when processing is cancelled

This commit is contained in:
Malte Rosenbjerg 2022-04-15 15:08:30 +02:00
parent 6c752e4edb
commit 4f38eed753
4 changed files with 96 additions and 40 deletions

View file

@ -113,13 +113,11 @@ public void Video_ToMP4_Args_Pipe_DifferentImageSizes()
};
var videoFramesSource = new RawVideoPipeSource(frames);
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments
var ex = Assert.ThrowsException<FFMpegStreamFormatException>(() => 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<FFMpegException>(() => FFMpegArguments
var ex = await Assert.ThrowsExceptionAsync<FFMpegStreamFormatException>(() => 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<FFMpegException>(() => FFMpegArguments
var ex = Assert.ThrowsException<FFMpegStreamFormatException>(() => 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<FFMpegException>(() => FFMpegArguments
var ex = await Assert.ThrowsExceptionAsync<FFMpegStreamFormatException>(() => 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<OperationCanceledException>(() => 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<OperationCanceledException>(() => 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);

View file

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

View file

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

View file

@ -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<string>())) return false;
if (throwOnError)
throw;
}
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
@ -112,9 +113,10 @@ public async Task<bool> ProcessAsynchronously(bool throwOnError = true, FFOption
{
processResult = await Process(processArguments, cancellationTokenSource).ConfigureAwait(false);
}
catch (Exception e)
catch (OperationCanceledException)
{
if (!HandleException(throwOnError, e, processResult?.ErrorData ?? Array.Empty<string>())) return false;
if (throwOnError)
throw;
}
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
@ -127,8 +129,10 @@ private async Task<IProcessResult> 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<string> 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);