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 0000000..b71f09e Binary files /dev/null and b/FileTypeChecker.Tests/files/test.flac differ diff --git a/FileTypeChecker.Tests/files/test.m4a b/FileTypeChecker.Tests/files/test.m4a new file mode 100644 index 0000000..f48d494 Binary files /dev/null and b/FileTypeChecker.Tests/files/test.m4a differ diff --git a/FileTypeChecker.Tests/files/test.ogg b/FileTypeChecker.Tests/files/test.ogg new file mode 100644 index 0000000..335f7e8 Binary files /dev/null and b/FileTypeChecker.Tests/files/test.ogg differ 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) + { + } + } +}