From 5d62be654aab099556ecbe651d65bbf726079349 Mon Sep 17 00:00:00 2001 From: keg247 <44041557+keg247@users.noreply.github.com> Date: Sun, 19 Jun 2022 23:31:18 -0400 Subject: [PATCH 01/23] Move NotifyOnProgress processing to ErrorData --- FFMpegCore.Test/VideoTest.cs | 66 +++++++++++--------- FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs | 32 +++++----- 2 files changed, 54 insertions(+), 44 deletions(-) diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index 262bd75..8f73575 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -1,4 +1,7 @@ -using FFMpegCore.Enums; +using FFMpegCore.Arguments; +using FFMpegCore.Enums; +using FFMpegCore.Exceptions; +using FFMpegCore.Pipes; using FFMpegCore.Test.Resources; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; @@ -8,11 +11,8 @@ using System.IO; using System.Linq; using System.Text; -using System.Threading.Tasks; -using FFMpegCore.Arguments; -using FFMpegCore.Exceptions; -using FFMpegCore.Pipes; using System.Threading; +using System.Threading.Tasks; namespace FFMpegCore.Test { @@ -23,7 +23,7 @@ public class VideoTest public void Video_ToOGV() { using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}"); - + var success = FFMpegArguments .FromFileInput(TestResources.WebmVideo) .OutputToFile(outputFile, false) @@ -35,7 +35,7 @@ public void Video_ToOGV() public void Video_ToMP4() { using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); - + var success = FFMpegArguments .FromFileInput(TestResources.WebmVideo) .OutputToFile(outputFile, false) @@ -47,7 +47,7 @@ public void Video_ToMP4() public void Video_ToMP4_YUV444p() { using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); - + var success = FFMpegArguments .FromFileInput(TestResources.WebmVideo) .OutputToFile(outputFile, false, opt => opt @@ -63,7 +63,7 @@ public void Video_ToMP4_YUV444p() public void Video_ToMP4_Args() { using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); - + var success = FFMpegArguments .FromFileInput(TestResources.WebmVideo) .OutputToFile(outputFile, false, opt => opt @@ -76,7 +76,7 @@ public void Video_ToMP4_Args() public void Video_ToH265_MKV_Args() { using var outputFile = new TemporaryFile($"out.mkv"); - + var success = FFMpegArguments .FromFileInput(TestResources.WebmVideo) .OutputToFile(outputFile, false, opt => opt @@ -106,7 +106,7 @@ public void Video_ToMP4_Args_Pipe_DifferentImageSizes() { using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); - var frames = new List + var frames = new List { BitmapSource.CreateVideoFrame(0, System.Drawing.Imaging.PixelFormat.Format24bppRgb, 255, 255, 1, 0), BitmapSource.CreateVideoFrame(0, System.Drawing.Imaging.PixelFormat.Format24bppRgb, 256, 256, 1, 0) @@ -184,7 +184,7 @@ public void Video_ToMP4_Args_StreamPipe() { using var input = File.OpenRead(TestResources.WebmVideo); using var output = new TemporaryFile($"out{VideoType.Mp4.Extension}"); - + var success = FFMpegArguments .FromPipeInput(new StreamPipeSource(input)) .OutputToFile(output, false, opt => opt @@ -260,12 +260,12 @@ public async Task TestDuplicateRun() .FromFileInput(TestResources.Mp4Video) .OutputToFile("temporary.mp4") .ProcessSynchronously(); - + await FFMpegArguments .FromFileInput(TestResources.Mp4Video) .OutputToFile("temporary.mp4") .ProcessAsynchronously(); - + File.Delete("temporary.mp4"); } @@ -291,7 +291,7 @@ public void TranscodeToMemoryStream_Success() public void Video_ToTS() { using var outputFile = new TemporaryFile($"out{VideoType.MpegTs.Extension}"); - + var success = FFMpegArguments .FromFileInput(TestResources.Mp4Video) .OutputToFile(outputFile, false) @@ -303,7 +303,7 @@ public void Video_ToTS() public void Video_ToTS_Args() { using var outputFile = new TemporaryFile($"out{VideoType.MpegTs.Extension}"); - + var success = FFMpegArguments .FromFileInput(TestResources.Mp4Video) .OutputToFile(outputFile, false, opt => opt @@ -321,7 +321,7 @@ public async Task Video_ToTS_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelF { using var output = new TemporaryFile($"out{VideoType.Ts.Extension}"); var input = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, pixelFormat, 256, 256)); - + var success = await FFMpegArguments .FromPipeInput(input) .OutputToFile(output, false, opt => opt @@ -354,7 +354,7 @@ public void RawVideoPipeSource_Ogv_Scale(System.Drawing.Imaging.PixelFormat pixe { using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}"); var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, pixelFormat, 256, 256)); - + FFMpegArguments .FromPipeInput(videoFramesSource) .OutputToFile(outputFile, false, opt => opt @@ -371,7 +371,7 @@ public void RawVideoPipeSource_Ogv_Scale(System.Drawing.Imaging.PixelFormat pixe public void Scale_Mp4_Multithreaded() { using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); - + var success = FFMpegArguments .FromFileInput(TestResources.Mp4Video) .OutputToFile(outputFile, false, opt => opt @@ -389,7 +389,7 @@ public void Video_ToMP4_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixe { using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, pixelFormat, 256, 256)); - + var success = FFMpegArguments .FromPipeInput(videoFramesSource) .OutputToFile(outputFile, false, opt => opt @@ -403,7 +403,7 @@ public void Video_Snapshot_InMemory() { var input = FFProbe.Analyse(TestResources.Mp4Video); using var bitmap = FFMpeg.Snapshot(TestResources.Mp4Video); - + Assert.AreEqual(input.PrimaryVideoStream!.Width, bitmap.Width); Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height); Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png); @@ -428,13 +428,13 @@ public void Video_Join() { var inputCopy = new TemporaryFile("copy-input.mp4"); File.Copy(TestResources.Mp4Video, inputCopy); - + var outputPath = new TemporaryFile("out.mp4"); var input = FFProbe.Analyse(TestResources.Mp4Video); var success = FFMpeg.Join(outputPath, TestResources.Mp4Video, inputCopy); Assert.IsTrue(success); Assert.IsTrue(File.Exists(outputPath)); - + var expectedDuration = input.Duration * 2; var result = FFProbe.Analyse(outputPath); Assert.AreEqual(expectedDuration.Days, result.Duration.Days); @@ -505,14 +505,22 @@ public void Video_UpdatesProgress() var percentageDone = 0.0; var timeDone = TimeSpan.Zero; - void OnPercentageProgess(double percentage) => percentageDone = percentage; - void OnTimeProgess(TimeSpan time) => timeDone = time; - var analysis = FFProbe.Analyse(TestResources.Mp4Video); + + void OnPercentageProgess(double percentage) + { + if (percentage < 100) percentageDone = percentage; + } + + void OnTimeProgess(TimeSpan time) + { + if (time < analysis.Duration) timeDone = time; + } + var success = FFMpegArguments .FromFileInput(TestResources.Mp4Video) .OutputToFile(outputFile, false, opt => opt - .WithDuration(TimeSpan.FromSeconds(2))) + .WithDuration(analysis.Duration)) .NotifyOnProgress(OnPercentageProgess, analysis.Duration) .NotifyOnProgress(OnTimeProgess) .ProcessSynchronously(); @@ -520,7 +528,9 @@ public void Video_UpdatesProgress() Assert.IsTrue(success); Assert.IsTrue(File.Exists(outputFile)); Assert.AreNotEqual(0.0, percentageDone); + Assert.AreNotEqual(100.0, percentageDone); Assert.AreNotEqual(TimeSpan.Zero, timeDone); + Assert.AreNotEqual(analysis.Duration, timeDone); } [TestMethod, Timeout(10000)] @@ -528,7 +538,7 @@ public void Video_OutputsData() { var outputFile = new TemporaryFile("out.mp4"); var dataReceived = false; - + GlobalFFOptions.Configure(opt => opt.Encoding = Encoding.UTF8); var success = FFMpegArguments .FromFileInput(TestResources.Mp4Video) diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs index 43ace4d..dd2a5e9 100644 --- a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs +++ b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs @@ -1,13 +1,13 @@ -using System; +using FFMpegCore.Exceptions; +using FFMpegCore.Helpers; +using Instances; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using FFMpegCore.Exceptions; -using FFMpegCore.Helpers; -using Instances; namespace FFMpegCore { @@ -88,7 +88,7 @@ public bool ProcessSynchronously(bool throwOnError = true, FFOptions? ffMpegOpti var options = GetConfiguredOptions(ffMpegOptions); var processArguments = PrepareProcessArguments(options, out var cancellationTokenSource); - + IProcessResult? processResult = null; try { @@ -107,7 +107,7 @@ public async Task ProcessAsynchronously(bool throwOnError = true, FFOption { var options = GetConfiguredOptions(ffMpegOptions); var processArguments = PrepareProcessArguments(options, out var cancellationTokenSource); - + IProcessResult? processResult = null; try { @@ -118,7 +118,7 @@ public async Task ProcessAsynchronously(bool throwOnError = true, FFOption if (throwOnError) throw; } - + return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty()); } @@ -204,10 +204,10 @@ private ProcessArguments PrepareProcessArguments(FFOptions ffOptions, var processArguments = new ProcessArguments(startInfo); cancellationTokenSource = new CancellationTokenSource(); - if (_onOutput != null || _onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan != null)) + if (_onOutput != null) processArguments.OutputDataReceived += OutputData; - - if (_onError != null) + + if (_onError != null || _onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan != null)) processArguments.ErrorDataReceived += ErrorData; return processArguments; @@ -216,12 +216,6 @@ private ProcessArguments PrepareProcessArguments(FFOptions ffOptions, private void ErrorData(object sender, string msg) { _onError?.Invoke(msg); - } - - private void OutputData(object sender, string msg) - { - Debug.WriteLine(msg); - _onOutput?.Invoke(msg); var match = ProgressRegex.Match(msg); if (!match.Success) return; @@ -233,5 +227,11 @@ private void OutputData(object sender, string msg) var percentage = Math.Round(processed.TotalSeconds / _totalTimespan.Value.TotalSeconds * 100, 2); _onPercentageProgress(percentage); } + + private void OutputData(object sender, string msg) + { + Debug.WriteLine(msg); + _onOutput?.Invoke(msg); + } } } \ No newline at end of file From 46eddd374845571c512f2dcf5a8569e12eb0716b Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 28 Jan 2023 11:08:55 +0100 Subject: [PATCH 02/23] Update ci.yml --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5afb8d4..efcacd3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,13 +1,6 @@ name: CI on: - push: - branches: - - master - paths: - - .github/workflows/ci.yml - - FFMpegCore/** - - FFMpegCore.Test/** pull_request: branches: - master @@ -25,13 +18,20 @@ jobs: os: [windows-latest, ubuntu-latest] timeout-minutes: 6 steps: + - name: Checkout uses: actions/checkout@v2 + - name: Prepare .NET uses: actions/setup-dotnet@v1 with: + repo-token: ${{ secrets.GITHUB_TOKEN }} dotnet-version: '6.0.x' + - name: Prepare FFMpeg uses: FedericoCarboni/setup-ffmpeg@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Test with dotnet run: dotnet test --logger GitHubActions From 0ffd550ebd44d976fdcc97936e890ef2c7e4cc98 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 28 Jan 2023 11:12:25 +0100 Subject: [PATCH 03/23] Fix argument --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index efcacd3..a1d5c8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,13 +25,12 @@ jobs: - name: Prepare .NET uses: actions/setup-dotnet@v1 with: - repo-token: ${{ secrets.GITHUB_TOKEN }} dotnet-version: '6.0.x' - name: Prepare FFMpeg uses: FedericoCarboni/setup-ffmpeg@v1 with: - repo-token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} - name: Test with dotnet run: dotnet test --logger GitHubActions From fafb01e02be024b987985169b4092d1c0eaa11b4 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 28 Jan 2023 11:25:28 +0100 Subject: [PATCH 04/23] Bump action versions --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1d5c8a..665df6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,15 +20,15 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Prepare .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: dotnet-version: '6.0.x' - name: Prepare FFMpeg - uses: FedericoCarboni/setup-ffmpeg@v1 + uses: FedericoCarboni/setup-ffmpeg@v2 with: token: ${{ secrets.GITHUB_TOKEN }} From 0537c87fa29b03b97ee9275726c9b71c85329d70 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 28 Jan 2023 11:28:07 +0100 Subject: [PATCH 05/23] Ignore Uri_Duration until root cause found --- FFMpegCore.Test/FFProbeTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FFMpegCore.Test/FFProbeTests.cs b/FFMpegCore.Test/FFProbeTests.cs index a4d836d..af7f08b 100644 --- a/FFMpegCore.Test/FFProbeTests.cs +++ b/FFMpegCore.Test/FFProbeTests.cs @@ -99,7 +99,7 @@ public void MediaAnalysis_ParseDuration(string duration, int expectedDays, int e Assert.AreEqual(expectedMilliseconds, parsedDuration.Milliseconds); } - [TestMethod] + [TestMethod, Ignore("Consistently fails on GitHub Workflow ubuntu agents")] public async Task Uri_Duration() { var fileAnalysis = await FFProbe.AnalyseAsync(new Uri("https://github.com/rosenbjerg/FFMpegCore/raw/master/FFMpegCore.Test/Resources/input_3sec.webm")); From dd958a3450e7addeb9c8e83b88626cb8c49d4f92 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 28 Jan 2023 11:37:34 +0100 Subject: [PATCH 06/23] Use action that can install specific ffmpeg version --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 665df6c..44c22ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,9 +28,10 @@ jobs: dotnet-version: '6.0.x' - name: Prepare FFMpeg - uses: FedericoCarboni/setup-ffmpeg@v2 + uses: Iamshankhadeep/setup-ffmpeg@v1.2 with: token: ${{ secrets.GITHUB_TOKEN }} + version: "4.4" - name: Test with dotnet run: dotnet test --logger GitHubActions From d85a4b81abaf4ab7832020cdb2ccfb46758d3bde Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 28 Jan 2023 11:39:44 +0100 Subject: [PATCH 07/23] Remove ignore --- FFMpegCore.Test/FFProbeTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FFMpegCore.Test/FFProbeTests.cs b/FFMpegCore.Test/FFProbeTests.cs index af7f08b..a4d836d 100644 --- a/FFMpegCore.Test/FFProbeTests.cs +++ b/FFMpegCore.Test/FFProbeTests.cs @@ -99,7 +99,7 @@ public void MediaAnalysis_ParseDuration(string duration, int expectedDays, int e Assert.AreEqual(expectedMilliseconds, parsedDuration.Milliseconds); } - [TestMethod, Ignore("Consistently fails on GitHub Workflow ubuntu agents")] + [TestMethod] public async Task Uri_Duration() { var fileAnalysis = await FFProbe.AnalyseAsync(new Uri("https://github.com/rosenbjerg/FFMpegCore/raw/master/FFMpegCore.Test/Resources/input_3sec.webm")); From d3d49655f47def3ef8f1dfe47cd92d5549395957 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 28 Jan 2023 11:41:19 +0100 Subject: [PATCH 08/23] Revert "Remove ignore" This reverts commit d85a4b81abaf4ab7832020cdb2ccfb46758d3bde. --- FFMpegCore.Test/FFProbeTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FFMpegCore.Test/FFProbeTests.cs b/FFMpegCore.Test/FFProbeTests.cs index a4d836d..af7f08b 100644 --- a/FFMpegCore.Test/FFProbeTests.cs +++ b/FFMpegCore.Test/FFProbeTests.cs @@ -99,7 +99,7 @@ public void MediaAnalysis_ParseDuration(string duration, int expectedDays, int e Assert.AreEqual(expectedMilliseconds, parsedDuration.Milliseconds); } - [TestMethod] + [TestMethod, Ignore("Consistently fails on GitHub Workflow ubuntu agents")] public async Task Uri_Duration() { var fileAnalysis = await FFProbe.AnalyseAsync(new Uri("https://github.com/rosenbjerg/FFMpegCore/raw/master/FFMpegCore.Test/Resources/input_3sec.webm")); From b84bb6470a190fa72db8182a84568fbfe3cddd3e Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 28 Jan 2023 13:25:48 +0100 Subject: [PATCH 09/23] Bump dependencies --- FFMpegCore.Test/FFMpegCore.Test.csproj | 13 ++++++++----- FFMpegCore/FFMpegCore.csproj | 6 +++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/FFMpegCore.Test/FFMpegCore.Test.csproj b/FFMpegCore.Test/FFMpegCore.Test.csproj index d281c3d..4ac890c 100644 --- a/FFMpegCore.Test/FFMpegCore.Test.csproj +++ b/FFMpegCore.Test/FFMpegCore.Test.csproj @@ -39,11 +39,14 @@ - - - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj index f40295c..79d191e 100644 --- a/FFMpegCore/FFMpegCore.csproj +++ b/FFMpegCore/FFMpegCore.csproj @@ -37,9 +37,9 @@ - - - + + + From f2abd9ae63e251f10f3053775a3b3e1ee8fba07f Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 28 Jan 2023 13:28:37 +0100 Subject: [PATCH 10/23] Use setup-dotnet@v2 since v3 seems to install .NET 7 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44c22ed..c470186 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v3 - name: Prepare .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v2 with: dotnet-version: '6.0.x' From 53651a02756adfb17f73f7c196b1a6fd6274ac9f Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Thu, 24 Mar 2022 20:19:37 +0100 Subject: [PATCH 11/23] Init --- .../FFMpegCore.Examples.csproj | 1 + FFMpegCore.Examples/Program.cs | 8 +- .../BitmapExtensions.cs | 2 +- .../BitmapVideoFrameWrapper.cs | 2 +- ...re.Extensions.System.Drawing.Common.csproj | 28 ++++ .../FFMpegImage.cs | 134 ++++++++++++++++++ .../ImageInfo.cs | 0 FFMpegCore.Test/AudioTest.cs | 3 +- FFMpegCore.Test/FFMpegCore.Test.csproj | 1 + FFMpegCore.Test/Utilities/BitmapSources.cs | 4 +- FFMpegCore.Test/VideoTest.cs | 9 +- FFMpegCore.sln | 6 + FFMpegCore/FFMpeg/FFMpeg.cs | 48 ------- FFMpegCore/FFMpegCore.csproj | 1 - FFMpegCore/Helpers/FFMpegHelper.cs | 5 +- 15 files changed, 186 insertions(+), 66 deletions(-) rename {FFMpegCore/Extend => FFMpegCore.Extensions.System.Drawing.Common}/BitmapExtensions.cs (91%) rename {FFMpegCore/Extend => FFMpegCore.Extensions.System.Drawing.Common}/BitmapVideoFrameWrapper.cs (98%) create mode 100644 FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj create mode 100644 FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs rename {FFMpegCore => FFMpegCore.Extensions.System.Drawing.Common}/ImageInfo.cs (100%) diff --git a/FFMpegCore.Examples/FFMpegCore.Examples.csproj b/FFMpegCore.Examples/FFMpegCore.Examples.csproj index 68e7b5c..347607f 100644 --- a/FFMpegCore.Examples/FFMpegCore.Examples.csproj +++ b/FFMpegCore.Examples/FFMpegCore.Examples.csproj @@ -6,6 +6,7 @@ + diff --git a/FFMpegCore.Examples/Program.cs b/FFMpegCore.Examples/Program.cs index a718a21..ea343f2 100644 --- a/FFMpegCore.Examples/Program.cs +++ b/FFMpegCore.Examples/Program.cs @@ -5,7 +5,7 @@ using FFMpegCore; using FFMpegCore.Enums; using FFMpegCore.Pipes; -using FFMpegCore.Extend; +using FFMpegCore.Extensions.System.Drawing.Common; var inputPath = "/path/to/input"; var outputPath = "/path/to/output"; @@ -34,7 +34,7 @@ { // process the snapshot in-memory and use the Bitmap directly - var bitmap = FFMpeg.Snapshot(inputPath, new Size(200, 400), TimeSpan.FromMinutes(1)); + var bitmap = FFMpegImage.Snapshot(inputPath, new Size(200, 400), TimeSpan.FromMinutes(1)); // or persists the image on the drive FFMpeg.Snapshot(inputPath, outputPath, new Size(200, 400), TimeSpan.FromMinutes(1)); @@ -61,7 +61,7 @@ await FFMpegArguments } { - FFMpeg.JoinImageSequence(@"..\joined_video.mp4", frameRate: 1, + FFMpegImage.JoinImageSequence(@"..\joined_video.mp4", frameRate: 1, ImageInfo.FromPath(@"..\1.png"), ImageInfo.FromPath(@"..\2.png"), ImageInfo.FromPath(@"..\3.png") @@ -83,7 +83,7 @@ await FFMpegArguments var inputImagePath = "/path/to/input/image"; { - FFMpeg.PosterWithAudio(inputPath, inputAudioPath, outputPath); + FFMpegImage.PosterWithAudio(inputPath, inputAudioPath, outputPath); // or var image = Image.FromFile(inputImagePath); image.AddAudio(inputAudioPath, outputPath); diff --git a/FFMpegCore/Extend/BitmapExtensions.cs b/FFMpegCore.Extensions.System.Drawing.Common/BitmapExtensions.cs similarity index 91% rename from FFMpegCore/Extend/BitmapExtensions.cs rename to FFMpegCore.Extensions.System.Drawing.Common/BitmapExtensions.cs index e2f5505..e549580 100644 --- a/FFMpegCore/Extend/BitmapExtensions.cs +++ b/FFMpegCore.Extensions.System.Drawing.Common/BitmapExtensions.cs @@ -2,7 +2,7 @@ using System.Drawing; using System.IO; -namespace FFMpegCore.Extend +namespace FFMpegCore.Extensions.System.Drawing.Common { public static class BitmapExtensions { diff --git a/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs b/FFMpegCore.Extensions.System.Drawing.Common/BitmapVideoFrameWrapper.cs similarity index 98% rename from FFMpegCore/Extend/BitmapVideoFrameWrapper.cs rename to FFMpegCore.Extensions.System.Drawing.Common/BitmapVideoFrameWrapper.cs index 2222db6..2259fea 100644 --- a/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs +++ b/FFMpegCore.Extensions.System.Drawing.Common/BitmapVideoFrameWrapper.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using FFMpegCore.Pipes; -namespace FFMpegCore.Extend +namespace FFMpegCore.Extensions.System.Drawing.Common { public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable { diff --git a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj new file mode 100644 index 0000000..beeb939 --- /dev/null +++ b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj @@ -0,0 +1,28 @@ + + + + en + https://github.com/rosenbjerg/FFMpegCore + https://github.com/rosenbjerg/FFMpegCore + + Image extension for FFMpegCore, using System.Common.Drawing + + + 8 + 4.0.0.0 + 4.0.0 + MIT + Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev + ffmpeg ffprobe convert video audio mediafile resize analyze muxing + GitHub + true + enable + netstandard2.0 + + + + + + + + diff --git a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs new file mode 100644 index 0000000..19fd16e --- /dev/null +++ b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs @@ -0,0 +1,134 @@ +using System; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using FFMpegCore.Enums; +using FFMpegCore.Helpers; +using FFMpegCore.Pipes; + +namespace FFMpegCore.Extensions.System.Drawing.Common +{ + public static class FFMpegImage + { + public static void ConversionSizeExceptionCheck(Image image) + => FFMpegHelper.ConversionSizeExceptionCheck(image.Size.Width, image.Size.Height); + + /// + /// Converts an image sequence to a video. + /// + /// Output video file. + /// FPS + /// Image sequence collection + /// Output video information. + public static bool JoinImageSequence(string output, double frameRate = 30, params ImageInfo[] images) + { + var tempFolderName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, Guid.NewGuid().ToString()); + var temporaryImageFiles = images.Select((imageInfo, index) => + { + using var image = Image.FromFile(imageInfo.FullName); + FFMpegHelper.ConversionSizeExceptionCheck(image); + var destinationPath = Path.Combine(tempFolderName, $"{index.ToString().PadLeft(9, '0')}{imageInfo.Extension}"); + Directory.CreateDirectory(tempFolderName); + File.Copy(imageInfo.FullName, destinationPath); + return destinationPath; + }).ToArray(); + + var firstImage = images.First(); + try + { + return FFMpegArguments + .FromFileInput(Path.Combine(tempFolderName, "%09d.png"), false) + .OutputToFile(output, true, options => options + .Resize(firstImage.Width, firstImage.Height) + .WithFramerate(frameRate)) + .ProcessSynchronously(); + } + finally + { + Cleanup(temporaryImageFiles); + Directory.Delete(tempFolderName); + } + } + /// + /// Adds a poster image to an audio file. + /// + /// Source image file. + /// Source audio file. + /// Output video file. + /// + public static bool PosterWithAudio(string image, string audio, string output) + { + FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4); + using (var img = Image.FromFile(image)) + FFMpegHelper.ConversionSizeExceptionCheck(img); + + return FFMpegArguments + .FromFileInput(image, false, options => options + .Loop(1)) + .AddFileInput(audio) + .OutputToFile(output, true, options => options + .WithVideoCodec(VideoCodec.LibX264) + .CopyChannel() + .WithConstantRateFactor(21) + .WithAudioBitrate(AudioQuality.Normal) + .UsingShortest()) + .ProcessSynchronously(); + } + /// + /// Saves a 'png' thumbnail to an in-memory bitmap + /// + /// Source video file. + /// Seek position where the thumbnail should be taken. + /// Thumbnail size. If width or height equal 0, the other will be computed automatically. + /// Selected video stream index. + /// Input file index + /// Bitmap with the requested snapshot. + public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) + { + var source = FFProbe.Analyse(input); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); + using var ms = new MemoryStream(); + + arguments + .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options + .ForceFormat("rawvideo"))) + .ProcessSynchronously(); + + ms.Position = 0; + using var bitmap = new Bitmap(ms); + return bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat); + } + /// + /// Saves a 'png' thumbnail to an in-memory bitmap + /// + /// Source video file. + /// Seek position where the thumbnail should be taken. + /// Thumbnail size. If width or height equal 0, the other will be computed automatically. + /// Selected video stream index. + /// Input file index + /// Bitmap with the requested snapshot. + public static async Task SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) + { + var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); + using var ms = new MemoryStream(); + + await arguments + .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options + .ForceFormat("rawvideo"))) + .ProcessAsynchronously(); + + ms.Position = 0; + return new Bitmap(ms); + } + private static void Cleanup(IEnumerable pathList) + { + foreach (var path in pathList) + { + if (File.Exists(path)) + File.Delete(path); + } + } + } +} \ No newline at end of file diff --git a/FFMpegCore/ImageInfo.cs b/FFMpegCore.Extensions.System.Drawing.Common/ImageInfo.cs similarity index 100% rename from FFMpegCore/ImageInfo.cs rename to FFMpegCore.Extensions.System.Drawing.Common/ImageInfo.cs diff --git a/FFMpegCore.Test/AudioTest.cs b/FFMpegCore.Test/AudioTest.cs index 795fedf..23c4e79 100644 --- a/FFMpegCore.Test/AudioTest.cs +++ b/FFMpegCore.Test/AudioTest.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using FFMpegCore.Extensions.System.Drawing.Common; namespace FFMpegCore.Test { @@ -68,7 +69,7 @@ public void Audio_Add() public void Image_AddAudio() { using var outputFile = new TemporaryFile("out.mp4"); - FFMpeg.PosterWithAudio(TestResources.PngImage, TestResources.Mp3Audio, outputFile); + FFMpegImage.PosterWithAudio(TestResources.PngImage, TestResources.Mp3Audio, outputFile); var analysis = FFProbe.Analyse(TestResources.Mp3Audio); Assert.IsTrue(analysis.Duration.TotalSeconds > 0); Assert.IsTrue(File.Exists(outputFile)); diff --git a/FFMpegCore.Test/FFMpegCore.Test.csproj b/FFMpegCore.Test/FFMpegCore.Test.csproj index 4ac890c..c84f3fe 100644 --- a/FFMpegCore.Test/FFMpegCore.Test.csproj +++ b/FFMpegCore.Test/FFMpegCore.Test.csproj @@ -50,6 +50,7 @@ + diff --git a/FFMpegCore.Test/Utilities/BitmapSources.cs b/FFMpegCore.Test/Utilities/BitmapSources.cs index 8ea02e8..50dd691 100644 --- a/FFMpegCore.Test/Utilities/BitmapSources.cs +++ b/FFMpegCore.Test/Utilities/BitmapSources.cs @@ -1,9 +1,9 @@ -using FFMpegCore.Extend; -using System; +using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Numerics; +using FFMpegCore.Extensions.System.Drawing.Common; using FFMpegCore.Pipes; namespace FFMpegCore.Test diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index 8f73575..ad72e57 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -13,6 +13,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using FFMpegCore.Extensions.System.Drawing.Common; namespace FFMpegCore.Test { @@ -402,8 +403,8 @@ public void Video_ToMP4_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixe public void Video_Snapshot_InMemory() { var input = FFProbe.Analyse(TestResources.Mp4Video); - using var bitmap = FFMpeg.Snapshot(TestResources.Mp4Video); - + using var bitmap = FFMpegImage.Snapshot(TestResources.Mp4Video); + Assert.AreEqual(input.PrimaryVideoStream!.Width, bitmap.Width); Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height); Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png); @@ -460,8 +461,8 @@ public void Video_Join_Image_Sequence() } }); - using var outputFile = new TemporaryFile("out.mp4"); - var success = FFMpeg.JoinImageSequence(outputFile, images: imageSet.ToArray()); + var outputFile = new TemporaryFile("out.mp4"); + var success = FFMpegImage.JoinImageSequence(outputFile, images: imageSet.ToArray()); Assert.IsTrue(success); var result = FFProbe.Analyse(outputFile); Assert.AreEqual(3, result.Duration.Seconds); diff --git a/FFMpegCore.sln b/FFMpegCore.sln index 7a27980..5a9faa8 100644 --- a/FFMpegCore.sln +++ b/FFMpegCore.sln @@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Test", "FFMpegCo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Examples", "FFMpegCore.Examples\FFMpegCore.Examples.csproj", "{3125CF91-FFBD-4E4E-8930-247116AFE772}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFMpegCore.Extensions.System.Drawing.Common", "FFMpegCore.Extensions.System.Drawing.Common\FFMpegCore.Extensions.System.Drawing.Common.csproj", "{9C1A4930-9369-4A18-AD98-929A2A510D80}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {3125CF91-FFBD-4E4E-8930-247116AFE772}.Debug|Any CPU.Build.0 = Debug|Any CPU {3125CF91-FFBD-4E4E-8930-247116AFE772}.Release|Any CPU.ActiveCfg = Release|Any CPU {3125CF91-FFBD-4E4E-8930-247116AFE772}.Release|Any CPU.Build.0 = Release|Any CPU + {9C1A4930-9369-4A18-AD98-929A2A510D80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C1A4930-9369-4A18-AD98-929A2A510D80}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C1A4930-9369-4A18-AD98-929A2A510D80}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C1A4930-9369-4A18-AD98-929A2A510D80}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/FFMpegCore/FFMpeg/FFMpeg.cs b/FFMpegCore/FFMpeg/FFMpeg.cs index 9e9e0ce..165ec31 100644 --- a/FFMpegCore/FFMpeg/FFMpeg.cs +++ b/FFMpegCore/FFMpeg/FFMpeg.cs @@ -59,54 +59,6 @@ public static async Task SnapshotAsync(string input, string output, Size? .ProcessAsynchronously(); } - /// - /// Saves a 'png' thumbnail to an in-memory bitmap - /// - /// Source video file. - /// Seek position where the thumbnail should be taken. - /// Thumbnail size. If width or height equal 0, the other will be computed automatically. - /// Selected video stream index. - /// Input file index - /// Bitmap with the requested snapshot. - public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) - { - var source = FFProbe.Analyse(input); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); - using var ms = new MemoryStream(); - - arguments - .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options - .ForceFormat("rawvideo"))) - .ProcessSynchronously(); - - ms.Position = 0; - using var bitmap = new Bitmap(ms); - return bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat); - } - /// - /// Saves a 'png' thumbnail to an in-memory bitmap - /// - /// Source video file. - /// Seek position where the thumbnail should be taken. - /// Thumbnail size. If width or height equal 0, the other will be computed automatically. - /// Selected video stream index. - /// Input file index - /// Bitmap with the requested snapshot. - public static async Task SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) - { - var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); - using var ms = new MemoryStream(); - - await arguments - .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options - .ForceFormat("rawvideo"))) - .ProcessAsynchronously(); - - ms.Position = 0; - return new Bitmap(ms); - } - private static (FFMpegArguments, Action outputOptions) BuildSnapshotArguments( string input, IMediaAnalysis source, diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj index 79d191e..a3251db 100644 --- a/FFMpegCore/FFMpegCore.csproj +++ b/FFMpegCore/FFMpegCore.csproj @@ -38,7 +38,6 @@ - diff --git a/FFMpegCore/Helpers/FFMpegHelper.cs b/FFMpegCore/Helpers/FFMpegHelper.cs index cb3b4cf..ecea946 100644 --- a/FFMpegCore/Helpers/FFMpegHelper.cs +++ b/FFMpegCore/Helpers/FFMpegHelper.cs @@ -10,13 +10,10 @@ public static class FFMpegHelper { private static bool _ffmpegVerified; - public static void ConversionSizeExceptionCheck(Image image) - => ConversionSizeExceptionCheck(image.Size.Width, image.Size.Height); - public static void ConversionSizeExceptionCheck(IMediaAnalysis info) => ConversionSizeExceptionCheck(info.PrimaryVideoStream!.Width, info.PrimaryVideoStream.Height); - private static void ConversionSizeExceptionCheck(int width, int height) + public static void ConversionSizeExceptionCheck(int width, int height) { if (height % 2 != 0 || width % 2 != 0 ) throw new ArgumentException("FFMpeg yuv420p encoding requires the width and height to be a multiple of 2!"); From 2abb2a1e6b09fec0b6939ab848fb2b652f71b880 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 28 Jan 2023 13:37:20 +0100 Subject: [PATCH 12/23] WIP --- .../FFMpegCore.Extensions.System.Drawing.Common.csproj | 6 +++++- FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs | 5 ++++- FFMpegCore/FFMpegCore.csproj | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj index beeb939..c2575f7 100644 --- a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj +++ b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj @@ -16,13 +16,17 @@ ffmpeg ffprobe convert video audio mediafile resize analyze muxing GitHub true + true enable netstandard2.0 - + + + + diff --git a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs index 19fd16e..13052a8 100644 --- a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs +++ b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; @@ -27,7 +28,7 @@ public static bool JoinImageSequence(string output, double frameRate = 30, param var temporaryImageFiles = images.Select((imageInfo, index) => { using var image = Image.FromFile(imageInfo.FullName); - FFMpegHelper.ConversionSizeExceptionCheck(image); + FFMpegHelper.ConversionSizeExceptionCheck(image.Width, image.Height); var destinationPath = Path.Combine(tempFolderName, $"{index.ToString().PadLeft(9, '0')}{imageInfo.Extension}"); Directory.CreateDirectory(tempFolderName); File.Copy(imageInfo.FullName, destinationPath); @@ -74,6 +75,8 @@ public static bool PosterWithAudio(string image, string audio, string output) .WithAudioBitrate(AudioQuality.Normal) .UsingShortest()) .ProcessSynchronously(); + + } /// /// Saves a 'png' thumbnail to an in-memory bitmap diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj index a3251db..e537ab6 100644 --- a/FFMpegCore/FFMpegCore.csproj +++ b/FFMpegCore/FFMpegCore.csproj @@ -25,6 +25,7 @@ ffmpeg ffprobe convert video audio mediafile resize analyze muxing GitHub true + true enable netstandard2.0 From 8e9b7df4deb5a9f150409e9e2b0b2fdd38b004ed Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sun, 29 Jan 2023 22:17:40 +0100 Subject: [PATCH 13/23] Add Directory.Build.props --- Directory.Build.props | 15 ++ FFMpegCore.Examples/Program.cs | 4 +- .../BitmapExtensions.cs | 2 +- ...re.Extensions.System.Drawing.Common.csproj | 21 +-- .../FFMpegImage.cs | 15 +- FFMpegCore.Test/FFMpegCore.Test.csproj | 75 +++----- .../FFMpeg/Arguments/AudioFiltersArgument.cs | 4 +- FFMpegCore/FFMpeg/FFMpeg.cs | 177 ++++++------------ FFMpegCore/FFMpegCore.csproj | 56 ++---- 9 files changed, 135 insertions(+), 234 deletions(-) create mode 100644 Directory.Build.props diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..caefd5d --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,15 @@ + + + netstandard2.0 + en + 5.0.0.0 + default + enable + + GitHub + https://github.com/rosenbjerg/FFMpegCore + https://github.com/rosenbjerg/FFMpegCore + MIT + en + + \ No newline at end of file diff --git a/FFMpegCore.Examples/Program.cs b/FFMpegCore.Examples/Program.cs index ea343f2..e7b93e2 100644 --- a/FFMpegCore.Examples/Program.cs +++ b/FFMpegCore.Examples/Program.cs @@ -84,8 +84,8 @@ await FFMpegArguments var inputImagePath = "/path/to/input/image"; { FFMpegImage.PosterWithAudio(inputPath, inputAudioPath, outputPath); - // or - var image = Image.FromFile(inputImagePath); + // or + using var image = Image.FromFile(inputImagePath); image.AddAudio(inputAudioPath, outputPath); } diff --git a/FFMpegCore.Extensions.System.Drawing.Common/BitmapExtensions.cs b/FFMpegCore.Extensions.System.Drawing.Common/BitmapExtensions.cs index e549580..6633f69 100644 --- a/FFMpegCore.Extensions.System.Drawing.Common/BitmapExtensions.cs +++ b/FFMpegCore.Extensions.System.Drawing.Common/BitmapExtensions.cs @@ -12,7 +12,7 @@ public static bool AddAudio(this Image poster, string audio, string output) poster.Save(destination); try { - return FFMpeg.PosterWithAudio(destination, audio, output); + return FFMpegImage.PosterWithAudio(destination, audio, output); } finally { diff --git a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj index c2575f7..aafb577 100644 --- a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj +++ b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj @@ -1,28 +1,17 @@ - en - https://github.com/rosenbjerg/FFMpegCore - https://github.com/rosenbjerg/FFMpegCore - - Image extension for FFMpegCore, using System.Common.Drawing + true + Image extension for FFMpegCore using System.Common.Drawing + 5.0.0 - 8 - 4.0.0.0 - 4.0.0 - MIT - Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev ffmpeg ffprobe convert video audio mediafile resize analyze muxing - GitHub - true - true - enable - netstandard2.0 + Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev - + diff --git a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs index 13052a8..467fe6a 100644 --- a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs +++ b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs @@ -41,6 +41,7 @@ public static bool JoinImageSequence(string output, double frameRate = 30, param return FFMpegArguments .FromFileInput(Path.Combine(tempFolderName, "%09d.png"), false) .OutputToFile(output, true, options => options + .ForcePixelFormat("yuv420p") .Resize(firstImage.Width, firstImage.Height) .WithFramerate(frameRate)) .ProcessSynchronously(); @@ -62,22 +63,22 @@ public static bool PosterWithAudio(string image, string audio, string output) { FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4); using (var img = Image.FromFile(image)) - FFMpegHelper.ConversionSizeExceptionCheck(img); + FFMpegHelper.ConversionSizeExceptionCheck(img.Width, img.Height); return FFMpegArguments .FromFileInput(image, false, options => options - .Loop(1)) + .Loop(1) + .ForceFormat("image2")) .AddFileInput(audio) .OutputToFile(output, true, options => options + .ForcePixelFormat("yuv420p") .WithVideoCodec(VideoCodec.LibX264) - .CopyChannel() .WithConstantRateFactor(21) .WithAudioBitrate(AudioQuality.Normal) .UsingShortest()) .ProcessSynchronously(); - - } + /// /// Saves a 'png' thumbnail to an in-memory bitmap /// @@ -90,7 +91,7 @@ public static bool PosterWithAudio(string image, string audio, string output) public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) { var source = FFProbe.Analyse(input); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); + var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); using var ms = new MemoryStream(); arguments @@ -114,7 +115,7 @@ public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? capture public static async Task SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) { var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); + var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); using var ms = new MemoryStream(); await arguments diff --git a/FFMpegCore.Test/FFMpegCore.Test.csproj b/FFMpegCore.Test/FFMpegCore.Test.csproj index c84f3fe..d606d6b 100644 --- a/FFMpegCore.Test/FFMpegCore.Test.csproj +++ b/FFMpegCore.Test/FFMpegCore.Test.csproj @@ -2,42 +2,11 @@ net6.0 - false - disable - default - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - Always - - - - - - PreserveNewest - - - @@ -55,46 +24,60 @@ + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + - Always + PreserveNewest - Always + PreserveNewest - Always + PreserveNewest - Always + PreserveNewest - Always + PreserveNewest - Always + PreserveNewest - Always + PreserveNewest - Always + PreserveNewest - Always + PreserveNewest - Always + PreserveNewest - Always + PreserveNewest PreserveNewest - - - - diff --git a/FFMpegCore/FFMpeg/Arguments/AudioFiltersArgument.cs b/FFMpegCore/FFMpeg/Arguments/AudioFiltersArgument.cs index 50b26b3..dac7d15 100644 --- a/FFMpegCore/FFMpeg/Arguments/AudioFiltersArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/AudioFiltersArgument.cs @@ -34,8 +34,8 @@ private string GetText() public interface IAudioFilterArgument { - public string Key { get; } - public string Value { get; } + string Key { get; } + string Value { get; } } public class AudioFilterOptions diff --git a/FFMpegCore/FFMpeg/FFMpeg.cs b/FFMpegCore/FFMpeg/FFMpeg.cs index 165ec31..909a96a 100644 --- a/FFMpegCore/FFMpeg/FFMpeg.cs +++ b/FFMpegCore/FFMpeg/FFMpeg.cs @@ -1,7 +1,6 @@ using FFMpegCore.Enums; using FFMpegCore.Exceptions; using FFMpegCore.Helpers; -using FFMpegCore.Pipes; using System; using System.Collections.Generic; using System.Drawing; @@ -12,54 +11,9 @@ namespace FFMpegCore { - public static class FFMpeg + public static class SnapshotArgumentBuilder { - /// - /// Saves a 'png' thumbnail from the input video to drive - /// - /// Source video analysis - /// Output video file path - /// Seek position where the thumbnail should be taken. - /// Thumbnail size. If width or height equal 0, the other will be computed automatically. - /// Selected video stream index. - /// Input file index - /// Bitmap with the requested snapshot. - public static bool Snapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) - { - if (Path.GetExtension(output) != FileExtension.Png) - output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; - - var source = FFProbe.Analyse(input); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); - - return arguments - .OutputToFile(output, true, outputOptions) - .ProcessSynchronously(); - } - /// - /// Saves a 'png' thumbnail from the input video to drive - /// - /// Source video analysis - /// Output video file path - /// Seek position where the thumbnail should be taken. - /// Thumbnail size. If width or height equal 0, the other will be computed automatically. - /// Selected video stream index. - /// Input file index - /// Bitmap with the requested snapshot. - public static async Task SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) - { - if (Path.GetExtension(output) != FileExtension.Png) - output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; - - var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); - - return await arguments - .OutputToFile(output, true, outputOptions) - .ProcessAsynchronously(); - } - - private static (FFMpegArguments, Action outputOptions) BuildSnapshotArguments( + public static (FFMpegArguments, Action outputOptions) BuildSnapshotArguments( string input, IMediaAnalysis source, Size? size = null, @@ -109,13 +63,61 @@ private static (FFMpegArguments, Action outputOptions) Bu return null; } + } + public static class FFMpeg + { + /// + /// Saves a 'png' thumbnail from the input video to drive + /// + /// Source video analysis + /// Output video file path + /// Seek position where the thumbnail should be taken. + /// Thumbnail size. If width or height equal 0, the other will be computed automatically. + /// Selected video stream index. + /// Input file index + /// Bitmap with the requested snapshot. + public static bool Snapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) + { + if (Path.GetExtension(output) != FileExtension.Png) + output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; + + var source = FFProbe.Analyse(input); + var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); + + return arguments + .OutputToFile(output, true, outputOptions) + .ProcessSynchronously(); + } + /// + /// Saves a 'png' thumbnail from the input video to drive + /// + /// Source video analysis + /// Output video file path + /// Seek position where the thumbnail should be taken. + /// Thumbnail size. If width or height equal 0, the other will be computed automatically. + /// Selected video stream index. + /// Input file index + /// Bitmap with the requested snapshot. + public static async Task SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) + { + if (Path.GetExtension(output) != FileExtension.Png) + output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; + + var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false); + var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); + + return await arguments + .OutputToFile(output, true, outputOptions) + .ProcessAsynchronously(); + } + /// /// Convert a video do a different format. /// - /// Input video source. + /// Input video source. /// Output information. - /// Target conversion video type. + /// Target conversion video format. /// Conversion target speed/quality (faster speed = lower quality). /// Video size. /// Conversion target audio quality. @@ -189,35 +191,6 @@ public static bool Convert( }; } - /// - /// Adds a poster image to an audio file. - /// - /// Source image file. - /// Source audio file. - /// Output video file. - /// - public static bool PosterWithAudio(string image, string audio, string output) - { - FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4); - using (var imageFile = Image.FromFile(image)) - { - FFMpegHelper.ConversionSizeExceptionCheck(imageFile); - } - - return FFMpegArguments - .FromFileInput(image, false, options => options - .Loop(1) - .ForceFormat("image2")) - .AddFileInput(audio) - .OutputToFile(output, true, options => options - .ForcePixelFormat("yuv420p") - .WithVideoCodec(VideoCodec.LibX264) - .WithConstantRateFactor(21) - .WithAudioBitrate(AudioQuality.Normal) - .UsingShortest()) - .ProcessSynchronously(); - } - /// /// Joins a list of video files. /// @@ -251,44 +224,6 @@ public static bool Join(string output, params string[] videos) } } - /// - /// Converts an image sequence to a video. - /// - /// Output video file. - /// FPS - /// Image sequence collection - /// Output video information. - public static bool JoinImageSequence(string output, double frameRate = 30, params ImageInfo[] images) - { - var tempFolderName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, Guid.NewGuid().ToString()); - var temporaryImageFiles = images.Select((imageInfo, index) => - { - using var image = Image.FromFile(imageInfo.FullName); - FFMpegHelper.ConversionSizeExceptionCheck(image); - var destinationPath = Path.Combine(tempFolderName, $"{index.ToString().PadLeft(9, '0')}{imageInfo.Extension}"); - Directory.CreateDirectory(tempFolderName); - File.Copy(imageInfo.FullName, destinationPath); - return destinationPath; - }).ToArray(); - - var firstImage = images.First(); - try - { - return FFMpegArguments - .FromFileInput(Path.Combine(tempFolderName, "%09d.png"), false) - .OutputToFile(output, true, options => options - .ForcePixelFormat("yuv420p") - .Resize(firstImage.Width, firstImage.Height) - .WithFramerate(frameRate)) - .ProcessSynchronously(); - } - finally - { - Cleanup(temporaryImageFiles); - Directory.Delete(tempFolderName); - } - } - /// /// Records M3U8 streams to the specified output. /// @@ -397,15 +332,15 @@ public static IReadOnlyList GetPixelFormats() return FFMpegCache.PixelFormats.Values.ToList().AsReadOnly(); } - public static bool TryGetPixelFormat(string name, out PixelFormat fmt) + public static bool TryGetPixelFormat(string name, out PixelFormat format) { if (!GlobalFFOptions.Current.UseCache) { - fmt = GetPixelFormatsInternal().FirstOrDefault(x => x.Name == name.ToLowerInvariant().Trim()); - return fmt != null; + format = GetPixelFormatsInternal().FirstOrDefault(x => x.Name == name.ToLowerInvariant().Trim()); + return format != null; } else - return FFMpegCache.PixelFormats.TryGetValue(name, out fmt); + return FFMpegCache.PixelFormats.TryGetValue(name, out format); } public static PixelFormat GetPixelFormat(string name) diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj index e537ab6..ecd0b85 100644 --- a/FFMpegCore/FFMpegCore.csproj +++ b/FFMpegCore/FFMpegCore.csproj @@ -1,45 +1,23 @@  - - en - https://github.com/rosenbjerg/FFMpegCore - https://github.com/rosenbjerg/FFMpegCore - - A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your .NET applications - 4.0.0.0 - README.md - - Fixes for `MetaDataArgument` (thanks @JKamsker) -- Support for Audible `aaxc` (thanks @JKamsker) -- Include errordata in `IMediaAnalysis` (thanks @JKamsker) -- Pass `FFOptions` properly when using ffprobe (thanks @Notheisz57) -- CancellationToken support for `AnalyseAsync` -- Case-insensitive dictionaries for `Tags` and `Disposition` -- Fix for `PosterWithAudio` -- Fix for `JoinImageSequence` -- Updates to dependendies -- A lot of bug fixes - 8 - 4.8.0 - MIT - Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev - ffmpeg ffprobe convert video audio mediafile resize analyze muxing - GitHub - true - true - enable - netstandard2.0 - + + true + A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your .NET applications + 5.0.0 + + + ffmpeg ffprobe convert video audio mediafile resize analyze muxing + Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev + README.md + - - - Always - - - + + + - - - - + + + + From 5b0c74e5c0093e415c9aa62923bc17475af015a8 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sun, 29 Jan 2023 22:49:45 +0100 Subject: [PATCH 14/23] Add SupportedOSPlatform attribute on tests using SDC --- FFMpegCore.Test/AudioTest.cs | 3 +++ FFMpegCore.Test/Utilities/BitmapSources.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/FFMpegCore.Test/AudioTest.cs b/FFMpegCore.Test/AudioTest.cs index 23c4e79..edf238c 100644 --- a/FFMpegCore.Test/AudioTest.cs +++ b/FFMpegCore.Test/AudioTest.cs @@ -8,8 +8,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.Versioning; using System.Threading.Tasks; using FFMpegCore.Extensions.System.Drawing.Common; +using FFMpegCore.Test.Utilities; namespace FFMpegCore.Test { @@ -66,6 +68,7 @@ public void Audio_Add() } [TestMethod] + [SupportedOSPlatform("windows")] public void Image_AddAudio() { using var outputFile = new TemporaryFile("out.mp4"); diff --git a/FFMpegCore.Test/Utilities/BitmapSources.cs b/FFMpegCore.Test/Utilities/BitmapSources.cs index 50dd691..61feff5 100644 --- a/FFMpegCore.Test/Utilities/BitmapSources.cs +++ b/FFMpegCore.Test/Utilities/BitmapSources.cs @@ -6,7 +6,7 @@ using FFMpegCore.Extensions.System.Drawing.Common; using FFMpegCore.Pipes; -namespace FFMpegCore.Test +namespace FFMpegCore.Test.Utilities { static class BitmapSource { From 11997d91f98cd6ab427408b3dd341755b8ed2c2e Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sun, 29 Jan 2023 22:51:20 +0100 Subject: [PATCH 15/23] Fix using temporarily --- FFMpegCore.Test/AudioTest.cs | 1 - FFMpegCore.Test/Utilities/BitmapSources.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/FFMpegCore.Test/AudioTest.cs b/FFMpegCore.Test/AudioTest.cs index edf238c..aae8c3a 100644 --- a/FFMpegCore.Test/AudioTest.cs +++ b/FFMpegCore.Test/AudioTest.cs @@ -11,7 +11,6 @@ using System.Runtime.Versioning; using System.Threading.Tasks; using FFMpegCore.Extensions.System.Drawing.Common; -using FFMpegCore.Test.Utilities; namespace FFMpegCore.Test { diff --git a/FFMpegCore.Test/Utilities/BitmapSources.cs b/FFMpegCore.Test/Utilities/BitmapSources.cs index 61feff5..50dd691 100644 --- a/FFMpegCore.Test/Utilities/BitmapSources.cs +++ b/FFMpegCore.Test/Utilities/BitmapSources.cs @@ -6,7 +6,7 @@ using FFMpegCore.Extensions.System.Drawing.Common; using FFMpegCore.Pipes; -namespace FFMpegCore.Test.Utilities +namespace FFMpegCore.Test { static class BitmapSource { From fb6865587afe1f47567f5bafdf99f04b10e294e0 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sun, 29 Jan 2023 22:55:35 +0100 Subject: [PATCH 16/23] Add IgnoreIf attribute to only run SDC tests on Windows --- FFMpegCore.Test/AudioTest.cs | 5 +- .../Utilities/IgnoreIfAttribute.cs | 39 ++++++++++ .../Utilities/OperatingSystemUtils.cs | 11 +++ .../TestMethodWithIgnoreIfSupportAttribute.cs | 75 +++++++++++++++++++ FFMpegCore.Test/VideoTest.cs | 53 +++++++++---- 5 files changed, 166 insertions(+), 17 deletions(-) create mode 100644 FFMpegCore.Test/Utilities/IgnoreIfAttribute.cs create mode 100644 FFMpegCore.Test/Utilities/OperatingSystemUtils.cs create mode 100644 FFMpegCore.Test/Utilities/TestMethodWithIgnoreIfSupportAttribute.cs diff --git a/FFMpegCore.Test/AudioTest.cs b/FFMpegCore.Test/AudioTest.cs index aae8c3a..5da9b08 100644 --- a/FFMpegCore.Test/AudioTest.cs +++ b/FFMpegCore.Test/AudioTest.cs @@ -11,6 +11,7 @@ using System.Runtime.Versioning; using System.Threading.Tasks; using FFMpegCore.Extensions.System.Drawing.Common; +using FFMpegCore.Test.Utilities; namespace FFMpegCore.Test { @@ -66,8 +67,8 @@ public void Audio_Add() Assert.IsTrue(File.Exists(outputFile)); } - [TestMethod] - [SupportedOSPlatform("windows")] + [TestMethodWithIgnoreIfSupport] + [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] public void Image_AddAudio() { using var outputFile = new TemporaryFile("out.mp4"); diff --git a/FFMpegCore.Test/Utilities/IgnoreIfAttribute.cs b/FFMpegCore.Test/Utilities/IgnoreIfAttribute.cs new file mode 100644 index 0000000..526a76c --- /dev/null +++ b/FFMpegCore.Test/Utilities/IgnoreIfAttribute.cs @@ -0,0 +1,39 @@ +using System; +using System.Reflection; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace FFMpegCore.Test.Utilities; + +// https://matt.kotsenas.com/posts/ignoreif-mstest +/// +/// An extension to the [Ignore] attribute. Instead of using test lists / test categories to conditionally +/// skip tests, allow a [TestClass] or [TestMethod] to specify a method to run. If the method returns +/// `true` the test method will be skipped. The "ignore criteria" method must be `static`, return a single +/// `bool` value, and not accept any parameters. By default, it is named "IgnoreIf". +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class IgnoreIfAttribute : Attribute +{ + public string IgnoreCriteriaMethodName { get; set; } + + public IgnoreIfAttribute(string ignoreCriteriaMethodName = "IgnoreIf") + { + IgnoreCriteriaMethodName = ignoreCriteriaMethodName; + } + + internal bool ShouldIgnore(ITestMethod testMethod) + { + try + { + // Search for the method specified by name in this class or any parent classes. + var searchFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Static; + var method = testMethod.MethodInfo.DeclaringType!.GetMethod(IgnoreCriteriaMethodName, searchFlags); + return (bool) method?.Invoke(null, null)!; + } + catch (Exception e) + { + var message = $"Conditional ignore method {IgnoreCriteriaMethodName} not found. Ensure the method is in the same class as the test method, marked as `static`, returns a `bool`, and doesn't accept any parameters."; + throw new ArgumentException(message, e); + } + } +} \ No newline at end of file diff --git a/FFMpegCore.Test/Utilities/OperatingSystemUtils.cs b/FFMpegCore.Test/Utilities/OperatingSystemUtils.cs new file mode 100644 index 0000000..5526a2a --- /dev/null +++ b/FFMpegCore.Test/Utilities/OperatingSystemUtils.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace FFMpegCore.Test.Utilities; + +public static class OperatingSystemUtils +{ + public static bool NotWindows() + { + return !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + } +} \ No newline at end of file diff --git a/FFMpegCore.Test/Utilities/TestMethodWithIgnoreIfSupportAttribute.cs b/FFMpegCore.Test/Utilities/TestMethodWithIgnoreIfSupportAttribute.cs new file mode 100644 index 0000000..5ea2436 --- /dev/null +++ b/FFMpegCore.Test/Utilities/TestMethodWithIgnoreIfSupportAttribute.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace FFMpegCore.Test.Utilities; + +// https://matt.kotsenas.com/posts/ignoreif-mstest +/// +/// An extension to the [TestMethod] attribute. It walks the method and class hierarchy looking +/// for an [IgnoreIf] attribute. If one or more are found, they are each evaluated, if the attribute +/// returns `true`, evaluation is short-circuited, and the test method is skipped. +/// +public class TestMethodWithIgnoreIfSupportAttribute : TestMethodAttribute +{ + public override TestResult[] Execute(ITestMethod testMethod) + { + var ignoreResults = TestMethodUtils.GetIgnoreResults(testMethod); + return ignoreResults.Any() + ? ignoreResults + : base.Execute(testMethod); + } +} +public class DataTestMethodWithIgnoreIfSupportAttribute : DataTestMethodAttribute +{ + public override TestResult[] Execute(ITestMethod testMethod) + { + var ignoreResults = TestMethodUtils.GetIgnoreResults(testMethod); + return ignoreResults.Any() + ? ignoreResults + : base.Execute(testMethod); + } +} + +internal class TestMethodUtils +{ + internal static TestResult[] GetIgnoreResults(ITestMethod testMethod) + { + var ignoreAttributes = FindAttributes(testMethod); + + // Evaluate each attribute, and skip if one returns `true` + foreach (var ignoreAttribute in ignoreAttributes) + { + if (ignoreAttribute.ShouldIgnore(testMethod)) + { + var message = $"Test not executed. Conditional ignore method '{ignoreAttribute.IgnoreCriteriaMethodName}' evaluated to 'true'."; + { + return new[] + { + new TestResult { Outcome = UnitTestOutcome.Inconclusive, TestFailureException = new AssertInconclusiveException(message) } + }; + } + } + } + + return Array.Empty(); + } + + private static IEnumerable FindAttributes(ITestMethod testMethod) + { + // Look for an [IgnoreIf] on the method, including any virtuals this method overrides + var ignoreAttributes = new List(); + ignoreAttributes.AddRange(testMethod.GetAttributes(inherit: true)); + + // Walk the class hierarchy looking for an [IgnoreIf] attribute + var type = testMethod.MethodInfo.DeclaringType; + while (type != null) + { + ignoreAttributes.AddRange(type.GetCustomAttributes(inherit: true)); + type = type.DeclaringType; + } + return ignoreAttributes; + } +} \ No newline at end of file diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index ad72e57..06e030c 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -10,10 +10,12 @@ using System.Drawing.Imaging; using System.IO; using System.Linq; +using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using FFMpegCore.Extensions.System.Drawing.Common; +using FFMpegCore.Test.Utilities; namespace FFMpegCore.Test { @@ -86,7 +88,9 @@ public void Video_ToH265_MKV_Args() Assert.IsTrue(success); } - [DataTestMethod, Timeout(10000)] + [SupportedOSPlatform("windows")] + [DataTestMethodWithIgnoreIfSupport, Timeout(10000)] + [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] [DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)] [DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)] public void Video_ToMP4_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat) @@ -102,7 +106,9 @@ public void Video_ToMP4_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat Assert.IsTrue(success); } - [TestMethod, Timeout(10000)] + [SupportedOSPlatform("windows")] + [TestMethodWithIgnoreIfSupport, Timeout(10000)] + [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] public void Video_ToMP4_Args_Pipe_DifferentImageSizes() { using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); @@ -121,8 +127,9 @@ public void Video_ToMP4_Args_Pipe_DifferentImageSizes() .ProcessSynchronously()); } - - [TestMethod, Timeout(10000)] + [SupportedOSPlatform("windows")] + [TestMethodWithIgnoreIfSupport, Timeout(10000)][SupportedOSPlatform("windows")] + [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Async() { using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); @@ -141,7 +148,9 @@ public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Async() .ProcessAsynchronously()); } - [TestMethod, Timeout(10000)] + [SupportedOSPlatform("windows")] + [TestMethodWithIgnoreIfSupport, Timeout(10000)] + [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] public void Video_ToMP4_Args_Pipe_DifferentPixelFormats() { using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); @@ -159,9 +168,10 @@ public void Video_ToMP4_Args_Pipe_DifferentPixelFormats() .WithVideoCodec(VideoCodec.LibX264)) .ProcessSynchronously()); } - - - [TestMethod, Timeout(10000)] + + [SupportedOSPlatform("windows")] + [TestMethodWithIgnoreIfSupport, Timeout(10000)] + [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_Async() { using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); @@ -315,7 +325,9 @@ public void Video_ToTS_Args() Assert.IsTrue(success); } - [DataTestMethod, Timeout(10000)] + [SupportedOSPlatform("windows")] + [DataTestMethodWithIgnoreIfSupport, Timeout(10000)] + [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] [DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)] [DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)] public async Task Video_ToTS_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat) @@ -347,7 +359,9 @@ public async Task Video_ToOGV_Resize() Assert.IsTrue(success); } - [DataTestMethod, Timeout(10000)] + [SupportedOSPlatform("windows")] + [DataTestMethodWithIgnoreIfSupport, Timeout(10000)] + [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] [DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)] [DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)] // [DataRow(PixelFormat.Format48bppRgb)] @@ -382,7 +396,9 @@ public void Scale_Mp4_Multithreaded() Assert.IsTrue(success); } - [DataTestMethod, Timeout(10000)] + [SupportedOSPlatform("windows")] + [DataTestMethodWithIgnoreIfSupport, Timeout(10000)] + [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] [DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)] [DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)] // [DataRow(PixelFormat.Format48bppRgb)] @@ -399,7 +415,9 @@ public void Video_ToMP4_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixe Assert.IsTrue(success); } - [TestMethod, Timeout(10000)] + [SupportedOSPlatform("windows")] + [TestMethodWithIgnoreIfSupport, Timeout(10000)] + [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] public void Video_Snapshot_InMemory() { var input = FFProbe.Analyse(TestResources.Mp4Video); @@ -410,7 +428,9 @@ public void Video_Snapshot_InMemory() Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png); } - [TestMethod, Timeout(10000)] + [SupportedOSPlatform("windows")] + [TestMethodWithIgnoreIfSupport, Timeout(10000)] + [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] public void Video_Snapshot_PersistSnapshot() { var outputPath = new TemporaryFile("out.png"); @@ -446,7 +466,8 @@ public void Video_Join() Assert.AreEqual(input.PrimaryVideoStream.Width, result.PrimaryVideoStream.Width); } - [TestMethod, Timeout(10000)] + [TestMethodWithIgnoreIfSupport, Timeout(10000)] + [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] public void Video_Join_Image_Sequence() { var imageSet = new List(); @@ -555,7 +576,9 @@ public void Video_OutputsData() Assert.IsTrue(File.Exists(outputFile)); } - [TestMethod, Timeout(10000)] + [SupportedOSPlatform("windows")] + [TestMethodWithIgnoreIfSupport, Timeout(10000)] + [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] public void Video_TranscodeInMemory() { using var resStream = new MemoryStream(); From ba2e31c43b0c0533eb37315066545f67fc0a7334 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sun, 29 Jan 2023 22:55:47 +0100 Subject: [PATCH 17/23] Cleanup pipelines --- .github/workflows/ci.yml | 6 +++--- .github/workflows/release.yml | 21 ++++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c470186..c807872 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,9 +23,9 @@ jobs: uses: actions/checkout@v3 - name: Prepare .NET - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v3 with: - dotnet-version: '6.0.x' + dotnet-version: '7.0.x' - name: Prepare FFMpeg uses: Iamshankhadeep/setup-ffmpeg@v1.2 @@ -34,4 +34,4 @@ jobs: version: "4.4" - name: Test with dotnet - run: dotnet test --logger GitHubActions + run: dotnet test FFMpegCore.sln --logger GitHubActions diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7cf1425..0c7725b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,13 +8,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 - - name: Prepare .NET - uses: actions/setup-dotnet@v1 - with: - dotnet-version: '6.0.x' - - name: Build solution - run: dotnet build --output build -c Release - - name: Publish NuGet package - run: dotnet nuget push "build/*.nupkg" --source nuget.org --api-key ${{ secrets.NUGET_TOKEN }} + uses: actions/checkout@v3 + + - name: Prepare .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '7.0.x' + + - name: Build solution + run: dotnet pack FFMpegCore.sln --output build -c Release + + - name: Publish NuGet package + run: dotnet nuget push build/*.nupkg --source nuget.org --api-key ${{ secrets.NUGET_TOKEN }} From 476c8f33c07c22323792b445aa75d527fe3f2287 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sun, 29 Jan 2023 23:01:26 +0100 Subject: [PATCH 18/23] Cleanup --- FFMpegCore.Test/Utilities/BitmapSources.cs | 4 +++- FFMpegCore/FFMpeg/Arguments/AudibleEncryptionKeyArgument.cs | 6 +++--- FFMpegCore/FFProbe/FFProbeAnalysis.cs | 4 ++-- FFMpegCore/FFProbe/MediaAnalysis.cs | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/FFMpegCore.Test/Utilities/BitmapSources.cs b/FFMpegCore.Test/Utilities/BitmapSources.cs index 50dd691..044bfa8 100644 --- a/FFMpegCore.Test/Utilities/BitmapSources.cs +++ b/FFMpegCore.Test/Utilities/BitmapSources.cs @@ -3,11 +3,13 @@ using System.Drawing; using System.Drawing.Imaging; using System.Numerics; +using System.Runtime.Versioning; using FFMpegCore.Extensions.System.Drawing.Common; using FFMpegCore.Pipes; -namespace FFMpegCore.Test +namespace FFMpegCore.Test.Utilities { + [SupportedOSPlatform("windows")] static class BitmapSource { public static IEnumerable CreateBitmaps(int count, PixelFormat fmt, int w, int h) diff --git a/FFMpegCore/FFMpeg/Arguments/AudibleEncryptionKeyArgument.cs b/FFMpegCore/FFMpeg/Arguments/AudibleEncryptionKeyArgument.cs index 0f514dc..8b142e4 100644 --- a/FFMpegCore/FFMpeg/Arguments/AudibleEncryptionKeyArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/AudibleEncryptionKeyArgument.cs @@ -4,10 +4,10 @@ public class AudibleEncryptionKeyArgument : IArgument { private readonly bool _aaxcMode; - private readonly string _key; - private readonly string _iv; + private readonly string? _key; + private readonly string? _iv; - private readonly string _activationBytes; + private readonly string? _activationBytes; public AudibleEncryptionKeyArgument(string activationBytes) diff --git a/FFMpegCore/FFProbe/FFProbeAnalysis.cs b/FFMpegCore/FFProbe/FFProbeAnalysis.cs index cbbb9fd..b81d535 100644 --- a/FFMpegCore/FFProbe/FFProbeAnalysis.cs +++ b/FFMpegCore/FFProbe/FFProbeAnalysis.cs @@ -12,7 +12,7 @@ public class FFProbeAnalysis public Format Format { get; set; } = null!; [JsonIgnore] - public IReadOnlyList ErrorData { get; set; } + public IReadOnlyList ErrorData { get; set; } = new List(); } public class FFProbeStream : ITagsContainer, IDispositionContainer @@ -108,7 +108,7 @@ public class Format : ITagsContainer public string Size { get; set; } = null!; [JsonPropertyName("bit_rate")] - public string BitRate { get; set; } = null!; + public string? BitRate { get; set; } = null!; [JsonPropertyName("probe_score")] public int ProbeScore { get; set; } diff --git a/FFMpegCore/FFProbe/MediaAnalysis.cs b/FFMpegCore/FFProbe/MediaAnalysis.cs index e1fbd1d..fd2b135 100644 --- a/FFMpegCore/FFProbe/MediaAnalysis.cs +++ b/FFMpegCore/FFProbe/MediaAnalysis.cs @@ -13,7 +13,7 @@ internal MediaAnalysis(FFProbeAnalysis analysis) VideoStreams = analysis.Streams.Where(stream => stream.CodecType == "video").Select(ParseVideoStream).ToList(); AudioStreams = analysis.Streams.Where(stream => stream.CodecType == "audio").Select(ParseAudioStream).ToList(); SubtitleStreams = analysis.Streams.Where(stream => stream.CodecType == "subtitle").Select(ParseSubtitleStream).ToList(); - ErrorData = analysis.ErrorData ?? new List().AsReadOnly(); + ErrorData = analysis.ErrorData; } private MediaFormat ParseFormat(Format analysisFormat) From 237fa3353f4b56f37710b5e095f8029b00b0e222 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sun, 29 Jan 2023 23:01:37 +0100 Subject: [PATCH 19/23] Cleanup using directives --- FFMpegCore.Test/AudioTest.cs | 1 - FFMpegCore.Test/MetaDataBuilderTests.cs | 6 +----- FFMpegCore/Extend/StringExtensions.cs | 3 +-- FFMpegCore/FFMpeg/Arguments/IDynamicArgument.cs | 1 - FFMpegCore/FFMpeg/Arguments/MapMetadataArgument.cs | 5 +---- FFMpegCore/FFMpeg/Arguments/MetaDataArgument.cs | 5 +---- FFMpegCore/FFMpeg/FFMpegArguments.cs | 1 - FFMpegCore/Helpers/FFMpegHelper.cs | 1 - 8 files changed, 4 insertions(+), 19 deletions(-) diff --git a/FFMpegCore.Test/AudioTest.cs b/FFMpegCore.Test/AudioTest.cs index 5da9b08..6f57d51 100644 --- a/FFMpegCore.Test/AudioTest.cs +++ b/FFMpegCore.Test/AudioTest.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.Versioning; using System.Threading.Tasks; using FFMpegCore.Extensions.System.Drawing.Common; using FFMpegCore.Test.Utilities; diff --git a/FFMpegCore.Test/MetaDataBuilderTests.cs b/FFMpegCore.Test/MetaDataBuilderTests.cs index 747fd9e..23d40a4 100644 --- a/FFMpegCore.Test/MetaDataBuilderTests.cs +++ b/FFMpegCore.Test/MetaDataBuilderTests.cs @@ -3,12 +3,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; + using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace FFMpegCore.Test { diff --git a/FFMpegCore/Extend/StringExtensions.cs b/FFMpegCore/Extend/StringExtensions.cs index 7b02089..29c8d42 100644 --- a/FFMpegCore/Extend/StringExtensions.cs +++ b/FFMpegCore/Extend/StringExtensions.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Text; namespace FFMpegCore.Extend diff --git a/FFMpegCore/FFMpeg/Arguments/IDynamicArgument.cs b/FFMpegCore/FFMpeg/Arguments/IDynamicArgument.cs index 36a504e..20f8cdf 100644 --- a/FFMpegCore/FFMpeg/Arguments/IDynamicArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/IDynamicArgument.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Text; namespace FFMpegCore.Arguments { diff --git a/FFMpegCore/FFMpeg/Arguments/MapMetadataArgument.cs b/FFMpegCore/FFMpeg/Arguments/MapMetadataArgument.cs index afec731..7038139 100644 --- a/FFMpegCore/FFMpeg/Arguments/MapMetadataArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/MapMetadataArgument.cs @@ -1,9 +1,6 @@ -using FFMpegCore.Extend; - -using System; +using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; diff --git a/FFMpegCore/FFMpeg/Arguments/MetaDataArgument.cs b/FFMpegCore/FFMpeg/Arguments/MetaDataArgument.cs index 89bb1fe..5bcb7b1 100644 --- a/FFMpegCore/FFMpeg/Arguments/MetaDataArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/MetaDataArgument.cs @@ -1,10 +1,7 @@ -using FFMpegCore.Extend; - -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; diff --git a/FFMpegCore/FFMpeg/FFMpegArguments.cs b/FFMpegCore/FFMpeg/FFMpegArguments.cs index c100c85..fad42ce 100644 --- a/FFMpegCore/FFMpeg/FFMpegArguments.cs +++ b/FFMpegCore/FFMpeg/FFMpegArguments.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; diff --git a/FFMpegCore/Helpers/FFMpegHelper.cs b/FFMpegCore/Helpers/FFMpegHelper.cs index ecea946..97def0f 100644 --- a/FFMpegCore/Helpers/FFMpegHelper.cs +++ b/FFMpegCore/Helpers/FFMpegHelper.cs @@ -1,5 +1,4 @@ using System; -using System.Drawing; using System.IO; using FFMpegCore.Exceptions; using Instances; From 104caa609b6848b70f1ba3029c26dd7b277870a5 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sun, 29 Jan 2023 23:03:50 +0100 Subject: [PATCH 20/23] More cleanup --- FFMpegCore.Test/AudioTest.cs | 4 ++-- FFMpegCore.Test/FFMpegArgumentProcessorTest.cs | 6 +----- FFMpegCore.Test/MetaDataBuilderTests.cs | 3 --- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/FFMpegCore.Test/AudioTest.cs b/FFMpegCore.Test/AudioTest.cs index 6f57d51..ab432e9 100644 --- a/FFMpegCore.Test/AudioTest.cs +++ b/FFMpegCore.Test/AudioTest.cs @@ -242,7 +242,7 @@ public void Audio_Pan_ToMono() Assert.IsTrue(success); Assert.AreEqual(1, mediaAnalysis.AudioStreams.Count); - Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream.ChannelLayout); + Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout); } [TestMethod, Timeout(10000)] @@ -260,7 +260,7 @@ public void Audio_Pan_ToMonoNoDefinitions() Assert.IsTrue(success); Assert.AreEqual(1, mediaAnalysis.AudioStreams.Count); - Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream.ChannelLayout); + Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout); } [TestMethod, Timeout(10000)] diff --git a/FFMpegCore.Test/FFMpegArgumentProcessorTest.cs b/FFMpegCore.Test/FFMpegArgumentProcessorTest.cs index 6e30999..c849b81 100644 --- a/FFMpegCore.Test/FFMpegArgumentProcessorTest.cs +++ b/FFMpegCore.Test/FFMpegArgumentProcessorTest.cs @@ -13,7 +13,7 @@ public void TestInitialize() { // After testing reset global configuration to null, to be not wrong for other test relying on configuration - typeof(GlobalFFOptions).GetField("_current", BindingFlags.NonPublic | BindingFlags.Static).SetValue(GlobalFFOptions.Current, null); + typeof(GlobalFFOptions).GetField("_current", BindingFlags.NonPublic | BindingFlags.Static)!.SetValue(GlobalFFOptions.Current, null); } private static FFMpegArgumentProcessor CreateArgumentProcessor() => FFMpegArguments @@ -69,10 +69,6 @@ public void Processor_Options_CanBeOverridden_And_Configured() [TestMethod] public void Options_Global_And_Session_Options_Can_Differ() { - FFMpegArgumentProcessor CreateArgumentProcessor() => FFMpegArguments - .FromFileInput("") - .OutputToFile(""); - var globalWorkingDir = "Whatever"; GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalWorkingDir }); diff --git a/FFMpegCore.Test/MetaDataBuilderTests.cs b/FFMpegCore.Test/MetaDataBuilderTests.cs index 23d40a4..5209d4e 100644 --- a/FFMpegCore.Test/MetaDataBuilderTests.cs +++ b/FFMpegCore.Test/MetaDataBuilderTests.cs @@ -1,9 +1,6 @@ using FFMpegCore.Builders.MetaData; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using System; - using System.Text.RegularExpressions; namespace FFMpegCore.Test From e51cf7cc6303a8a49170f4c2e1c43bf5caaa142b Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sun, 29 Jan 2023 23:08:47 +0100 Subject: [PATCH 21/23] Simplify attribute --- FFMpegCore.Test/AudioTest.cs | 3 +- .../Utilities/IgnoreIfAttribute.cs | 39 ---------- .../Utilities/OperatingSystemUtils.cs | 11 --- .../TestMethodWithIgnoreIfSupportAttribute.cs | 75 ------------------- .../Utilities/WindowsOnlyDataTestMethod.cs | 23 ++++++ .../Utilities/WindowsOnlyTestMethod.cs | 23 ++++++ FFMpegCore.Test/VideoTest.cs | 36 +++------ 7 files changed, 59 insertions(+), 151 deletions(-) delete mode 100644 FFMpegCore.Test/Utilities/IgnoreIfAttribute.cs delete mode 100644 FFMpegCore.Test/Utilities/OperatingSystemUtils.cs delete mode 100644 FFMpegCore.Test/Utilities/TestMethodWithIgnoreIfSupportAttribute.cs create mode 100644 FFMpegCore.Test/Utilities/WindowsOnlyDataTestMethod.cs create mode 100644 FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs diff --git a/FFMpegCore.Test/AudioTest.cs b/FFMpegCore.Test/AudioTest.cs index ab432e9..d20d21b 100644 --- a/FFMpegCore.Test/AudioTest.cs +++ b/FFMpegCore.Test/AudioTest.cs @@ -66,8 +66,7 @@ public void Audio_Add() Assert.IsTrue(File.Exists(outputFile)); } - [TestMethodWithIgnoreIfSupport] - [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] + [WindowsOnlyTestMethod] public void Image_AddAudio() { using var outputFile = new TemporaryFile("out.mp4"); diff --git a/FFMpegCore.Test/Utilities/IgnoreIfAttribute.cs b/FFMpegCore.Test/Utilities/IgnoreIfAttribute.cs deleted file mode 100644 index 526a76c..0000000 --- a/FFMpegCore.Test/Utilities/IgnoreIfAttribute.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Reflection; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace FFMpegCore.Test.Utilities; - -// https://matt.kotsenas.com/posts/ignoreif-mstest -/// -/// An extension to the [Ignore] attribute. Instead of using test lists / test categories to conditionally -/// skip tests, allow a [TestClass] or [TestMethod] to specify a method to run. If the method returns -/// `true` the test method will be skipped. The "ignore criteria" method must be `static`, return a single -/// `bool` value, and not accept any parameters. By default, it is named "IgnoreIf". -/// -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] -public class IgnoreIfAttribute : Attribute -{ - public string IgnoreCriteriaMethodName { get; set; } - - public IgnoreIfAttribute(string ignoreCriteriaMethodName = "IgnoreIf") - { - IgnoreCriteriaMethodName = ignoreCriteriaMethodName; - } - - internal bool ShouldIgnore(ITestMethod testMethod) - { - try - { - // Search for the method specified by name in this class or any parent classes. - var searchFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Static; - var method = testMethod.MethodInfo.DeclaringType!.GetMethod(IgnoreCriteriaMethodName, searchFlags); - return (bool) method?.Invoke(null, null)!; - } - catch (Exception e) - { - var message = $"Conditional ignore method {IgnoreCriteriaMethodName} not found. Ensure the method is in the same class as the test method, marked as `static`, returns a `bool`, and doesn't accept any parameters."; - throw new ArgumentException(message, e); - } - } -} \ No newline at end of file diff --git a/FFMpegCore.Test/Utilities/OperatingSystemUtils.cs b/FFMpegCore.Test/Utilities/OperatingSystemUtils.cs deleted file mode 100644 index 5526a2a..0000000 --- a/FFMpegCore.Test/Utilities/OperatingSystemUtils.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Runtime.InteropServices; - -namespace FFMpegCore.Test.Utilities; - -public static class OperatingSystemUtils -{ - public static bool NotWindows() - { - return !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - } -} \ No newline at end of file diff --git a/FFMpegCore.Test/Utilities/TestMethodWithIgnoreIfSupportAttribute.cs b/FFMpegCore.Test/Utilities/TestMethodWithIgnoreIfSupportAttribute.cs deleted file mode 100644 index 5ea2436..0000000 --- a/FFMpegCore.Test/Utilities/TestMethodWithIgnoreIfSupportAttribute.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace FFMpegCore.Test.Utilities; - -// https://matt.kotsenas.com/posts/ignoreif-mstest -/// -/// An extension to the [TestMethod] attribute. It walks the method and class hierarchy looking -/// for an [IgnoreIf] attribute. If one or more are found, they are each evaluated, if the attribute -/// returns `true`, evaluation is short-circuited, and the test method is skipped. -/// -public class TestMethodWithIgnoreIfSupportAttribute : TestMethodAttribute -{ - public override TestResult[] Execute(ITestMethod testMethod) - { - var ignoreResults = TestMethodUtils.GetIgnoreResults(testMethod); - return ignoreResults.Any() - ? ignoreResults - : base.Execute(testMethod); - } -} -public class DataTestMethodWithIgnoreIfSupportAttribute : DataTestMethodAttribute -{ - public override TestResult[] Execute(ITestMethod testMethod) - { - var ignoreResults = TestMethodUtils.GetIgnoreResults(testMethod); - return ignoreResults.Any() - ? ignoreResults - : base.Execute(testMethod); - } -} - -internal class TestMethodUtils -{ - internal static TestResult[] GetIgnoreResults(ITestMethod testMethod) - { - var ignoreAttributes = FindAttributes(testMethod); - - // Evaluate each attribute, and skip if one returns `true` - foreach (var ignoreAttribute in ignoreAttributes) - { - if (ignoreAttribute.ShouldIgnore(testMethod)) - { - var message = $"Test not executed. Conditional ignore method '{ignoreAttribute.IgnoreCriteriaMethodName}' evaluated to 'true'."; - { - return new[] - { - new TestResult { Outcome = UnitTestOutcome.Inconclusive, TestFailureException = new AssertInconclusiveException(message) } - }; - } - } - } - - return Array.Empty(); - } - - private static IEnumerable FindAttributes(ITestMethod testMethod) - { - // Look for an [IgnoreIf] on the method, including any virtuals this method overrides - var ignoreAttributes = new List(); - ignoreAttributes.AddRange(testMethod.GetAttributes(inherit: true)); - - // Walk the class hierarchy looking for an [IgnoreIf] attribute - var type = testMethod.MethodInfo.DeclaringType; - while (type != null) - { - ignoreAttributes.AddRange(type.GetCustomAttributes(inherit: true)); - type = type.DeclaringType; - } - return ignoreAttributes; - } -} \ No newline at end of file diff --git a/FFMpegCore.Test/Utilities/WindowsOnlyDataTestMethod.cs b/FFMpegCore.Test/Utilities/WindowsOnlyDataTestMethod.cs new file mode 100644 index 0000000..01fce23 --- /dev/null +++ b/FFMpegCore.Test/Utilities/WindowsOnlyDataTestMethod.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace FFMpegCore.Test.Utilities; + +public class WindowsOnlyDataTestMethod : DataTestMethodAttribute +{ + public override TestResult[] Execute(ITestMethod testMethod) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var message = $"Test not executed on other platforms than Windows"; + { + return new[] + { + new TestResult { Outcome = UnitTestOutcome.Inconclusive, TestFailureException = new AssertInconclusiveException(message) } + }; + } + } + + return base.Execute(testMethod); + } +} \ No newline at end of file diff --git a/FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs b/FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs new file mode 100644 index 0000000..0dc4b9e --- /dev/null +++ b/FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace FFMpegCore.Test.Utilities; + +public class WindowsOnlyTestMethod : TestMethodAttribute +{ + public override TestResult[] Execute(ITestMethod testMethod) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var message = $"Test not executed on other platforms than Windows"; + { + return new[] + { + new TestResult { Outcome = UnitTestOutcome.Inconclusive, TestFailureException = new AssertInconclusiveException(message) } + }; + } + } + + return base.Execute(testMethod); + } +} \ No newline at end of file diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index 06e030c..46eac59 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -89,8 +89,7 @@ public void Video_ToH265_MKV_Args() } [SupportedOSPlatform("windows")] - [DataTestMethodWithIgnoreIfSupport, Timeout(10000)] - [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] + [WindowsOnlyDataTestMethod, Timeout(10000)] [DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)] [DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)] public void Video_ToMP4_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat) @@ -107,8 +106,7 @@ public void Video_ToMP4_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat } [SupportedOSPlatform("windows")] - [TestMethodWithIgnoreIfSupport, Timeout(10000)] - [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] + [WindowsOnlyTestMethod, Timeout(10000)] public void Video_ToMP4_Args_Pipe_DifferentImageSizes() { using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); @@ -128,8 +126,7 @@ public void Video_ToMP4_Args_Pipe_DifferentImageSizes() } [SupportedOSPlatform("windows")] - [TestMethodWithIgnoreIfSupport, Timeout(10000)][SupportedOSPlatform("windows")] - [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] + [WindowsOnlyTestMethod, Timeout(10000)] public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Async() { using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); @@ -149,8 +146,7 @@ public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Async() } [SupportedOSPlatform("windows")] - [TestMethodWithIgnoreIfSupport, Timeout(10000)] - [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] + [WindowsOnlyTestMethod, Timeout(10000)] public void Video_ToMP4_Args_Pipe_DifferentPixelFormats() { using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); @@ -170,8 +166,7 @@ public void Video_ToMP4_Args_Pipe_DifferentPixelFormats() } [SupportedOSPlatform("windows")] - [TestMethodWithIgnoreIfSupport, Timeout(10000)] - [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] + [WindowsOnlyTestMethod, Timeout(10000)] public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_Async() { using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); @@ -326,8 +321,7 @@ public void Video_ToTS_Args() } [SupportedOSPlatform("windows")] - [DataTestMethodWithIgnoreIfSupport, Timeout(10000)] - [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] + [WindowsOnlyDataTestMethod, Timeout(10000)] [DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)] [DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)] public async Task Video_ToTS_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat) @@ -360,8 +354,7 @@ public async Task Video_ToOGV_Resize() } [SupportedOSPlatform("windows")] - [DataTestMethodWithIgnoreIfSupport, Timeout(10000)] - [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] + [WindowsOnlyDataTestMethod, Timeout(10000)] [DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)] [DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)] // [DataRow(PixelFormat.Format48bppRgb)] @@ -397,8 +390,7 @@ public void Scale_Mp4_Multithreaded() } [SupportedOSPlatform("windows")] - [DataTestMethodWithIgnoreIfSupport, Timeout(10000)] - [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] + [WindowsOnlyDataTestMethod, Timeout(10000)] [DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)] [DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)] // [DataRow(PixelFormat.Format48bppRgb)] @@ -416,8 +408,7 @@ public void Video_ToMP4_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixe } [SupportedOSPlatform("windows")] - [TestMethodWithIgnoreIfSupport, Timeout(10000)] - [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] + [WindowsOnlyTestMethod, Timeout(10000)] public void Video_Snapshot_InMemory() { var input = FFProbe.Analyse(TestResources.Mp4Video); @@ -429,8 +420,7 @@ public void Video_Snapshot_InMemory() } [SupportedOSPlatform("windows")] - [TestMethodWithIgnoreIfSupport, Timeout(10000)] - [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] + [WindowsOnlyTestMethod, Timeout(10000)] public void Video_Snapshot_PersistSnapshot() { var outputPath = new TemporaryFile("out.png"); @@ -466,8 +456,7 @@ public void Video_Join() Assert.AreEqual(input.PrimaryVideoStream.Width, result.PrimaryVideoStream.Width); } - [TestMethodWithIgnoreIfSupport, Timeout(10000)] - [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] + [WindowsOnlyTestMethod, Timeout(10000)] public void Video_Join_Image_Sequence() { var imageSet = new List(); @@ -577,8 +566,7 @@ public void Video_OutputsData() } [SupportedOSPlatform("windows")] - [TestMethodWithIgnoreIfSupport, Timeout(10000)] - [IgnoreIf(nameof(OperatingSystemUtils.NotWindows))] + [WindowsOnlyTestMethod, Timeout(10000)] public void Video_TranscodeInMemory() { using var resStream = new MemoryStream(); From f33c060ff58f1d0e0aa4b31e679f92972cd731cc Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sun, 29 Jan 2023 23:10:24 +0100 Subject: [PATCH 22/23] Fix attribute --- FFMpegCore.Test/Utilities/WindowsOnlyDataTestMethod.cs | 2 +- FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FFMpegCore.Test/Utilities/WindowsOnlyDataTestMethod.cs b/FFMpegCore.Test/Utilities/WindowsOnlyDataTestMethod.cs index 01fce23..8c10054 100644 --- a/FFMpegCore.Test/Utilities/WindowsOnlyDataTestMethod.cs +++ b/FFMpegCore.Test/Utilities/WindowsOnlyDataTestMethod.cs @@ -7,7 +7,7 @@ public class WindowsOnlyDataTestMethod : DataTestMethodAttribute { public override TestResult[] Execute(ITestMethod testMethod) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { var message = $"Test not executed on other platforms than Windows"; { diff --git a/FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs b/FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs index 0dc4b9e..29300e3 100644 --- a/FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs +++ b/FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs @@ -7,7 +7,7 @@ public class WindowsOnlyTestMethod : TestMethodAttribute { public override TestResult[] Execute(ITestMethod testMethod) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { var message = $"Test not executed on other platforms than Windows"; { From 6898a710052f1561fac5d3b29327f49c0cb4b467 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sun, 29 Jan 2023 23:12:52 +0100 Subject: [PATCH 23/23] Add missing test file --- FFMpegCore.Test/FFMpegCore.Test.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/FFMpegCore.Test/FFMpegCore.Test.csproj b/FFMpegCore.Test/FFMpegCore.Test.csproj index d606d6b..5a0c11b 100644 --- a/FFMpegCore.Test/FFMpegCore.Test.csproj +++ b/FFMpegCore.Test/FFMpegCore.Test.csproj @@ -24,6 +24,9 @@ + + PreserveNewest + PreserveNewest