Skip to content

Commit

Permalink
Merge pull request #321 from rosenbjerg/feature/throw-operationcancel…
Browse files Browse the repository at this point in the history
…ledexception-on-cancellation

Throw OperationCanceledException when processing is cancelled
  • Loading branch information
rosenbjerg authored Apr 15, 2022
2 parents 6c752e4 + 4f38eed commit d64c9f4
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 40 deletions.
94 changes: 74 additions & 20 deletions FFMpegCore.Test/VideoTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}


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


Expand All @@ -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)]
Expand Down Expand Up @@ -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()
{
Expand All @@ -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);
Expand All @@ -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()
{
Expand All @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions FFMpegCore/FFMpeg/Arguments/InputPipeArgument.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO.Pipes;
using System;
using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Pipes;
Expand All @@ -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);
}
}
Expand Down
5 changes: 3 additions & 2 deletions FFMpegCore/FFMpeg/Arguments/PipeArgument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

Expand Down
32 changes: 16 additions & 16 deletions FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>());
Expand All @@ -106,17 +107,18 @@ public async Task<bool> 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<string>())) return false;
if (throwOnError)
throw;
}

return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
}

Expand All @@ -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))
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit d64c9f4

Please sign in to comment.