diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index 149dabd..45853ea 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 { @@ -612,5 +613,64 @@ public async Task Video_Cancel_Async_With_Timeout() Assert.AreEqual("h264", outputInfo.PrimaryVideoStream.CodecName); Assert.AreEqual("aac", outputInfo.PrimaryAudioStream!.CodecName); } + + [TestMethod, Timeout(10000)] + public async Task Video_Cancel_CancellationToken_Async() + { + 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(false); + + await Task.Delay(300); + cts.Cancel(); + + var result = await task; + + Assert.IsFalse(result); + } + + [TestMethod, Timeout(10000)] + public async Task Video_Cancel_CancellationToken_Async_With_Timeout() + { + 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, 5000) + .ProcessAsynchronously(false); + + await Task.Delay(300); + cts.Cancel(); + + var result = 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); + Assert.AreEqual("h264", outputInfo.PrimaryVideoStream.CodecName); + Assert.AreEqual("aac", outputInfo.PrimaryAudioStream!.CodecName); + } } } diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs index 67607af..060ffc3 100644 --- a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs +++ b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs @@ -50,6 +50,11 @@ public FFMpegArgumentProcessor CancellableThrough(out Action cancel, int timeout cancel = () => CancelEvent?.Invoke(this, timeout); return this; } + public FFMpegArgumentProcessor CancellableThrough(CancellationToken token, int timeout = 0) + { + token.Register(() => CancelEvent?.Invoke(this, timeout)); + return this; + } public bool ProcessSynchronously(bool throwOnError = true, FFOptions? ffMpegOptions = null) { using var instance = PrepareInstance(ffMpegOptions ?? GlobalFFOptions.Current, out var cancellationTokenSource); diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj index c8fa692..dd96a3d 100644 --- a/FFMpegCore/FFMpegCore.csproj +++ b/FFMpegCore/FFMpegCore.csproj @@ -9,10 +9,11 @@ 3.0.0.0 3.0.0.0 3.0.0.0 - - Added support for PCM audio through RawAudioPipeSource (thanks to Namaneo) -- Removed -y in InputPipeArgument due to reported problems + - Cancellation token support (thanks patagonaa) +- Support for setting stdout and stderr encoding for ffprobe (thanks CepheiSigma) +- Improved ffprobe exceptions 8 - 4.3.0 + 4.4.0 MIT Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev ffmpeg ffprobe convert video audio mediafile resize analyze muxing diff --git a/FFMpegCore/FFProbe/Exceptions/FFProbeException.cs b/FFMpegCore/FFProbe/Exceptions/FFProbeException.cs new file mode 100644 index 0000000..3495193 --- /dev/null +++ b/FFMpegCore/FFProbe/Exceptions/FFProbeException.cs @@ -0,0 +1,11 @@ +using System; + +namespace FFMpegCore.Exceptions +{ + public class FFProbeException : Exception + { + public FFProbeException(string message, Exception? inner = null) : base(message, inner) + { + } + } +} \ No newline at end of file diff --git a/FFMpegCore/FFProbe/Exceptions/FFProbeProcessException.cs b/FFMpegCore/FFProbe/Exceptions/FFProbeProcessException.cs new file mode 100644 index 0000000..5ab6b93 --- /dev/null +++ b/FFMpegCore/FFProbe/Exceptions/FFProbeProcessException.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace FFMpegCore.Exceptions +{ + public class FFProbeProcessException : FFProbeException + { + public IReadOnlyCollection ProcessErrors { get; } + + public FFProbeProcessException(string message, IReadOnlyCollection processErrors, Exception? inner = null) : base(message, inner) + { + ProcessErrors = processErrors; + } + } +} \ No newline at end of file diff --git a/FFMpegCore/FFProbe/Exceptions/FormatNullException.cs b/FFMpegCore/FFProbe/Exceptions/FormatNullException.cs new file mode 100644 index 0000000..4141f5f --- /dev/null +++ b/FFMpegCore/FFProbe/Exceptions/FormatNullException.cs @@ -0,0 +1,9 @@ +namespace FFMpegCore.Exceptions +{ + public class FormatNullException : FFProbeException + { + public FormatNullException() : base("Format not specified") + { + } + } +} \ No newline at end of file diff --git a/FFMpegCore/FFProbe/FFProbe.cs b/FFMpegCore/FFProbe/FFProbe.cs index ab35457..7d043a6 100644 --- a/FFMpegCore/FFProbe/FFProbe.cs +++ b/FFMpegCore/FFProbe/FFProbe.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.IO; using System.Text.Json; using System.Threading.Tasks; @@ -92,7 +93,7 @@ public static async Task AnalyseAsync(Stream stream, int outputC } var exitCode = await task.ConfigureAwait(false); if (exitCode != 0) - throw new FFMpegException(FFMpegExceptionType.Process, $"FFProbe process returned exit status {exitCode}", null, string.Join("\n", instance.ErrorData)); + throw new FFProbeProcessException($"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", instance.ErrorData); pipeArgument.Post(); return ParseOutput(instance); @@ -107,7 +108,7 @@ private static IMediaAnalysis ParseOutput(Instance instance) }); if (ffprobeAnalysis?.Format == null) - throw new Exception(); + throw new FormatNullException(); return new MediaAnalysis(ffprobeAnalysis); } @@ -117,7 +118,12 @@ private static Instance PrepareInstance(string filePath, int outputCapacity, FFO FFProbeHelper.RootExceptionCheck(); FFProbeHelper.VerifyFFProbeExists(ffOptions); var arguments = $"-loglevel error -print_format json -show_format -sexagesimal -show_streams \"{filePath}\""; - var instance = new Instance(GlobalFFOptions.GetFFProbeBinaryPath(), arguments) {DataBufferCapacity = outputCapacity}; + var startInfo = new ProcessStartInfo(GlobalFFOptions.GetFFProbeBinaryPath(), arguments) + { + StandardOutputEncoding = ffOptions.Encoding, + StandardErrorEncoding = ffOptions.Encoding + }; + var instance = new Instance(startInfo) { DataBufferCapacity = outputCapacity }; return instance; } } diff --git a/FFMpegCore/Helpers/FFProbeHelper.cs b/FFMpegCore/Helpers/FFProbeHelper.cs index d0064e4..4989542 100644 --- a/FFMpegCore/Helpers/FFProbeHelper.cs +++ b/FFMpegCore/Helpers/FFProbeHelper.cs @@ -30,7 +30,7 @@ public static void VerifyFFProbeExists(FFOptions ffMpegOptions) var (exitCode, _) = Instance.Finish(GlobalFFOptions.GetFFProbeBinaryPath(ffMpegOptions), "-version"); _ffprobeVerified = exitCode == 0; if (!_ffprobeVerified) - throw new FFMpegException(FFMpegExceptionType.Operation, "ffprobe was not found on your system"); + throw new FFProbeException("ffprobe was not found on your system"); } } }