From 97053929a9d6eebf758e3ae121a0b82fcabca9a4 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 25 Oct 2025 11:25:16 +0200 Subject: [PATCH 01/13] Add FFMetadataBuilder for easily constructing metadata text --- FFMpegCore/FFMetadataInputArgument.cs | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 FFMpegCore/FFMetadataInputArgument.cs diff --git a/FFMpegCore/FFMetadataInputArgument.cs b/FFMpegCore/FFMetadataInputArgument.cs new file mode 100644 index 0000000..68a2c2e --- /dev/null +++ b/FFMpegCore/FFMetadataInputArgument.cs @@ -0,0 +1,63 @@ +using System.Text; + +namespace FFMpegCore; + +public class FFMetadataBuilder +{ + private Dictionary Tags { get; } = new(); + private List Chapters { get; } = []; + + public static FFMetadataBuilder Empty() + { + return new FFMetadataBuilder(); + } + + public FFMetadataBuilder WithTag(string key, string value) + { + Tags.Add(key, value); + return this; + } + + public FFMetadataBuilder WithChapter(string title, long durationMs) + { + Chapters.Add(new FFMetadataChapter(title, durationMs)); + return this; + } + + public FFMetadataBuilder WithChapter(string title, double durationSeconds) + { + Chapters.Add(new FFMetadataChapter(title, Convert.ToInt64(durationSeconds * 1000))); + return this; + } + + public string GetMetadataFileContent() + { + var sb = new StringBuilder(); + sb.AppendLine(";FFMETADATA1"); + + foreach (var tag in Tags) + { + sb.AppendLine($"{tag.Key}={tag.Value}"); + } + + long totalDurationMs = 0; + foreach (var chapter in Chapters) + { + sb.AppendLine("[CHAPTER]"); + sb.AppendLine("TIMEBASE=1/1000"); + sb.AppendLine($"START={totalDurationMs}"); + sb.AppendLine($"END={totalDurationMs + chapter.DurationMs}"); + sb.AppendLine($"title={chapter.Title}"); + totalDurationMs += chapter.DurationMs; + } + + return sb.ToString(); + } + + + private class FFMetadataChapter(string title, long durationMs) + { + public string Title { get; } = title; + public long DurationMs { get; } = durationMs; + } +} From 62e829d9b484052ffea58a1b3302df72ac36b3d7 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 25 Oct 2025 11:25:43 +0200 Subject: [PATCH 02/13] Add AddMetaData overload accepting FFMetadataBuilder instance --- FFMpegCore/FFMpeg/FFMpegArguments.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/FFMpegCore/FFMpeg/FFMpegArguments.cs b/FFMpegCore/FFMpeg/FFMpegArguments.cs index 4c3b7bf..927442d 100644 --- a/FFMpegCore/FFMpeg/FFMpegArguments.cs +++ b/FFMpegCore/FFMpeg/FFMpegArguments.cs @@ -109,6 +109,11 @@ public sealed class FFMpegArguments : FFMpegArgumentsBase return WithInput(new MetaDataArgument(content), addArguments); } + public FFMpegArguments AddMetaData(FFMetadataBuilder metaDataBuilder, Action? addArguments = null) + { + return WithInput(new MetaDataArgument(metaDataBuilder.GetMetadataFileContent()), addArguments); + } + public FFMpegArguments AddMetaData(IReadOnlyMetaData metaData, Action? addArguments = null) { return WithInput(new MetaDataArgument(MetaDataSerializer.Instance.Serialize(metaData)), addArguments); From ef313ea411f5600a5d87ee5f5a494a20f9d6b12f Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 25 Oct 2025 11:25:52 +0200 Subject: [PATCH 03/13] Add test verifying functionality --- FFMpegCore.Test/VideoTest.cs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index 1f54d9f..accba60 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -82,6 +82,40 @@ public class VideoTest Assert.IsTrue(success); } + [TestMethod] + [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)] + public async Task Video_MetadataBuilder() + { + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + await FFMpegArguments + .FromFileInput(TestResources.WebmVideo) + .AddMetaData(FFMetadataBuilder.Empty() + .WithTag("title", "noname") + .WithTag("artist", "unknown") + .WithChapter("Chapter 1", 1.1) + .WithChapter("Chapter 2", 1.23)) + .OutputToFile(outputFile, false, opt => opt + .WithVideoCodec(VideoCodec.LibX264)) + .CancellableThrough(TestContext.CancellationToken) + .ProcessAsynchronously(); + + var analysis = await FFProbe.AnalyseAsync(outputFile, cancellationToken: TestContext.CancellationToken); + Assert.IsTrue(analysis.Format.Tags!.TryGetValue("title", out var title)); + Assert.IsTrue(analysis.Format.Tags!.TryGetValue("artist", out var artist)); + Assert.AreEqual("noname", title); + Assert.AreEqual("unknown", artist); + + Assert.HasCount(2, analysis.Chapters); + Assert.AreEqual("Chapter 1", analysis.Chapters.First().Title); + Assert.AreEqual(1.1, analysis.Chapters.First().Duration.TotalSeconds); + Assert.AreEqual(1.1, analysis.Chapters.First().End.TotalSeconds); + + Assert.AreEqual("Chapter 2", analysis.Chapters.Last().Title); + Assert.AreEqual(1.23, analysis.Chapters.Last().Duration.TotalSeconds); + Assert.AreEqual(1.1 + 1.23, analysis.Chapters.Last().End.TotalSeconds); + } + [TestMethod] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)] public void Video_ToH265_MKV_Args() From 15acd9f0dac97f5795f7215611400d30cf748145 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 25 Oct 2025 11:28:47 +0200 Subject: [PATCH 04/13] Add BOM --- FFMpegCore/FFMetadataInputArgument.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FFMpegCore/FFMetadataInputArgument.cs b/FFMpegCore/FFMetadataInputArgument.cs index 68a2c2e..cfd16c9 100644 --- a/FFMpegCore/FFMetadataInputArgument.cs +++ b/FFMpegCore/FFMetadataInputArgument.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace FFMpegCore; From 53445322e41d341513ce3afc9eb5cc6cc575f2a1 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 25 Oct 2025 11:36:40 +0200 Subject: [PATCH 05/13] Fix linting --- FFMpegCore/FFMetadataInputArgument.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/FFMpegCore/FFMetadataInputArgument.cs b/FFMpegCore/FFMetadataInputArgument.cs index cfd16c9..f2fce59 100644 --- a/FFMpegCore/FFMetadataInputArgument.cs +++ b/FFMpegCore/FFMetadataInputArgument.cs @@ -54,7 +54,6 @@ public class FFMetadataBuilder return sb.ToString(); } - private class FFMetadataChapter(string title, long durationMs) { public string Title { get; } = title; From 930d493b8c0a2735537922dd1d53a69829d7e6f5 Mon Sep 17 00:00:00 2001 From: Sergey Nechaev <6499856+snechaev@users.noreply.github.com> Date: Wed, 22 Oct 2025 20:32:30 +0200 Subject: [PATCH 06/13] Add test to verify unexpected exception on FFProbe operations cancellation. Ref.: #594 --- FFMpegCore.Test/FFProbeTests.cs | 57 +++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/FFMpegCore.Test/FFProbeTests.cs b/FFMpegCore.Test/FFProbeTests.cs index a702eed..16f3b4c 100644 --- a/FFMpegCore.Test/FFProbeTests.cs +++ b/FFMpegCore.Test/FFProbeTests.cs @@ -285,4 +285,61 @@ public class FFProbeTests var info = FFProbe.Analyse(TestResources.Mp4Video, customArguments: "-headers \"Hello: World\""); Assert.AreEqual(3, info.Duration.Seconds); } + + [TestMethod] + [Timeout(10000, CooperativeCancellation = true)] + public async Task Parallel_FFProbe_Cancellation_Should_Throw_Only_OperationCanceledException() + { + // Warm up FFMpegCore environment + Helpers.FFProbeHelper.VerifyFFProbeExists(GlobalFFOptions.Current); + + var mp4 = TestResources.Mp4Video; + if (!File.Exists(mp4)) + { + Assert.Inconclusive($"Test video not found: {mp4}"); + return; + } + + var cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.CancellationToken); + var token = cts.Token; + using var semaphore = new SemaphoreSlim(Environment.ProcessorCount, Environment.ProcessorCount); + var tasks = Enumerable.Range(0, 50).Select(x => Task.Run(async () => + { + await semaphore.WaitAsync(token); + try + { + var analysis = await FFProbe.AnalyseAsync(mp4, cancellationToken: token); + return analysis; + } + finally + { + semaphore.Release(); + } + }, token)).ToList(); + + // Wait for 2 tasks to finish, then cancel all + await Task.WhenAny(tasks); + await Task.WhenAny(tasks); + await cts.CancelAsync(); + cts.Dispose(); + + var exceptions = new List(); + foreach (var task in tasks) + { + try + { + await task; + } + catch (Exception e) + { + exceptions.Add(e); + } + } + + Assert.IsNotEmpty(exceptions, "No exceptions were thrown on cancellation. Test was useless. " + + ".Try adjust cancellation timings to make cancellation at the moment, when ffprobe is still running."); + + // Check that all exceptions are OperationCanceledException + CollectionAssert.AllItemsAreInstancesOfType(exceptions, typeof(OperationCanceledException)); + } } From b863f5d19e876cb3527e05436b3ef4377406dcd0 Mon Sep 17 00:00:00 2001 From: Sergey Nechaev <6499856+snechaev@users.noreply.github.com> Date: Wed, 22 Oct 2025 20:41:26 +0200 Subject: [PATCH 07/13] FFProbe: Do not throw FFMpegException if cancellation was requested. Throw OperationCancelledException in this case to provide more uniform and expected behavior. Fixes #594 --- FFMpegCore/FFProbe/FFProbe.cs | 11 +++++++---- FFMpegCore/FFProbe/ProcessArgumentsExtensions.cs | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/FFMpegCore/FFProbe/FFProbe.cs b/FFMpegCore/FFProbe/FFProbe.cs index e199376..ba08346 100644 --- a/FFMpegCore/FFProbe/FFProbe.cs +++ b/FFMpegCore/FFProbe/FFProbe.cs @@ -84,7 +84,7 @@ public static class FFProbe var instance = PrepareStreamAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current, customArguments); var result = await instance.StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(false); - ThrowIfExitCodeNotZero(result); + ThrowIfExitCodeNotZero(result, cancellationToken); return ParseOutput(result); } @@ -123,7 +123,7 @@ public static class FFProbe { var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, ffOptions ?? GlobalFFOptions.Current, customArguments); var result = await instance.StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(false); - ThrowIfExitCodeNotZero(result); + ThrowIfExitCodeNotZero(result, cancellationToken); return ParseOutput(result); } @@ -150,7 +150,7 @@ public static class FFProbe } var result = await task.ConfigureAwait(false); - ThrowIfExitCodeNotZero(result); + ThrowIfExitCodeNotZero(result, cancellationToken); pipeArgument.Post(); return ParseOutput(result); @@ -212,8 +212,11 @@ public static class FFProbe } } - private static void ThrowIfExitCodeNotZero(IProcessResult result) + private static void ThrowIfExitCodeNotZero(IProcessResult result, CancellationToken cancellationToken = default) { + // if cancellation requested, then we are not interested in the exit code, just throw the cancellation exception + // to get consistent and expected behavior. + cancellationToken.ThrowIfCancellationRequested(); if (result.ExitCode != 0) { var message = $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})"; diff --git a/FFMpegCore/FFProbe/ProcessArgumentsExtensions.cs b/FFMpegCore/FFProbe/ProcessArgumentsExtensions.cs index ef7140a..6682e94 100644 --- a/FFMpegCore/FFProbe/ProcessArgumentsExtensions.cs +++ b/FFMpegCore/FFProbe/ProcessArgumentsExtensions.cs @@ -13,6 +13,6 @@ public static class ProcessArgumentsExtensions public static async Task StartAndWaitForExitAsync(this ProcessArguments processArguments, CancellationToken cancellationToken = default) { using var instance = processArguments.Start(); - return await instance.WaitForExitAsync(cancellationToken); + return await instance.WaitForExitAsync(cancellationToken).ConfigureAwait(false); } } From e44611bd25360df725d9331ca6bbffef3e71ad72 Mon Sep 17 00:00:00 2001 From: Sergey Nechaev <6499856+snechaev@users.noreply.github.com> Date: Wed, 22 Oct 2025 21:23:30 +0200 Subject: [PATCH 08/13] Additional test to verify that FFProbeHelper still throws FFMpegException when FFProbe exits with non-zero code and no cancellation was requested. Ref.: #594 --- FFMpegCore.Test/FFProbeTests.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/FFMpegCore.Test/FFProbeTests.cs b/FFMpegCore.Test/FFProbeTests.cs index 16f3b4c..ee837bf 100644 --- a/FFMpegCore.Test/FFProbeTests.cs +++ b/FFMpegCore.Test/FFProbeTests.cs @@ -1,4 +1,5 @@ -using FFMpegCore.Test.Resources; +using FFMpegCore.Exceptions; +using FFMpegCore.Test.Resources; namespace FFMpegCore.Test; @@ -342,4 +343,13 @@ public class FFProbeTests // Check that all exceptions are OperationCanceledException CollectionAssert.AllItemsAreInstancesOfType(exceptions, typeof(OperationCanceledException)); } + + [TestMethod] + [Timeout(10000, CooperativeCancellation = true)] + public async Task FFProbe_Should_Throw_FFMpegException_When_Exits_With_Non_Zero_Code() + { + var input = TestResources.SrtSubtitle; //non media file + await Assert.ThrowsAsync(async () => await FFProbe.AnalyseAsync(input, + cancellationToken: TestContext.CancellationToken, customArguments: "--some-invalid-argument")); + } } From 560c791802d3c7b799aced9ab9dca2f7b76e6968 Mon Sep 17 00:00:00 2001 From: Sergey Nechaev <6499856+snechaev@users.noreply.github.com> Date: Fri, 24 Oct 2025 18:06:55 +0200 Subject: [PATCH 09/13] Update the `ThrowIfExitCodeNotZero()` to check the exit code before handling cancellation. This preserves the original semantics and contract (throw only if the ffprobe exits with a non-zero code). --- FFMpegCore/FFProbe/FFProbe.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FFMpegCore/FFProbe/FFProbe.cs b/FFMpegCore/FFProbe/FFProbe.cs index ba08346..5f275d2 100644 --- a/FFMpegCore/FFProbe/FFProbe.cs +++ b/FFMpegCore/FFProbe/FFProbe.cs @@ -214,11 +214,11 @@ public static class FFProbe private static void ThrowIfExitCodeNotZero(IProcessResult result, CancellationToken cancellationToken = default) { - // if cancellation requested, then we are not interested in the exit code, just throw the cancellation exception - // to get consistent and expected behavior. - cancellationToken.ThrowIfCancellationRequested(); if (result.ExitCode != 0) { + // if cancellation requested, then we are not interested in the exit code, just throw the cancellation exception + // to get consistent and expected behavior. + cancellationToken.ThrowIfCancellationRequested(); var message = $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})"; throw new FFMpegException(FFMpegExceptionType.Process, message, null, string.Join("\n", result.ErrorData)); } From 67af2aa01dff975fd5c328a8a2025b6a109fd687 Mon Sep 17 00:00:00 2001 From: Sergey Nechaev <6499856+snechaev@users.noreply.github.com> Date: Mon, 27 Oct 2025 13:35:36 +0100 Subject: [PATCH 10/13] Move cancellation check outside of the `ThrowIfExitCodeNotZero()` and call it separately in all the async code paths. --- FFMpegCore/FFProbe/FFProbe.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/FFMpegCore/FFProbe/FFProbe.cs b/FFMpegCore/FFProbe/FFProbe.cs index 5f275d2..164ea72 100644 --- a/FFMpegCore/FFProbe/FFProbe.cs +++ b/FFMpegCore/FFProbe/FFProbe.cs @@ -84,7 +84,8 @@ public static class FFProbe var instance = PrepareStreamAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current, customArguments); var result = await instance.StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(false); - ThrowIfExitCodeNotZero(result, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfExitCodeNotZero(result); return ParseOutput(result); } @@ -123,7 +124,8 @@ public static class FFProbe { var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, ffOptions ?? GlobalFFOptions.Current, customArguments); var result = await instance.StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(false); - ThrowIfExitCodeNotZero(result, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfExitCodeNotZero(result); return ParseOutput(result); } @@ -150,7 +152,8 @@ public static class FFProbe } var result = await task.ConfigureAwait(false); - ThrowIfExitCodeNotZero(result, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + ThrowIfExitCodeNotZero(result); pipeArgument.Post(); return ParseOutput(result); @@ -212,13 +215,10 @@ public static class FFProbe } } - private static void ThrowIfExitCodeNotZero(IProcessResult result, CancellationToken cancellationToken = default) + private static void ThrowIfExitCodeNotZero(IProcessResult result) { if (result.ExitCode != 0) { - // if cancellation requested, then we are not interested in the exit code, just throw the cancellation exception - // to get consistent and expected behavior. - cancellationToken.ThrowIfCancellationRequested(); var message = $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})"; throw new FFMpegException(FFMpegExceptionType.Process, message, null, string.Join("\n", result.ErrorData)); } From 919c6ef526e39c6fcaf28c4073c8cfcbeeed4eff Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Mon, 27 Oct 2025 19:38:58 +0100 Subject: [PATCH 11/13] Use CreateLinkedTokenSource to bind to TestContext cancellationtoken --- FFMpegCore.Test/VideoTest.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index accba60..e338324 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -1050,7 +1050,7 @@ public class VideoTest { using var outputFile = new TemporaryFile("out.mp4"); - var cts = new CancellationTokenSource(); + using var cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.CancellationToken); var task = FFMpegArguments .FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args @@ -1061,7 +1061,6 @@ public class VideoTest .WithVideoCodec(VideoCodec.LibX264) .WithSpeedPreset(Speed.VeryFast)) .CancellableThrough(cts.Token) - .CancellableThrough(TestContext.CancellationToken) .ProcessAsynchronously(false); cts.CancelAfter(300); @@ -1077,7 +1076,7 @@ public class VideoTest { using var outputFile = new TemporaryFile("out.mp4"); - var cts = new CancellationTokenSource(); + using var cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.CancellationToken); var task = FFMpegArguments .FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args @@ -1088,7 +1087,6 @@ public class VideoTest .WithVideoCodec(VideoCodec.LibX264) .WithSpeedPreset(Speed.VeryFast)) .CancellableThrough(cts.Token) - .CancellableThrough(TestContext.CancellationToken) .ProcessAsynchronously(); cts.CancelAfter(300); @@ -1102,7 +1100,7 @@ public class VideoTest { using var outputFile = new TemporaryFile("out.mp4"); - var cts = new CancellationTokenSource(); + using var cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.CancellationToken); var task = FFMpegArguments .FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args @@ -1126,7 +1124,7 @@ public class VideoTest { using var outputFile = new TemporaryFile("out.mp4"); - var cts = new CancellationTokenSource(); + using var cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.CancellationToken); cts.Cancel(); var task = FFMpegArguments @@ -1149,7 +1147,7 @@ public class VideoTest { using var outputFile = new TemporaryFile("out.mp4"); - var cts = new CancellationTokenSource(); + using var cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.CancellationToken); var task = FFMpegArguments .FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args From 3c8d2c23c1f923c255ddd378f7cfa2c5b92f0663 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Mon, 27 Oct 2025 19:39:13 +0100 Subject: [PATCH 12/13] Use using for CancellationTokenSource --- FFMpegCore.Test/FFProbeTests.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/FFMpegCore.Test/FFProbeTests.cs b/FFMpegCore.Test/FFProbeTests.cs index ee837bf..2ee5a92 100644 --- a/FFMpegCore.Test/FFProbeTests.cs +++ b/FFMpegCore.Test/FFProbeTests.cs @@ -1,4 +1,5 @@ using FFMpegCore.Exceptions; +using FFMpegCore.Helpers; using FFMpegCore.Test.Resources; namespace FFMpegCore.Test; @@ -292,7 +293,7 @@ public class FFProbeTests public async Task Parallel_FFProbe_Cancellation_Should_Throw_Only_OperationCanceledException() { // Warm up FFMpegCore environment - Helpers.FFProbeHelper.VerifyFFProbeExists(GlobalFFOptions.Current); + FFProbeHelper.VerifyFFProbeExists(GlobalFFOptions.Current); var mp4 = TestResources.Mp4Video; if (!File.Exists(mp4)) @@ -301,28 +302,26 @@ public class FFProbeTests return; } - var cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.CancellationToken); - var token = cts.Token; + using var cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.CancellationToken); using var semaphore = new SemaphoreSlim(Environment.ProcessorCount, Environment.ProcessorCount); var tasks = Enumerable.Range(0, 50).Select(x => Task.Run(async () => { - await semaphore.WaitAsync(token); + await semaphore.WaitAsync(cts.Token); try { - var analysis = await FFProbe.AnalyseAsync(mp4, cancellationToken: token); + var analysis = await FFProbe.AnalyseAsync(mp4, cancellationToken: cts.Token); return analysis; } finally { semaphore.Release(); } - }, token)).ToList(); + }, cts.Token)).ToList(); // Wait for 2 tasks to finish, then cancel all await Task.WhenAny(tasks); await Task.WhenAny(tasks); await cts.CancelAsync(); - cts.Dispose(); var exceptions = new List(); foreach (var task in tasks) From d916fd3be4e41ae60f54dfddaf3d0e2ebe4b5281 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Mon, 27 Oct 2025 19:53:39 +0100 Subject: [PATCH 13/13] Update nuget details --- FFMpegCore/FFMpegCore.csproj | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj index 671239d..df567ad 100644 --- a/FFMpegCore/FFMpegCore.csproj +++ b/FFMpegCore/FFMpegCore.csproj @@ -3,13 +3,14 @@ true A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your .NET applications - 5.3.0 + 5.4.0 ../nupkg - - **Fixed race condition on Named pipe dispose/disconnect** by techtel-pstevens - - **More extensions for snapshot function(jpg, bmp, webp)** by GorobVictor - - **Include more GUID characters in pipe path** by reima, rosenbjerg - - **Updated dependencies and minor cleanup**: by rosenbjerg + - Fixed exception thrown on cancelling ffprobe analysis - by snechaev + - Added FFMetadataBuilder - by rosenbjerg + - Fix JoinImageSequence by passing framerate argument to input as well as output - by rosenbjerg + - Change fps input from int to double - by rosenbjerg + - Fix GetCreationTime method on ITagsContainer - by rosenbjerg ffmpeg ffprobe convert video audio mediafile resize analyze muxing Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev