From acaac7efbad24c646c8b96a31b8cac6dead1d970 Mon Sep 17 00:00:00 2001 From: Ross Wilson Date: Thu, 23 Apr 2026 18:42:26 -0600 Subject: [PATCH] Add M4A, FLAC, OGG audio types and IsAudio() extension methods New file type definitions: - M4A: ftyp box with M4A/M4B brand (audio/mp4) - FLAC: fLaC magic marker (audio/flac) - OGG: OggS capture pattern (audio/ogg) New IsAudio() convenience methods on Stream, byte[], and ReadOnlySpan, plus async variants, matching the existing IsImage/IsArchive/IsExecutable/IsDocument pattern. Includes test files and 27 new test cases across sync, async, and byte array test suites. --- .../FileTypeChecker.Tests.csproj | 9 +++++ .../FileTypeValidatorAsyncTests.cs | 6 ++++ .../FileTypeValidatorTests.cs | 21 +++++++++++ FileTypeChecker.Tests/files/test.flac | Bin 0 -> 32345 bytes FileTypeChecker.Tests/files/test.m4a | Bin 0 -> 18856 bytes FileTypeChecker.Tests/files/test.ogg | Bin 0 -> 19371 bytes FileTypeChecker/Extensions/ByteExtensions.cs | 29 +++++++++++++++ .../Extensions/StreamExtensions.cs | 31 ++++++++++++++++ FileTypeChecker/FileTypeValidator.cs | 34 ++++++++++++++++++ FileTypeChecker/Types/Flac.cs | 19 ++++++++++ FileTypeChecker/Types/M4a.cs | 23 ++++++++++++ FileTypeChecker/Types/Ogg.cs | 19 ++++++++++ 12 files changed, 191 insertions(+) create mode 100644 FileTypeChecker.Tests/files/test.flac create mode 100644 FileTypeChecker.Tests/files/test.m4a create mode 100644 FileTypeChecker.Tests/files/test.ogg create mode 100644 FileTypeChecker/Types/Flac.cs create mode 100644 FileTypeChecker/Types/M4a.cs create mode 100644 FileTypeChecker/Types/Ogg.cs diff --git a/FileTypeChecker.Tests/FileTypeChecker.Tests.csproj b/FileTypeChecker.Tests/FileTypeChecker.Tests.csproj index f7ca308..828f8a8 100644 --- a/FileTypeChecker.Tests/FileTypeChecker.Tests.csproj +++ b/FileTypeChecker.Tests/FileTypeChecker.Tests.csproj @@ -131,5 +131,14 @@ Always + + Always + + + Always + + + Always + diff --git a/FileTypeChecker.Tests/FileTypeValidatorAsyncTests.cs b/FileTypeChecker.Tests/FileTypeValidatorAsyncTests.cs index d424829..fa7d383 100644 --- a/FileTypeChecker.Tests/FileTypeValidatorAsyncTests.cs +++ b/FileTypeChecker.Tests/FileTypeValidatorAsyncTests.cs @@ -59,6 +59,9 @@ public async Task IsTypeRecognizableAsync_ShouldReturnFalseIfFormatIsUnknown() [TestCase("test.mp4")] [TestCase("file_example_AVI_480_750kB.avi")] [TestCase("file_example_WAV_1MG.wav")] + [TestCase("test.m4a")] + [TestCase("test.flac")] + [TestCase("test.ogg")] public async Task IsTypeRecognizableAsync_ShouldReturnTrueIfFileIsRecognized(string fileName) { await using var fileStream = File.OpenRead(Path.Combine(FilesPath, fileName)); @@ -104,6 +107,9 @@ public void GetFileTypeAsync_ShouldThrowTypeNotFoundExceptionWhenTypeIsNotRegist [TestCase("test-issue-41.xlsx", typeof(MicrosoftOffice365Document))] [TestCase("file_example_AVI_480_750kB.avi", typeof(AudioVideoInterleaveVideoFormat))] [TestCase("file_example_WAV_1MG.wav", typeof(WaveformAudioFileFormat))] + [TestCase("test.m4a", typeof(M4a))] + [TestCase("test.flac", typeof(Flac))] + [TestCase("test.ogg", typeof(Ogg))] public async Task GetFileTypeAsync_ShouldReturnAccurateType(string fileName, Type expectedType) { await using var fileStream = File.OpenRead(Path.Combine(FilesPath, fileName)); diff --git a/FileTypeChecker.Tests/FileTypeValidatorTests.cs b/FileTypeChecker.Tests/FileTypeValidatorTests.cs index be7acec..4fe82e6 100644 --- a/FileTypeChecker.Tests/FileTypeValidatorTests.cs +++ b/FileTypeChecker.Tests/FileTypeValidatorTests.cs @@ -60,6 +60,9 @@ public void IsTypeRecognizable_ShouldReturnFalseIfFormatIsUnknown() [TestCase("test.mp4")] [TestCase("file_example_AVI_480_750kB.avi")] [TestCase("file_example_WAV_1MG.wav")] + [TestCase("test.m4a")] + [TestCase("test.flac")] + [TestCase("test.ogg")] public void IsTypeRecognizable_ShouldReturnTrueIfFileIsRecognized(string filePath) { using var fileStream = File.OpenRead(Path.Combine(FilesPath, filePath)); @@ -95,6 +98,9 @@ public void IsTypeRecognizable_ShouldReturnTrueIfFileIsRecognized(string filePat [TestCase("FileTypeCheckerLogo-150.heic", HighEfficiencyImageFile.TypeMimeType)] [TestCase("file_example_AVI_480_750kB.avi", AudioVideoInterleaveVideoFormat.TypeMimeType)] [TestCase("file_example_WAV_1MG.wav", WaveformAudioFileFormat.TypeMimeType)] + [TestCase("test.m4a", M4a.TypeMimeType)] + [TestCase("test.flac", Flac.TypeMimeType)] + [TestCase("test.ogg", Ogg.TypeMimeType)] public void GetFileType_ShouldReturnFileMimeType(string filePath, string expectedFileExtension) { using var fileStream = File.OpenRead(Path.Combine(FilesPath, filePath)); @@ -128,6 +134,9 @@ public void GetFileType_ShouldReturnFileMimeType(string filePath, string expecte [TestCase("FileTypeCheckerLogo-150.heic", HighEfficiencyImageFile.TypeMimeType)] [TestCase("file_example_AVI_480_750kB.avi", AudioVideoInterleaveVideoFormat.TypeMimeType)] [TestCase("file_example_WAV_1MG.wav", WaveformAudioFileFormat.TypeMimeType)] + [TestCase("test.m4a", M4a.TypeMimeType)] + [TestCase("test.flac", Flac.TypeMimeType)] + [TestCase("test.ogg", Ogg.TypeMimeType)] public void TryGetFileType_ShouldReturnFileMimeType(string filePath, string expectedFileExtension) { using var fileStream = File.OpenRead(Path.Combine(FilesPath, filePath)); @@ -160,6 +169,9 @@ public void TryGetFileType_ShouldReturnFileMimeType(string filePath, string expe [TestCase("FileTypeCheckerLogo-150.heic", HighEfficiencyImageFile.TypeExtension)] [TestCase("file_example_AVI_480_750kB.avi", AudioVideoInterleaveVideoFormat.TypeExtension)] [TestCase("file_example_WAV_1MG.wav", WaveformAudioFileFormat.TypeExtension)] + [TestCase("test.m4a", M4a.TypeExtension)] + [TestCase("test.flac", Flac.TypeExtension)] + [TestCase("test.ogg", Ogg.TypeExtension)] public void GetFileType_ShouldReturnFileExtension(string filePath, string expectedFileExtension) { using var fileStream = File.OpenRead(Path.Combine(FilesPath, filePath)); @@ -194,6 +206,9 @@ public void GetFileType_ShouldReturnFileExtension(string filePath, string expect [TestCase("test-offset.mp4", M4V.TypeExtension)] [TestCase("file_example_AVI_480_750kB.avi", AudioVideoInterleaveVideoFormat.TypeExtension)] [TestCase("file_example_WAV_1MG.wav", WaveformAudioFileFormat.TypeExtension)] + [TestCase("test.m4a", M4a.TypeExtension)] + [TestCase("test.flac", Flac.TypeExtension)] + [TestCase("test.ogg", Ogg.TypeExtension)] public void TryGetFileType_ShouldReturnFileExtension(string filePath, string expectedFileExtension) { using var fileStream = File.OpenRead(Path.Combine(FilesPath, filePath)); @@ -274,6 +289,9 @@ public void TryGetFileType_TypeShouldBeNullWhenNotMatching() [TestCase("FileTypeCheckerLogo-150.heic", HighEfficiencyImageFile.TypeName)] [TestCase("file_example_AVI_480_750kB.avi", AudioVideoInterleaveVideoFormat.TypeName)] [TestCase("file_example_WAV_1MG.wav", WaveformAudioFileFormat.TypeName)] + [TestCase("test.m4a", M4a.TypeName)] + [TestCase("test.flac", Flac.TypeName)] + [TestCase("test.ogg", Ogg.TypeName)] public void GetFileType_ShouldReturnFileName(string filePath, string expectedFileTypeName) { using var fileStream = File.OpenRead(Path.Combine(FilesPath, filePath)); @@ -342,6 +360,9 @@ public void Is_ShouldReturnFalseIfTypesDidNotMatch() [TestCase("test-issue-41.xlsx", typeof(MicrosoftOffice365Document))] [TestCase("file_example_AVI_480_750kB.avi", typeof(AudioVideoInterleaveVideoFormat))] [TestCase("file_example_WAV_1MG.wav", typeof(WaveformAudioFileFormat))] + [TestCase("test.m4a", typeof(M4a))] + [TestCase("test.flac", typeof(Flac))] + [TestCase("test.ogg", typeof(Ogg))] public void GetFileType_ShouldReturnAccurateTypeWhenUsingBytes(string fileName, Type expectedType) { var buffer = GetFileBytes(fileName); diff --git a/FileTypeChecker.Tests/files/test.flac b/FileTypeChecker.Tests/files/test.flac new file mode 100644 index 0000000000000000000000000000000000000000..b71f09e884280a70da0f78c45dc1a3b70ab442a3 GIT binary patch literal 32345 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%0DdFBf#oFf4N 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 literal 0 HcmV?d00001 diff --git a/FileTypeChecker.Tests/files/test.ogg b/FileTypeChecker.Tests/files/test.ogg new file mode 100644 index 0000000000000000000000000000000000000000..335f7e881934a2fecd92e44b9d93bbaa33f94db6 GIT binary patch literal 19371 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 TOQXY2AS3lQjo8@@!@T|lWLWaf literal 0 HcmV?d00001 diff --git a/FileTypeChecker/Extensions/ByteExtensions.cs b/FileTypeChecker/Extensions/ByteExtensions.cs index 275cd90..f08688b 100644 --- a/FileTypeChecker/Extensions/ByteExtensions.cs +++ b/FileTypeChecker/Extensions/ByteExtensions.cs @@ -97,6 +97,20 @@ public static bool IsArchive(this byte[] content) || content.Is() || content.Is(); + /// + /// Validates that the current file is an audio file. + /// + /// File content as byte array. + /// Returns true if the provided file is an audio file otherwise returns false. Supported audio types are: MP3, WAV, FLAC, OGG, M4A, and WMA. + public static bool IsAudio(this byte[] content) + => content.Is() + || content.Is() + || content.Is() + || content.Is() + || content.Is() + || content.Is() + || content.Is(); + /// /// Validates that the current file is executable or executable and linkable. /// @@ -173,6 +187,21 @@ public static bool IsArchive(this ReadOnlySpan content) || content.Is() || content.Is(); + /// + /// Validates that the current file is an audio file using high-performance ReadOnlySpan. + /// This overload avoids memory allocations and provides optimal performance. + /// + /// File content as ReadOnlySpan of bytes. + /// Returns true if the provided file is an audio file otherwise returns false. Supported audio types are: MP3, WAV, FLAC, OGG, M4A, and WMA. + public static bool IsAudio(this ReadOnlySpan content) + => content.Is() + || content.Is() + || content.Is() + || content.Is() + || content.Is() + || content.Is() + || content.Is(); + /// /// Validates that the current file is executable or executable and linkable using high-performance ReadOnlySpan. /// This overload avoids memory allocations and provides optimal performance. diff --git a/FileTypeChecker/Extensions/StreamExtensions.cs b/FileTypeChecker/Extensions/StreamExtensions.cs index 5923e04..4b27ebe 100644 --- a/FileTypeChecker/Extensions/StreamExtensions.cs +++ b/FileTypeChecker/Extensions/StreamExtensions.cs @@ -59,6 +59,20 @@ public static bool IsArchive(this Stream fileContent) || fileContent.Is() || fileContent.Is(); + /// + /// Validates that the current file is an audio file. + /// + /// File to check as a stream. + /// True if the provided file is an audio file; otherwise, false. Supported audio types include: MP3, WAV, FLAC, OGG, M4A, and WMA. + public static bool IsAudio(this Stream fileContent) + => fileContent.Is() + || fileContent.Is() + || fileContent.Is() + || fileContent.Is() + || fileContent.Is() + || fileContent.Is() + || fileContent.Is(); + /// /// Validates that the current file is an executable. /// @@ -139,6 +153,23 @@ public static async Task IsArchiveAsync(this Stream fileContent, Cancellat || await fileContent.IsAsync(cancellationToken).ConfigureAwait(false); } + /// + /// Asynchronously validates that the current file is an audio file. + /// + /// File to check as a stream. + /// A cancellation token that can be used to cancel the asynchronous operation. + /// A task that represents the asynchronous operation. The task result contains true if the provided file is an audio file; otherwise, false. Supported audio types include: MP3, WAV, FLAC, OGG, M4A, and WMA. + public static async Task IsAudioAsync(this Stream fileContent, CancellationToken cancellationToken = default) + { + return await fileContent.IsAsync(cancellationToken).ConfigureAwait(false) + || await fileContent.IsAsync(cancellationToken).ConfigureAwait(false) + || await fileContent.IsAsync(cancellationToken).ConfigureAwait(false) + || await fileContent.IsAsync(cancellationToken).ConfigureAwait(false) + || await fileContent.IsAsync(cancellationToken).ConfigureAwait(false) + || await fileContent.IsAsync(cancellationToken).ConfigureAwait(false) + || await fileContent.IsAsync(cancellationToken).ConfigureAwait(false); + } + /// /// Asynchronously validates that the current file is an executable. /// diff --git a/FileTypeChecker/FileTypeValidator.cs b/FileTypeChecker/FileTypeValidator.cs index 9e089e0..99e4fe0 100644 --- a/FileTypeChecker/FileTypeValidator.cs +++ b/FileTypeChecker/FileTypeValidator.cs @@ -156,6 +156,14 @@ public static void RegisterCustomTypes(params Assembly[] assemblies) public static bool Is(Stream fileContent) where T : FileType, IFileType, new() => fileContent.Is(); + /// + /// Validates that the current file is an audio file. + /// + /// File to check as a stream. + /// Returns true if the provided file is an audio file otherwise returns false. Supported audio types are: MP3, WAV, FLAC, OGG, M4A, and WMA. + public static bool IsAudio(Stream fileContent) + => fileContent.IsAudio(); + /// /// Validates that the current file is an image. /// @@ -181,6 +189,14 @@ public static bool IsArchive(Stream fileContent) public static bool Is(byte[] fileContent) where T : FileType, IFileType, new() => fileContent.Is(); + /// + /// Validates that the current file is an audio file. + /// + /// File bytes. + /// Returns true if the provided file is an audio file otherwise returns false. Supported audio types are: MP3, WAV, FLAC, OGG, M4A, and WMA. + public static bool IsAudio(byte[] fileContent) + => fileContent.IsAudio(); + /// /// Validates that the current file is an image. /// @@ -274,6 +290,15 @@ public static async Task TryGetFileTypeAsync(Stream fileContent, Ca public static async Task IsAsync(Stream fileContent, CancellationToken cancellationToken = default) where T : FileType, IFileType, new() => await fileContent.IsAsync(cancellationToken).ConfigureAwait(false); + /// + /// Asynchronously validates that the current file is an audio file. + /// + /// File to check as a stream. + /// A cancellation token that can be used to cancel the asynchronous operation. + /// A task that represents the asynchronous operation. The task result contains true if the provided file is an audio file; otherwise, false. Supported audio types include: MP3, WAV, FLAC, OGG, M4A, and WMA. + public static async Task IsAudioAsync(Stream fileContent, CancellationToken cancellationToken = default) + => await fileContent.IsAudioAsync(cancellationToken).ConfigureAwait(false); + /// /// Asynchronously validates that the current file is an image. /// @@ -362,6 +387,15 @@ public static MatchResult TryGetFileType(ReadOnlySpan fileContent) public static bool Is(ReadOnlySpan fileContent) where T : FileType, IFileType, new() => fileContent.Is(); + /// + /// Validates that the current file is an audio file using high-performance ReadOnlySpan. + /// This overload avoids memory allocations and provides optimal performance. + /// + /// File content as ReadOnlySpan of bytes. + /// True if the provided file is an audio file; otherwise, false. Supported audio types include: MP3, WAV, FLAC, OGG, M4A, and WMA. + public static bool IsAudio(ReadOnlySpan fileContent) + => fileContent.IsAudio(); + /// /// Validates that the current file is an image using high-performance ReadOnlySpan. /// This overload avoids memory allocations and provides optimal performance. diff --git a/FileTypeChecker/Types/Flac.cs b/FileTypeChecker/Types/Flac.cs new file mode 100644 index 0000000..c3c4861 --- /dev/null +++ b/FileTypeChecker/Types/Flac.cs @@ -0,0 +1,19 @@ +namespace FileTypeChecker.Types +{ + using Abstracts; + + /// + /// Free Lossless Audio Codec file. Identified by the fLaC magic marker. + /// + public class Flac : FileType, IFileType + { + public const string TypeName = "FLAC audio file"; + public const string TypeMimeType = "audio/flac"; + public const string TypeExtension = "flac"; + private static readonly byte[] MagicBytes = { 0x66, 0x4C, 0x61, 0x43 }; // fLaC + + public Flac() : base(TypeName, TypeMimeType, TypeExtension, MagicBytes) + { + } + } +} diff --git a/FileTypeChecker/Types/M4a.cs b/FileTypeChecker/Types/M4a.cs new file mode 100644 index 0000000..32393e1 --- /dev/null +++ b/FileTypeChecker/Types/M4a.cs @@ -0,0 +1,23 @@ +using FileTypeChecker.Abstracts; + +namespace FileTypeChecker.Types +{ + /// + /// MPEG-4 audio file (AAC). Identified by ftyp box with M4A or M4B brand. + /// + public class M4a : FileType, IFileType + { + public const string TypeName = "MPEG-4 audio file"; + public const string TypeMimeType = "audio/mp4"; + public const string TypeExtension = "m4a"; + private static readonly MagicSequence[] MagicBytesSequence = + { + new(new byte[] { 0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41, 0x20 }, 4), // ftypM4A + new(new byte[] { 0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x42, 0x20 }, 4), // ftypM4B + }; + + public M4a() : base(TypeName, TypeMimeType, TypeExtension, MagicBytesSequence) + { + } + } +} diff --git a/FileTypeChecker/Types/Ogg.cs b/FileTypeChecker/Types/Ogg.cs new file mode 100644 index 0000000..52895be --- /dev/null +++ b/FileTypeChecker/Types/Ogg.cs @@ -0,0 +1,19 @@ +namespace FileTypeChecker.Types +{ + using Abstracts; + + /// + /// Ogg container file (Vorbis, Opus, FLAC). Identified by the OggS capture pattern. + /// + public class Ogg : FileType, IFileType + { + public const string TypeName = "Ogg audio file"; + public const string TypeMimeType = "audio/ogg"; + public const string TypeExtension = "ogg"; + private static readonly byte[] MagicBytes = { 0x4F, 0x67, 0x67, 0x53 }; // OggS + + public Ogg() : base(TypeName, TypeMimeType, TypeExtension, MagicBytes) + { + } + } +}