diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index ec80fe3..33f51e4 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -12,6 +12,7 @@ using FFMpegCore.Arguments; using FFMpegCore.Exceptions; using FFMpegCore.Pipes; +using System.Threading; namespace FFMpegCore.Test { @@ -551,24 +552,55 @@ public void Video_TranscodeInMemory() public async Task Video_Cancel_Async() { var outputFile = new TemporaryFile("out.mp4"); - + var task = FFMpegArguments - .FromFileInput(TestResources.Mp4Video) + .FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args + .WithCustomArgument("-re") + .ForceFormat("lavfi")) .OutputToFile(outputFile, false, opt => opt - .Resize(new Size(1000, 1000)) .WithAudioCodec(AudioCodec.Aac) .WithVideoCodec(VideoCodec.LibX264) - .WithConstantRateFactor(14) - .WithSpeedPreset(Speed.VerySlow) - .Loop(3)) + .WithSpeedPreset(Speed.VeryFast)) .CancellableThrough(out var cancel) .ProcessAsynchronously(false); - + await Task.Delay(300); cancel(); - + var result = await task; + Assert.IsFalse(result); } + + [TestMethod, Timeout(10000)] + public async Task Video_Cancel_Async_With_Timeout() + { + 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, 10000) + .ProcessAsynchronously(false); + + await Task.Delay(300); + cancel(); + + var result = await task; + + var outputInfo = FFProbe.Analyse(outputFile); + + Assert.IsTrue(result); + Assert.IsNotNull(outputInfo); + Assert.AreEqual(320, outputInfo.PrimaryVideoStream.Width); + Assert.AreEqual(240, outputInfo.PrimaryVideoStream.Height); + Assert.AreEqual("h264", outputInfo.PrimaryVideoStream.CodecName); + Assert.AreEqual("aac", outputInfo.PrimaryAudioStream.CodecName); + } } } diff --git a/FFMpegCore/FFMpeg/Arguments/InputDeviceArgument.cs b/FFMpegCore/FFMpeg/Arguments/InputDeviceArgument.cs new file mode 100644 index 0000000..f276bbb --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/InputDeviceArgument.cs @@ -0,0 +1,26 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents an input device parameter + /// + public class InputDeviceArgument : IInputArgument + { + private readonly string Device; + + public InputDeviceArgument(string device) + { + Device = device; + } + + public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask; + + public void Pre() { } + + public void Post() { } + + public string Text => $"-i {Device}"; + } +} diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs index 9d0a4ad..75b98f4 100644 --- a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs +++ b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs @@ -27,7 +27,7 @@ internal FFMpegArgumentProcessor(FFMpegArguments ffMpegArguments) public string Arguments => _ffMpegArguments.Text; - private event EventHandler CancelEvent = null!; + private event EventHandler CancelEvent = null!; public FFMpegArgumentProcessor NotifyOnProgress(Action onPercentageProgress, TimeSpan totalTimeSpan) { @@ -45,9 +45,9 @@ public FFMpegArgumentProcessor NotifyOnOutput(Action onOutput) _onOutput = onOutput; return this; } - public FFMpegArgumentProcessor CancellableThrough(out Action cancel) + public FFMpegArgumentProcessor CancellableThrough(out Action cancel, int timeout = 0) { - cancel = () => CancelEvent?.Invoke(this, EventArgs.Empty); + cancel = () => CancelEvent?.Invoke(this, timeout); return this; } public bool ProcessSynchronously(bool throwOnError = true) @@ -55,11 +55,15 @@ public bool ProcessSynchronously(bool throwOnError = true) using var instance = PrepareInstance(out var cancellationTokenSource); var errorCode = -1; - void OnCancelEvent(object sender, EventArgs args) + void OnCancelEvent(object sender, int timeout) { instance.SendInput("q"); - cancellationTokenSource.Cancel(); - instance.Started = false; + + if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true)) + { + cancellationTokenSource.Cancel(); + instance.Started = false; + } } CancelEvent += OnCancelEvent; instance.Exited += delegate { cancellationTokenSource.Cancel(); }; @@ -102,11 +106,15 @@ public async Task ProcessAsynchronously(bool throwOnError = true) using var instance = PrepareInstance(out var cancellationTokenSource); var errorCode = -1; - void OnCancelEvent(object sender, EventArgs args) + void OnCancelEvent(object sender, int timeout) { instance.SendInput("q"); - cancellationTokenSource.Cancel(); - instance.Started = false; + + if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true)) + { + cancellationTokenSource.Cancel(); + instance.Started = false; + } } CancelEvent += OnCancelEvent; diff --git a/FFMpegCore/FFMpeg/FFMpegArguments.cs b/FFMpegCore/FFMpeg/FFMpegArguments.cs index 37dc36f..64fff3c 100644 --- a/FFMpegCore/FFMpeg/FFMpegArguments.cs +++ b/FFMpegCore/FFMpeg/FFMpegArguments.cs @@ -22,6 +22,7 @@ private FFMpegArguments() { } public static FFMpegArguments FromFileInput(string filePath, bool verifyExists = true, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(verifyExists, filePath), addArguments); public static FFMpegArguments FromFileInput(FileInfo fileInfo, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(fileInfo.FullName, false), addArguments); public static FFMpegArguments FromUrlInput(Uri uri, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(uri.AbsoluteUri, false), addArguments); + public static FFMpegArguments FromDeviceInput(string device, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputDeviceArgument(device), addArguments); public static FFMpegArguments FromPipeInput(IPipeSource sourcePipe, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputPipeArgument(sourcePipe), addArguments);