From b5dd8600a447dea9595335955048f9f235cc0db0 Mon Sep 17 00:00:00 2001 From: alex6dj Date: Mon, 4 Oct 2021 12:27:24 -0400 Subject: [PATCH] Pan filter implementation and testing Former-commit-id: 7a661b6ab37e4dc90d20901b7c98939d21241e4d --- FFMpegCore.Test/ArgumentBuilderTest.cs | 33 +++++++++++ FFMpegCore.Test/AudioTest.cs | 58 +++++++++++++++++++ .../FFMpeg/Arguments/AudioFiltersArgument.cs | 7 ++- FFMpegCore/FFMpeg/Arguments/PanArgument.cs | 43 ++++++++++++++ 4 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 FFMpegCore/FFMpeg/Arguments/PanArgument.cs diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs index 082d9bf..140e033 100644 --- a/FFMpegCore.Test/ArgumentBuilderTest.cs +++ b/FFMpegCore.Test/ArgumentBuilderTest.cs @@ -414,5 +414,38 @@ public void Builder_BuildString_ForcePixelFormat() .OutputToFile("output.mp4", false, opt => opt.ForcePixelFormat("yuv444p")).Arguments; Assert.AreEqual("-i \"input.mp4\" -pix_fmt yuv444p \"output.mp4\"", str); } + + [TestMethod] + public void Builder_BuildString_PanAudioFilterChannelNumber() + { + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, + opt => opt.WithAudioFilters(filterOptions => filterOptions.Pan(2, "c0=c1", "c1=c1"))) + .Arguments; + + Assert.AreEqual("-i \"input.mp4\" -af \"pan=2c|c0=c1|c1=c1\" \"output.mp4\"", str); + } + + [TestMethod] + public void Builder_BuildString_PanAudioFilterChannelLayout() + { + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, + opt => opt.WithAudioFilters(filterOptions => filterOptions.Pan("stereo", "c0=c0", "c1=c1"))) + .Arguments; + + Assert.AreEqual("-i \"input.mp4\" -af \"pan=stereo|c0=c0|c1=c1\" \"output.mp4\"", str); + } + + [TestMethod] + public void Builder_BuildString_PanAudioFilterChannelNoOutputDefinition() + { + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, + opt => opt.WithAudioFilters(filterOptions => filterOptions.Pan("stereo"))) + .Arguments; + + Assert.AreEqual("-i \"input.mp4\" -af \"pan=stereo\" \"output.mp4\"", str); + } } } \ No newline at end of file diff --git a/FFMpegCore.Test/AudioTest.cs b/FFMpegCore.Test/AudioTest.cs index b6fde77..1231b6f 100644 --- a/FFMpegCore.Test/AudioTest.cs +++ b/FFMpegCore.Test/AudioTest.cs @@ -223,5 +223,63 @@ public void Audio_ToAAC_Args_Pipe_InvalidSampleRate() .WithAudioCodec(AudioCodec.Aac)) .ProcessSynchronously()); } + + [TestMethod, Timeout(10000)] + public void Audio_Pan_ToMono() + { + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var success = FFMpegArguments.FromFileInput(TestResources.Mp4Video) + .OutputToFile(outputFile, true, + argumentOptions => argumentOptions + .WithAudioFilters(filter => filter.Pan(1, "c0 < 0.9 * c0 + 0.1 * c1"))) + .ProcessSynchronously(); + + var mediaAnalysis = FFProbe.Analyse(outputFile); + + Assert.IsTrue(success); + Assert.AreEqual(1, mediaAnalysis.AudioStreams.Count); + } + + [TestMethod, Timeout(10000)] + public void Audio_Pan_ToMonoNoDefinitions() + { + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var success = FFMpegArguments.FromFileInput(TestResources.Mp4Video) + .OutputToFile(outputFile, true, + argumentOptions => argumentOptions + .WithAudioFilters(filter => filter.Pan(1))) + .ProcessSynchronously(); + + var mediaAnalysis = FFProbe.Analyse(outputFile); + + Assert.IsTrue(success); + Assert.AreEqual(1, mediaAnalysis.AudioStreams.Count); + } + + [TestMethod, Timeout(10000)] + public void Audio_Pan_ToMonoChannelsToOutputDefinitionsMismatch() + { + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var ex = Assert.ThrowsException(() => FFMpegArguments.FromFileInput(TestResources.Mp4Video) + .OutputToFile(outputFile, true, + argumentOptions => argumentOptions + .WithAudioFilters(filter => filter.Pan(1, "c0=c0", "c1=c1"))) + .ProcessSynchronously()); + } + + [TestMethod, Timeout(10000)] + public void Audio_Pan_ToMonoChannelsLayoutToOutputDefinitionsMismatch() + { + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var ex = Assert.ThrowsException(() => FFMpegArguments.FromFileInput(TestResources.Mp4Video) + .OutputToFile(outputFile, true, + argumentOptions => argumentOptions + .WithAudioFilters(filter => filter.Pan("mono", "c0=c0", "c1=c1"))) + .ProcessSynchronously()); + } } } \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Arguments/AudioFiltersArgument.cs b/FFMpegCore/FFMpeg/Arguments/AudioFiltersArgument.cs index 834b784..4776d81 100644 --- a/FFMpegCore/FFMpeg/Arguments/AudioFiltersArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/AudioFiltersArgument.cs @@ -1,7 +1,5 @@ using System.Collections.Generic; -using System.Drawing; using System.Linq; -using FFMpegCore.Enums; using FFMpegCore.Exceptions; namespace FFMpegCore.Arguments @@ -43,7 +41,10 @@ public interface IAudioFilterArgument public class AudioFilterOptions { public List Arguments { get; } = new List(); - + + public AudioFilterOptions Pan(string channelLayout, params string[] outputDefinitions) => WithArgument(new PanArgument(channelLayout, outputDefinitions)); + public AudioFilterOptions Pan(int channels, params string[] outputDefinitions) => WithArgument(new PanArgument(channels, outputDefinitions)); + private AudioFilterOptions WithArgument(IAudioFilterArgument argument) { Arguments.Add(argument); diff --git a/FFMpegCore/FFMpeg/Arguments/PanArgument.cs b/FFMpegCore/FFMpeg/Arguments/PanArgument.cs new file mode 100644 index 0000000..74d5699 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/PanArgument.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents scale parameter + /// + public class PanArgument : IAudioFilterArgument + { + public readonly string ChannelLayout; + private readonly string[] _outputDefinitions; + + public PanArgument(string channelLayout, params string[] outputDefinitions) + { + if (string.IsNullOrWhiteSpace(channelLayout)) + { + throw new ArgumentException("The channel layout must be set" ,nameof(channelLayout)); + } + + ChannelLayout = channelLayout; + + _outputDefinitions = outputDefinitions; + } + + public PanArgument(int channels, params string[] outputDefinitions) + { + if (channels <= 0) throw new ArgumentOutOfRangeException(nameof(channels)); + + if (outputDefinitions.Length > channels) + throw new ArgumentException("The number of output definitions must be equal or lower than number of channels", nameof(outputDefinitions)); + + ChannelLayout = $"{channels}c"; + + _outputDefinitions = outputDefinitions; + } + + public string Key { get; } = "pan"; + + public string Value => + string.Join("|", Enumerable.Empty().Append(ChannelLayout).Concat(_outputDefinitions)); + } +}