diff --git a/FFMpegCore.Extensions.SkiaSharp/FFMpegImage.cs b/FFMpegCore.Extensions.SkiaSharp/FFMpegImage.cs index f412844..41c6a1c 100644 --- a/FFMpegCore.Extensions.SkiaSharp/FFMpegImage.cs +++ b/FFMpegCore.Extensions.SkiaSharp/FFMpegImage.cs @@ -39,18 +39,21 @@ public static class FFMpegImage /// Thumbnail size. If width or height equal 0, the other will be computed automatically. /// Selected video stream index. /// Input file index + /// Cancellation token /// Bitmap with the requested snapshot. public static async Task SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, - int inputFileIndex = 0) + int inputFileIndex = 0, CancellationToken cancellationToken = default) { - var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false); + var source = await FFProbe.AnalyseAsync(input, cancellationToken: cancellationToken).ConfigureAwait(false); var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); using var ms = new MemoryStream(); await arguments .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options .ForceFormat("rawvideo"))) - .ProcessAsynchronously(); + .CancellableThrough(cancellationToken) + .ProcessAsynchronously() + .ConfigureAwait(false); ms.Position = 0; return SKBitmap.Decode(ms); diff --git a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs index 1c7f965..2dd2234 100644 --- a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs +++ b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs @@ -38,18 +38,21 @@ public static class FFMpegImage /// Thumbnail size. If width or height equal 0, the other will be computed automatically. /// Selected video stream index. /// Input file index + /// Cancellation token /// Bitmap with the requested snapshot. public static async Task SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, - int inputFileIndex = 0) + int inputFileIndex = 0, CancellationToken cancellationToken = default) { - var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false); + var source = await FFProbe.AnalyseAsync(input, cancellationToken: cancellationToken).ConfigureAwait(false); var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); using var ms = new MemoryStream(); await arguments .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options .ForceFormat("rawvideo"))) - .ProcessAsynchronously(); + .CancellableThrough(cancellationToken) + .ProcessAsynchronously() + .ConfigureAwait(false); ms.Position = 0; return new Bitmap(ms); diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index e338324..b0a85aa 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -732,7 +732,8 @@ public class VideoTest using var outputPath = new TemporaryFile("out.gif"); var input = FFProbe.Analyse(TestResources.Mp4Video); - await FFMpeg.GifSnapshotAsync(TestResources.Mp4Video, outputPath, captureTime: TimeSpan.FromSeconds(0)); + await FFMpeg.GifSnapshotAsync(TestResources.Mp4Video, outputPath, captureTime: TimeSpan.FromSeconds(0), + cancellationToken: TestContext.CancellationToken); var analysis = FFProbe.Analyse(outputPath); Assert.AreNotEqual(input.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Width); @@ -748,7 +749,8 @@ public class VideoTest var input = FFProbe.Analyse(TestResources.Mp4Video); var desiredGifSize = new Size(320, 240); - await FFMpeg.GifSnapshotAsync(TestResources.Mp4Video, outputPath, desiredGifSize, TimeSpan.FromSeconds(0)); + await FFMpeg.GifSnapshotAsync(TestResources.Mp4Video, outputPath, desiredGifSize, TimeSpan.FromSeconds(0), + cancellationToken: TestContext.CancellationToken); var analysis = FFProbe.Analyse(outputPath); Assert.AreNotEqual(input.PrimaryVideoStream!.Width, desiredGifSize.Width); diff --git a/FFMpegCore/FFMpeg/FFMpeg.cs b/FFMpegCore/FFMpeg/FFMpeg.cs index 0b6de74..b9a0d5d 100644 --- a/FFMpegCore/FFMpeg/FFMpeg.cs +++ b/FFMpegCore/FFMpeg/FFMpeg.cs @@ -37,16 +37,19 @@ public static class FFMpeg /// Thumbnail size. If width or height equal 0, the other will be computed automatically. /// Selected video stream index. /// Input file index + /// Cancellation token /// Bitmap with the requested snapshot. public static async Task SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, - int inputFileIndex = 0) + int inputFileIndex = 0, CancellationToken cancellationToken = default) { CheckSnapshotOutputExtension(output, FileExtension.Image.All); - var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false); + var source = await FFProbe.AnalyseAsync(input, cancellationToken: cancellationToken).ConfigureAwait(false); return await SnapshotProcess(input, output, source, size, captureTime, streamIndex, inputFileIndex) - .ProcessAsynchronously(); + .CancellableThrough(cancellationToken) + .ProcessAsynchronously() + .ConfigureAwait(false); } public static bool GifSnapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, TimeSpan? duration = null, @@ -61,14 +64,16 @@ public static class FFMpeg } public static async Task GifSnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, TimeSpan? duration = null, - int? streamIndex = null) + int? streamIndex = null, CancellationToken cancellationToken = default) { CheckSnapshotOutputExtension(output, [FileExtension.Gif]); - var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false); + var source = await FFProbe.AnalyseAsync(input, cancellationToken: cancellationToken).ConfigureAwait(false); return await GifSnapshotProcess(input, output, source, size, captureTime, duration, streamIndex) - .ProcessAsynchronously(); + .CancellableThrough(cancellationToken) + .ProcessAsynchronously() + .ConfigureAwait(false); } private static FFMpegArgumentProcessor SnapshotProcess(string input, string output, IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, @@ -321,11 +326,15 @@ public static class FFMpeg /// Output video file. /// The start time of when the sub video needs to start /// The end time of where the sub video needs to end + /// Cancellation token /// Output video information. - public static async Task SubVideoAsync(string input, string output, TimeSpan startTime, TimeSpan endTime) + public static async Task SubVideoAsync(string input, string output, TimeSpan startTime, TimeSpan endTime, + CancellationToken cancellationToken = default) { return await BaseSubVideo(input, output, startTime, endTime) - .ProcessAsynchronously(); + .CancellableThrough(cancellationToken) + .ProcessAsynchronously() + .ConfigureAwait(false); } /// diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs index 2f350ce..8191223 100644 --- a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs +++ b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs @@ -166,12 +166,28 @@ public class FFMpegArgumentProcessor void OnCancelEvent(object sender, int timeout) { - instance.SendInput("q"); + ExecuteIgnoringFinishedProcessExceptions(() => instance.SendInput("q")); if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true)) { cancellationTokenSource.Cancel(); - instance.Kill(); + ExecuteIgnoringFinishedProcessExceptions(() => instance.Kill()); + } + + static void ExecuteIgnoringFinishedProcessExceptions(Action action) + { + try + { + action(); + } + catch (Instances.Exceptions.InstanceProcessAlreadyExitedException) + { + //ignore + } + catch (ObjectDisposedException) + { + //ignore + } } }