Add cancellation token support to SnapshotAsync, GifSnapshotAsync and SubVideoAsync methods. Fixes #592.

This commit is contained in:
Sergey Nechaev 2025-10-21 16:02:35 +02:00
parent a599c48511
commit b3c201b42e
4 changed files with 33 additions and 16 deletions

View file

@ -39,18 +39,21 @@ public static class FFMpegImage
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param> /// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
/// <param name="streamIndex">Selected video stream index.</param> /// <param name="streamIndex">Selected video stream index.</param>
/// <param name="inputFileIndex">Input file index</param> /// <param name="inputFileIndex">Input file index</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Bitmap with the requested snapshot.</returns> /// <returns>Bitmap with the requested snapshot.</returns>
public static async Task<SKBitmap> SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, public static async Task<SKBitmap> 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); var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
using var ms = new MemoryStream(); using var ms = new MemoryStream();
await arguments await arguments
.OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
.ForceFormat("rawvideo"))) .ForceFormat("rawvideo")))
.ProcessAsynchronously(); .CancellableThrough(cancellationToken)
.ProcessAsynchronously()
.ConfigureAwait(false);
ms.Position = 0; ms.Position = 0;
return SKBitmap.Decode(ms); return SKBitmap.Decode(ms);

View file

@ -38,18 +38,21 @@ public static class FFMpegImage
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param> /// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
/// <param name="streamIndex">Selected video stream index.</param> /// <param name="streamIndex">Selected video stream index.</param>
/// <param name="inputFileIndex">Input file index</param> /// <param name="inputFileIndex">Input file index</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Bitmap with the requested snapshot.</returns> /// <returns>Bitmap with the requested snapshot.</returns>
public static async Task<Bitmap> SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, public static async Task<Bitmap> 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); var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
using var ms = new MemoryStream(); using var ms = new MemoryStream();
await arguments await arguments
.OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
.ForceFormat("rawvideo"))) .ForceFormat("rawvideo")))
.ProcessAsynchronously(); .CancellableThrough(cancellationToken)
.ProcessAsynchronously()
.ConfigureAwait(false);
ms.Position = 0; ms.Position = 0;
return new Bitmap(ms); return new Bitmap(ms);

View file

@ -698,7 +698,8 @@ public class VideoTest
using var outputPath = new TemporaryFile("out.gif"); using var outputPath = new TemporaryFile("out.gif");
var input = FFProbe.Analyse(TestResources.Mp4Video); 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); var analysis = FFProbe.Analyse(outputPath);
Assert.AreNotEqual(input.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Width); Assert.AreNotEqual(input.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Width);
@ -714,7 +715,8 @@ public class VideoTest
var input = FFProbe.Analyse(TestResources.Mp4Video); var input = FFProbe.Analyse(TestResources.Mp4Video);
var desiredGifSize = new Size(320, 240); 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); var analysis = FFProbe.Analyse(outputPath);
Assert.AreNotEqual(input.PrimaryVideoStream!.Width, desiredGifSize.Width); Assert.AreNotEqual(input.PrimaryVideoStream!.Width, desiredGifSize.Width);

View file

@ -37,16 +37,19 @@ public static class FFMpeg
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param> /// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
/// <param name="streamIndex">Selected video stream index.</param> /// <param name="streamIndex">Selected video stream index.</param>
/// <param name="inputFileIndex">Input file index</param> /// <param name="inputFileIndex">Input file index</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Bitmap with the requested snapshot.</returns> /// <returns>Bitmap with the requested snapshot.</returns>
public static async Task<bool> SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, public static async Task<bool> 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); 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) 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, 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<bool> GifSnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, TimeSpan? duration = null, public static async Task<bool> 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]); 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) 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, private static FFMpegArgumentProcessor SnapshotProcess(string input, string output, IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null,
@ -321,11 +326,15 @@ public static class FFMpeg
/// <param name="output">Output video file.</param> /// <param name="output">Output video file.</param>
/// <param name="startTime">The start time of when the sub video needs to start</param> /// <param name="startTime">The start time of when the sub video needs to start</param>
/// <param name="endTime">The end time of where the sub video needs to end</param> /// <param name="endTime">The end time of where the sub video needs to end</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Output video information.</returns> /// <returns>Output video information.</returns>
public static async Task<bool> SubVideoAsync(string input, string output, TimeSpan startTime, TimeSpan endTime) public static async Task<bool> SubVideoAsync(string input, string output, TimeSpan startTime, TimeSpan endTime,
CancellationToken cancellationToken = default)
{ {
return await BaseSubVideo(input, output, startTime, endTime) return await BaseSubVideo(input, output, startTime, endTime)
.ProcessAsynchronously(); .CancellableThrough(cancellationToken)
.ProcessAsynchronously()
.ConfigureAwait(false);
} }
/// <summary> /// <summary>