mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2025-12-14 01:55:45 +00:00
Merge pull request #593 from snechaev/PR-592_Snapshot_Cancellation_Support
Add cancellation token support for the [Gif]SnapshotAsync
This commit is contained in:
commit
9b1e373c55
5 changed files with 51 additions and 18 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -732,7 +732,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);
|
||||||
|
|
@ -748,7 +749,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);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -166,12 +166,28 @@ public class FFMpegArgumentProcessor
|
||||||
|
|
||||||
void OnCancelEvent(object sender, int timeout)
|
void OnCancelEvent(object sender, int timeout)
|
||||||
{
|
{
|
||||||
instance.SendInput("q");
|
ExecuteIgnoringFinishedProcessExceptions(() => instance.SendInput("q"));
|
||||||
|
|
||||||
if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true))
|
if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true))
|
||||||
{
|
{
|
||||||
cancellationTokenSource.Cancel();
|
cancellationTokenSource.Cancel();
|
||||||
instance.Kill();
|
ExecuteIgnoringFinishedProcessExceptions(() => instance.Kill());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ExecuteIgnoringFinishedProcessExceptions(Action action)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
catch (Instances.Exceptions.InstanceProcessAlreadyExitedException)
|
||||||
|
{
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue