From 8a764eccc7e0ca37d9df2e0da8694a8dbdb2bdcc Mon Sep 17 00:00:00 2001 From: AddyMills <74471839+AddyMills@users.noreply.github.com> Date: Fri, 1 Mar 2024 07:08:16 -0600 Subject: [PATCH 1/7] Add MultiInputArgument --- .../FFMpeg/Arguments/MultiInputArgument.cs | 47 +++++++++++++++++++ FFMpegCore/FFMpeg/FFMpegArguments.cs | 2 + FFMpegCore/FFMpegCore.csproj | 4 +- 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 FFMpegCore/FFMpeg/Arguments/MultiInputArgument.cs diff --git a/FFMpegCore/FFMpeg/Arguments/MultiInputArgument.cs b/FFMpegCore/FFMpeg/Arguments/MultiInputArgument.cs new file mode 100644 index 0000000..e64d96a --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/MultiInputArgument.cs @@ -0,0 +1,47 @@ +namespace FFMpegCore.Arguments +{ + /// + /// Represents input parameters for multiple files + /// + public class MultiInputArgument : IInputArgument + { + public readonly bool VerifyExists; + public readonly string[] FilePaths; + + public MultiInputArgument(bool verifyExists, params string[] filePaths) + { + VerifyExists = verifyExists; + FilePaths = filePaths; + } + + public MultiInputArgument(string[] filePaths, bool verifyExists) : this(verifyExists, filePaths) { } + + public void Pre() + { + if (VerifyExists) + { + var missingFiles = new List(); + foreach (var filePath in FilePaths) + { + if (!File.Exists(filePath)) + { + missingFiles.Add(filePath); + } + } + + if (missingFiles.Any()) + { + throw new FileNotFoundException($"The following input files were not found: {string.Join(", ", missingFiles)}"); + } + } + } + + public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask; + public void Post() { } + + /// + /// Generates a combined input argument text for all file paths + /// + public string Text => string.Join(" ", FilePaths.Select(filePath => $"-i \"{filePath}\"")); + } +} diff --git a/FFMpegCore/FFMpeg/FFMpegArguments.cs b/FFMpegCore/FFMpeg/FFMpegArguments.cs index cfc6d9d..cf57b84 100644 --- a/FFMpegCore/FFMpeg/FFMpegArguments.cs +++ b/FFMpegCore/FFMpeg/FFMpegArguments.cs @@ -21,6 +21,7 @@ private string GetText() public static FFMpegArguments FromConcatInput(IEnumerable filePaths, Action? addArguments = null) => new FFMpegArguments().WithInput(new ConcatArgument(filePaths), addArguments); public static FFMpegArguments FromDemuxConcatInput(IEnumerable filePaths, Action? addArguments = null) => new FFMpegArguments().WithInput(new DemuxConcatArgument(filePaths), addArguments); public static FFMpegArguments FromFileInput(string filePath, bool verifyExists = true, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(verifyExists, filePath), addArguments); + public static FFMpegArguments FromFileInput(string[] filePath, bool verifyExists = true, Action? addArguments = null) => new FFMpegArguments().WithInput(new MultiInputArgument(verifyExists, filePath), addArguments); public static FFMpegArguments FromFileInput(FileInfo fileInfo, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(fileInfo.FullName, false), addArguments); public static FFMpegArguments FromUrlInput(Uri uri, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(uri.AbsoluteUri, false), addArguments); public static FFMpegArguments FromDeviceInput(string device, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputDeviceArgument(device), addArguments); @@ -35,6 +36,7 @@ public FFMpegArguments WithGlobalOptions(Action configure public FFMpegArguments AddConcatInput(IEnumerable filePaths, Action? addArguments = null) => WithInput(new ConcatArgument(filePaths), addArguments); public FFMpegArguments AddDemuxConcatInput(IEnumerable filePaths, Action? addArguments = null) => WithInput(new DemuxConcatArgument(filePaths), addArguments); public FFMpegArguments AddFileInput(string filePath, bool verifyExists = true, Action? addArguments = null) => WithInput(new InputArgument(verifyExists, filePath), addArguments); + public FFMpegArguments AddFileInput(string[] filePath, bool verifyExists = true, Action? addArguments = null) => WithInput(new MultiInputArgument(verifyExists, filePath), addArguments); public FFMpegArguments AddFileInput(FileInfo fileInfo, Action? addArguments = null) => WithInput(new InputArgument(fileInfo.FullName, false), addArguments); public FFMpegArguments AddUrlInput(Uri uri, Action? addArguments = null) => WithInput(new InputArgument(uri.AbsoluteUri, false), addArguments); public FFMpegArguments AddDeviceInput(string device, Action? addArguments = null) => WithInput(new InputDeviceArgument(device), addArguments); diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj index ed3b71c..e6a45e9 100644 --- a/FFMpegCore/FFMpegCore.csproj +++ b/FFMpegCore/FFMpegCore.csproj @@ -3,7 +3,7 @@ true A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your .NET applications - 5.1.0 + 5.1.1 ../nupkg @@ -20,5 +20,7 @@ + + From 8f6d1aa8e959442a0116986d691dd7358fe476d0 Mon Sep 17 00:00:00 2001 From: AddyMills <74471839+AddyMills@users.noreply.github.com> Date: Fri, 1 Mar 2024 07:13:55 -0600 Subject: [PATCH 2/7] Update MultiInput to IEnumerable --- FFMpegCore/FFMpeg/Arguments/MultiInputArgument.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FFMpegCore/FFMpeg/Arguments/MultiInputArgument.cs b/FFMpegCore/FFMpeg/Arguments/MultiInputArgument.cs index e64d96a..288c761 100644 --- a/FFMpegCore/FFMpeg/Arguments/MultiInputArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/MultiInputArgument.cs @@ -6,15 +6,15 @@ public class MultiInputArgument : IInputArgument { public readonly bool VerifyExists; - public readonly string[] FilePaths; + public readonly IEnumerable FilePaths; - public MultiInputArgument(bool verifyExists, params string[] filePaths) + public MultiInputArgument(bool verifyExists, IEnumerable filePaths) { VerifyExists = verifyExists; FilePaths = filePaths; } - public MultiInputArgument(string[] filePaths, bool verifyExists) : this(verifyExists, filePaths) { } + public MultiInputArgument(IEnumerable filePaths, bool verifyExists) : this(verifyExists, filePaths) { } public void Pre() { From d83650168121c2c5e6b98aa9977ada3cb31b076f Mon Sep 17 00:00:00 2001 From: AddyMills <74471839+AddyMills@users.noreply.github.com> Date: Sun, 3 Mar 2024 11:42:52 -0600 Subject: [PATCH 3/7] Change string array to IEnumerable --- FFMpegCore/FFMpeg/FFMpegArguments.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FFMpegCore/FFMpeg/FFMpegArguments.cs b/FFMpegCore/FFMpeg/FFMpegArguments.cs index cf57b84..08b9d19 100644 --- a/FFMpegCore/FFMpeg/FFMpegArguments.cs +++ b/FFMpegCore/FFMpeg/FFMpegArguments.cs @@ -21,7 +21,7 @@ private string GetText() public static FFMpegArguments FromConcatInput(IEnumerable filePaths, Action? addArguments = null) => new FFMpegArguments().WithInput(new ConcatArgument(filePaths), addArguments); public static FFMpegArguments FromDemuxConcatInput(IEnumerable filePaths, Action? addArguments = null) => new FFMpegArguments().WithInput(new DemuxConcatArgument(filePaths), addArguments); public static FFMpegArguments FromFileInput(string filePath, bool verifyExists = true, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(verifyExists, filePath), addArguments); - public static FFMpegArguments FromFileInput(string[] filePath, bool verifyExists = true, Action? addArguments = null) => new FFMpegArguments().WithInput(new MultiInputArgument(verifyExists, filePath), addArguments); + public static FFMpegArguments FromFileInput(IEnumerable filePath, bool verifyExists = true, Action? addArguments = null) => new FFMpegArguments().WithInput(new MultiInputArgument(verifyExists, filePath), addArguments); public static FFMpegArguments FromFileInput(FileInfo fileInfo, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(fileInfo.FullName, false), addArguments); public static FFMpegArguments FromUrlInput(Uri uri, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(uri.AbsoluteUri, false), addArguments); public static FFMpegArguments FromDeviceInput(string device, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputDeviceArgument(device), addArguments); From 622db9600c66f058085ee89b8487a4734dc02016 Mon Sep 17 00:00:00 2001 From: AddyMills <74471839+AddyMills@users.noreply.github.com> Date: Sun, 3 Mar 2024 12:49:34 -0600 Subject: [PATCH 4/7] Add MultiInput Test --- FFMpegCore.Test/ArgumentBuilderTest.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs index cf455c8..00b0c96 100644 --- a/FFMpegCore.Test/ArgumentBuilderTest.cs +++ b/FFMpegCore.Test/ArgumentBuilderTest.cs @@ -9,6 +9,7 @@ namespace FFMpegCore.Test public class ArgumentBuilderTest { private readonly string[] _concatFiles = { "1.mp4", "2.mp4", "3.mp4", "4.mp4" }; + private readonly string[] _multiFiles = { "1.mp3", "2.mp3", "3.mp3", "4.mp3" }; [TestMethod] public void Builder_BuildString_IO_1() @@ -611,5 +612,24 @@ public void Builder_BuildString_TeeOutput() -i "input.mp4" -f tee "[movflags=faststart]output.mp4|[f=mpegts:select=\'0:v:0\']http://server/path" """, str); } + [TestMethod] + public void Builder_BuildString_MultiInput() + { + var audioStreams = string.Join("", _multiFiles.Select((item, index) => $"[{index}:0]")); + var mixFilter = $"{audioStreams}amix=inputs={_multiFiles.Length}:duration=longest:dropout_transition=1:normalize=0[final]"; + var ffmpegArgs = $"-filter_complex \"{mixFilter}\" -map \"[final]\""; + var str = FFMpegArguments + .FromFileInput(_multiFiles) + .OutputToFile("output.mp3", overwrite: true, options => options + .WithCustomArgument(ffmpegArgs) + .WithAudioCodec(AudioCodec.LibMp3Lame) // Set the audio codec to MP3 + .WithAudioBitrate(128) // Set the bitrate to 128kbps + .WithAudioSamplingRate(48000) // Set the sample rate to 48kHz + .WithoutMetadata() // Remove metadata + .WithCustomArgument("-ac 2 -write_xing 0 -id3v2_version 0")) // Force 2 Channels + .Arguments; + + Assert.AreEqual($"-i \"1.mp3\" -i \"2.mp3\" -i \"3.mp3\" -i \"4.mp3\" -filter_complex \"[0:0][1:0][2:0][3:0]amix=inputs=4:duration=longest:dropout_transition=1:normalize=0[final]\" -map \"[final]\" -c:a libmp3lame -b:a 128k -ar 48000 -map_metadata -1 -ac 2 -write_xing 0 -id3v2_version 0 \"output.mp3\" -y", str); + } } } From bb341e6dd7260196f13c69362aac3f6574b1cf95 Mon Sep 17 00:00:00 2001 From: AddyMills <74471839+AddyMills@users.noreply.github.com> Date: Sun, 3 Mar 2024 13:02:50 -0600 Subject: [PATCH 5/7] Update one more instance of string[] to IEnumerable --- FFMpegCore/FFMpeg/FFMpegArguments.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FFMpegCore/FFMpeg/FFMpegArguments.cs b/FFMpegCore/FFMpeg/FFMpegArguments.cs index 08b9d19..ddb1f72 100644 --- a/FFMpegCore/FFMpeg/FFMpegArguments.cs +++ b/FFMpegCore/FFMpeg/FFMpegArguments.cs @@ -36,7 +36,7 @@ public FFMpegArguments WithGlobalOptions(Action configure public FFMpegArguments AddConcatInput(IEnumerable filePaths, Action? addArguments = null) => WithInput(new ConcatArgument(filePaths), addArguments); public FFMpegArguments AddDemuxConcatInput(IEnumerable filePaths, Action? addArguments = null) => WithInput(new DemuxConcatArgument(filePaths), addArguments); public FFMpegArguments AddFileInput(string filePath, bool verifyExists = true, Action? addArguments = null) => WithInput(new InputArgument(verifyExists, filePath), addArguments); - public FFMpegArguments AddFileInput(string[] filePath, bool verifyExists = true, Action? addArguments = null) => WithInput(new MultiInputArgument(verifyExists, filePath), addArguments); + public FFMpegArguments AddFileInput(IEnumerable filePath, bool verifyExists = true, Action? addArguments = null) => WithInput(new MultiInputArgument(verifyExists, filePath), addArguments); public FFMpegArguments AddFileInput(FileInfo fileInfo, Action? addArguments = null) => WithInput(new InputArgument(fileInfo.FullName, false), addArguments); public FFMpegArguments AddUrlInput(Uri uri, Action? addArguments = null) => WithInput(new InputArgument(uri.AbsoluteUri, false), addArguments); public FFMpegArguments AddDeviceInput(string device, Action? addArguments = null) => WithInput(new InputDeviceArgument(device), addArguments); From 31b117d1862fd80b7df928291986e2f693ff2c74 Mon Sep 17 00:00:00 2001 From: AddyMills <74471839+AddyMills@users.noreply.github.com> Date: Sun, 3 Mar 2024 13:09:52 -0600 Subject: [PATCH 6/7] Remove whitespace --- FFMpegCore.Test/ArgumentBuilderTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs index 00b0c96..b6ae6dd 100644 --- a/FFMpegCore.Test/ArgumentBuilderTest.cs +++ b/FFMpegCore.Test/ArgumentBuilderTest.cs @@ -628,7 +628,6 @@ public void Builder_BuildString_MultiInput() .WithoutMetadata() // Remove metadata .WithCustomArgument("-ac 2 -write_xing 0 -id3v2_version 0")) // Force 2 Channels .Arguments; - Assert.AreEqual($"-i \"1.mp3\" -i \"2.mp3\" -i \"3.mp3\" -i \"4.mp3\" -filter_complex \"[0:0][1:0][2:0][3:0]amix=inputs=4:duration=longest:dropout_transition=1:normalize=0[final]\" -map \"[final]\" -c:a libmp3lame -b:a 128k -ar 48000 -map_metadata -1 -ac 2 -write_xing 0 -id3v2_version 0 \"output.mp3\" -y", str); } } From 94db493d1951b7a72698b972d6f345d6860ddac6 Mon Sep 17 00:00:00 2001 From: AddyMills <74471839+AddyMills@users.noreply.github.com> Date: Sun, 3 Mar 2024 18:56:06 -0600 Subject: [PATCH 7/7] Add IEnumerable tests for inputs --- FFMpegCore.Test/ArgumentBuilderTest.cs | 63 ++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs index b6ae6dd..2b6c0d4 100644 --- a/FFMpegCore.Test/ArgumentBuilderTest.cs +++ b/FFMpegCore.Test/ArgumentBuilderTest.cs @@ -630,5 +630,68 @@ public void Builder_BuildString_MultiInput() .Arguments; Assert.AreEqual($"-i \"1.mp3\" -i \"2.mp3\" -i \"3.mp3\" -i \"4.mp3\" -filter_complex \"[0:0][1:0][2:0][3:0]amix=inputs=4:duration=longest:dropout_transition=1:normalize=0[final]\" -map \"[final]\" -c:a libmp3lame -b:a 128k -ar 48000 -map_metadata -1 -ac 2 -write_xing 0 -id3v2_version 0 \"output.mp3\" -y", str); } + [TestMethod] + public void Pre_VerifyExists_AllFilesExist() + { + // Arrange + var filePaths = new List + { + Path.GetTempFileName(), + Path.GetTempFileName(), + Path.GetTempFileName() + }; + var argument = new MultiInputArgument(true, filePaths); + try + { + // Act & Assert + argument.Pre(); // No exception should be thrown + } + finally + { + // Cleanup + foreach (var filePath in filePaths) + { + File.Delete(filePath); + } + } + } + + [TestMethod] + public void Pre_VerifyExists_SomeFilesNotExist() + { + // Arrange + var filePaths = new List + { + Path.GetTempFileName(), + "file2.mp4", + "file3.mp4" + }; + var argument = new MultiInputArgument(true, filePaths); + try + { + // Act & Assert + Assert.ThrowsException(() => argument.Pre()); + } + finally + { + // Cleanup + File.Delete(filePaths[0]); + } + } + + [TestMethod] + public void Pre_VerifyExists_NoFilesExist() + { + // Arrange + var filePaths = new List + { + "file1.mp4", + "file2.mp4", + "file3.mp4" + }; + var argument = new MultiInputArgument(true, filePaths); + // Act & Assert + Assert.ThrowsException(() => argument.Pre()); + } } }