diff --git a/Inspector/Inspector.csproj b/Inspector/Inspector.csproj new file mode 100644 index 0000000..d1a5e8d --- /dev/null +++ b/Inspector/Inspector.csproj @@ -0,0 +1,28 @@ + + + + Exe + netcoreapp2.2 + + + + + + + + + Always + + + + + + + + + + + + + + diff --git a/Inspector/Program.cs b/Inspector/Program.cs new file mode 100644 index 0000000..bdbedc5 --- /dev/null +++ b/Inspector/Program.cs @@ -0,0 +1,29 @@ +using System; +using System.ComponentModel.DataAnnotations; +using McMaster.Extensions.CommandLineUtils; + +namespace Inspector +{ + public class Program + { + public static int Main(string[] args) + => CommandLineApplication.Execute(args); + + [Option(ShortName = "f", Description = "Folder To Inspect")] + [Required] + public string Folder { get; } + + [Option(ShortName = "d", Description = "Destination Folder")] + public string Destination { get; } + + [Option("-c", "Copy Dont Move Originals", CommandOptionType.NoValue)] + public bool DoCopy { get; } + + private void OnExecute() + { + var inspector = new Roadie.Library.Inspect.Inspector(); + inspector.Inspect(this.DoCopy, this.Folder, this.Destination ?? this.Folder); + + } + } +} diff --git a/Inspector/Properties/launchSettings.json b/Inspector/Properties/launchSettings.json new file mode 100644 index 0000000..d3d2a19 --- /dev/null +++ b/Inspector/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Inspector": { + "commandName": "Project", + "commandLineArgs": "-f \"C:\\roadie_dev_root\\inbound\"" + } + } +} \ No newline at end of file diff --git a/Inspector/appsettings.json b/Inspector/appsettings.json new file mode 100644 index 0000000..38a0675 --- /dev/null +++ b/Inspector/appsettings.json @@ -0,0 +1,104 @@ +{ + "RoadieSettings": { + "SiteName": "Roadie", + "DefaultTimeZone": "US/Central", + "DiagnosticsPassword": "RoadieDiagPassword", + "InboundFolder": "C:\\roadie_dev_root\\inbound", + "LibraryFolder": "C:\\\\roadie_dev_root\\\\library", + "Thumbnails": { + "Height": 80, + "Width": 80 + }, + "MediumThumbnails": { + "Height": 160, + "Width": 160 + }, + "LargeThumbnails": { + "Height": 320, + "Width": 320 + }, + "DontDoMetaDataProvidersSearchArtists": [ "Various Artists", "Sound Tracks" ], + "FileExtenionsToDelete": [ ".cue", ".db", ".gif", ".html", ".ini", ".jpg", ".jpeg", ".log", ".mpg", ".m3u", ".png", ".nfo", ".nzb", ".sfv", ".srr", ".txt", ".url" ], + "RecordNoResultSearches": true, + "SingleArtistHoldingFolder": "C:\\roadie_dev_root\\single_holding", + "ArtistNameReplace": { + "AC/DC": [ "AC; DC", "AC;DC", "AC/ DC", "AC DC" ], + "Love/Hate": [ "Love; Hate", "Love;Hate", "Love/ Hate", "Love Hate" ] + }, + "Integrations": { + "ITunesProviderEnabled": true, + "MusicBrainzProviderEnabled": true, + "SpotifyProviderEnabled": true, + "ApiKeys": [ + { + "ApiName": "BingImageSearch", + "Key": "" + }, + { + "ApiName": "LastFMApiKey", + "Key": "", + "KeySecret": "" + }, + { + "ApiName": "DiscogsConsumerKey", + "Key": "", + "KeySecret": "" + } + ] + }, + "Processing": { + "DoAudioCleanup": true, + "DoSaveEditsToTags": true, + "DoClearComments": true, + "DoParseFromFileName": true, + "DoParseFromDiscogsDBFindingTrackForArtist": true, + "DoParseFromDiscogsDB": true, + "DoParseFromMusicBrainz": true, + "DoParseFromLastFM": true, + "MaximumArtistImagesToAdd": 12, + "MaximumReleaseImagesToAdd": 12, + "MaxImageWidth": 2048, + "ReleaseRemoveStringsRegex": "(\\s*(-\\s)*((CD[0-9][0-9]*)))|((\\(|\\[)+([0-9]|,|self|bonus|released|th|anniversary|re|release|cd|disc|deluxe|digipack|vinyl|japanese|asian|remastered|limited|expanded|edition|\\s)+(]|\\)))", + "TrackRemoveStringsRegex": "^([0-9]+)(\\.|-|\\s)*", + "ReplaceStrings": [ + { + "order": 1, + "key": "-OBSERVER", + "replaceWith": "" + }, + { + "order": 2, + "key": "[Torrent Tatty]", + "replaceWith": "" + }, + { + "order": 3, + "key": "_", + "replaceWith": " " + }, + { + "order": 4, + "key": "-", + "replaceWith": " " + }, + { + "order": 5, + "key": "~", + "replaceWith": " " + }, + { + "order": 6, + "key": "^", + "replaceWith": " " + }, + { + "order": 7, + "key": "#", + "replaceWith": " " + } + ] + } + } +} + + diff --git a/Roadie.Api.Library.Tests/ID3TagsHelperTests.cs b/Roadie.Api.Library.Tests/ID3TagsHelperTests.cs index 2f90afa..d489fe2 100644 --- a/Roadie.Api.Library.Tests/ID3TagsHelperTests.cs +++ b/Roadie.Api.Library.Tests/ID3TagsHelperTests.cs @@ -179,6 +179,158 @@ namespace Roadie.Library.Tests Assert.Null(dn); } + [Fact] + public void ReadTotalTrackNumbersFromCue_Should_Be_Five() + { + var cuesDir = @"C:\roadie_dev_root\test_cue_and_playlists\cues1"; + var directory = new DirectoryInfo(cuesDir); + if(directory.Exists) + { + foreach(var file in Directory.GetFiles(cuesDir)) + { + var t = ID3TagsHelper.DetermineTotalTrackNumbers(file); + Assert.Equal(5, t.Value); + } + } + else + { + Assert.True(true); + } + } + + [Fact] + public void ReadTotalTrackNumbersFromCue_Should_Be_Eight() + { + var cuesDir = @"C:\roadie_dev_root\test_cue_and_playlists\cues2"; + var directory = new DirectoryInfo(cuesDir); + if (directory.Exists) + { + foreach (var file in Directory.GetFiles(cuesDir)) + { + var t = ID3TagsHelper.DetermineTotalTrackNumbers(file); + Assert.Equal(8, t.Value); + } + } + else + { + Assert.True(true); + } + } + + [Fact] + public void ReadTotalTrackNumbersFromCue_Should_Be_Six() + { + var cuesDir = @"C:\roadie_dev_root\test_cue_and_playlists\cues3"; + var directory = new DirectoryInfo(cuesDir); + if (directory.Exists) + { + foreach (var file in Directory.GetFiles(cuesDir)) + { + var t = ID3TagsHelper.DetermineTotalTrackNumbers(file); + Assert.Equal(6, t.Value); + } + } + else + { + Assert.True(true); + } + } + + [Fact] + public void ReadTotalTrackNumbersFromCue_Should_Be_Nine() + { + var cuesDir = @"C:\roadie_dev_root\test_cue_and_playlists\cues4"; + var directory = new DirectoryInfo(cuesDir); + if (directory.Exists) + { + foreach (var file in Directory.GetFiles(cuesDir)) + { + var t = ID3TagsHelper.DetermineTotalTrackNumbers(file); + Assert.Equal(9, t.Value); + } + } + else + { + Assert.True(true); + } + } + + [Fact] + public void ReadTotalTrackNumbersFromM3u_Should_Be_Eleven() + { + var cuesDir = @"C:\roadie_dev_root\test_cue_and_playlists\m3u1"; + var directory = new DirectoryInfo(cuesDir); + if (directory.Exists) + { + foreach (var file in Directory.GetFiles(cuesDir)) + { + var t = ID3TagsHelper.DetermineTotalTrackNumbers(file); + Assert.Equal(11, t.Value); + } + } + else + { + Assert.True(true); + } + } + + [Fact] + public void ReadTotalTrackNumbersFromM3u_Should_Be_Four() + { + var cuesDir = @"C:\roadie_dev_root\test_cue_and_playlists\m3u2"; + var directory = new DirectoryInfo(cuesDir); + if (directory.Exists) + { + foreach (var file in Directory.GetFiles(cuesDir)) + { + var t = ID3TagsHelper.DetermineTotalTrackNumbers(file); + Assert.Equal(4, t.Value); + } + } + else + { + Assert.True(true); + } + } + + [Fact] + public void ReadTotalTrackNumbersFromM3u_Should_Be_Eight() + { + var cuesDir = @"C:\roadie_dev_root\test_cue_and_playlists\m3u3"; + var directory = new DirectoryInfo(cuesDir); + if (directory.Exists) + { + foreach (var file in Directory.GetFiles(cuesDir)) + { + var t = ID3TagsHelper.DetermineTotalTrackNumbers(file); + Assert.Equal(8, t.Value); + } + } + else + { + Assert.True(true); + } + } + + [Fact] + public void ReadTotalTrackNumbersFromM3u_Should_Be_Fourteen() + { + var cuesDir = @"C:\roadie_dev_root\test_cue_and_playlists\m3u4"; + var directory = new DirectoryInfo(cuesDir); + if (directory.Exists) + { + foreach (var file in Directory.GetFiles(cuesDir)) + { + var t = ID3TagsHelper.DetermineTotalTrackNumbers(file); + Assert.Equal(14, t.Value); + } + } + else + { + Assert.True(true); + } + } + [Fact] public void ReadID3TagsMultipleMediasWithMax() @@ -481,6 +633,40 @@ namespace Roadie.Library.Tests } } + [Fact] + public void Write_Tags() + { + var file = new FileInfo(@"C:\roadie_dev_root\inbound\temp\01. Re1nstall 0verture.mp3"); + if (file.Exists) + { + short trackNumber = 15; + var numberOfTracks = 25; + + var tagLib = this.TagsHelper.MetaDataForFile(file.FullName); + Assert.True(tagLib.IsSuccess); + var metaData = tagLib.Data; + metaData.TrackNumber = trackNumber; + metaData.TotalTrackNumbers = numberOfTracks; + this.TagsHelper.WriteTags(metaData, file.FullName); + + var tagLibAfterWrite = this.TagsHelper.MetaDataForFile(file.FullName); + Assert.True(tagLib.IsSuccess); + Assert.Equal(metaData.Artist, tagLibAfterWrite.Data.Artist); + Assert.Equal(metaData.Release, tagLibAfterWrite.Data.Release); + Assert.Equal(metaData.Title, tagLibAfterWrite.Data.Title); + Assert.Equal(trackNumber, tagLibAfterWrite.Data.TrackNumber); + Assert.Equal(numberOfTracks, tagLibAfterWrite.Data.TotalTrackNumbers); + + + + } + else + { + Console.WriteLine($"skipping { file}"); + Assert.True(true); + } + } + [Fact] public void ReadID3TagsFromFile2() { @@ -613,5 +799,83 @@ namespace Roadie.Library.Tests } } + [Fact] + public void ReadID3TagsFromFile7() + { + var file = new FileInfo(@"C:\roadie_dev_root\1985 - Sacred Heart\Dio - Sacred Heart (1).mp3"); + if (file.Exists) + { + var tagLib = this.TagsHelper.MetaDataForFile(file.FullName); + Assert.True(tagLib.IsSuccess); + var metaData = tagLib.Data; + Assert.NotNull(metaData.Artist); + Assert.NotNull(metaData.Release); + Assert.NotNull(metaData.Title); + Assert.True(metaData.Year > 0); + Assert.NotNull(metaData.TrackNumber); + Assert.Equal(1, metaData.TrackNumber.Value); + Assert.True(metaData.TotalSeconds > 0); + Assert.True(metaData.ValidWeight > 30); + Assert.True(metaData.IsValid); + } + else + { + Console.WriteLine($"skipping { file}"); + Assert.True(true); + } + } + + [Fact] + public void ReadID3TagsFromFile8() + { + var file = new FileInfo(@"C:\roadie_dev_root\Grift\2017 Arvet\01 - Flyktfast.mp3"); + if (file.Exists) + { + var tagLib = this.TagsHelper.MetaDataForFile(file.FullName); + Assert.True(tagLib.IsSuccess); + var metaData = tagLib.Data; + Assert.NotNull(metaData.Artist); + Assert.NotNull(metaData.Release); + Assert.NotNull(metaData.Title); + Assert.True(metaData.Year > 0); + Assert.NotNull(metaData.TrackNumber); + Assert.Equal(1, metaData.TrackNumber.Value); + Assert.True(metaData.TotalSeconds > 0); + Assert.True(metaData.ValidWeight > 30); + Assert.True(metaData.IsValid); + } + else + { + Console.WriteLine($"skipping { file}"); + Assert.True(true); + } + } + + [Fact] + public void ReadID3TagsFromFile9() + { + var file = new FileInfo(@"C:\roadie_dev_root\Distorted Harmony - A Way Out - 2018\kWlZr0N_o72dwo0_CD001_0001.mp3"); + if (file.Exists) + { + var tagLib = this.TagsHelper.MetaDataForFile(file.FullName); + Assert.True(tagLib.IsSuccess); + var metaData = tagLib.Data; + Assert.NotNull(metaData.Artist); + Assert.NotNull(metaData.Release); + Assert.NotNull(metaData.Title); + Assert.True(metaData.Year > 0); + Assert.NotNull(metaData.TrackNumber); + Assert.Equal(1, metaData.TrackNumber.Value); + Assert.True(metaData.TotalSeconds > 0); + Assert.True(metaData.ValidWeight > 30); + Assert.True(metaData.IsValid); + } + else + { + Console.WriteLine($"skipping { file}"); + Assert.True(true); + } + } + } } diff --git a/Roadie.Api.Library.Tests/InspectorTests.cs b/Roadie.Api.Library.Tests/InspectorTests.cs new file mode 100644 index 0000000..31d6bea --- /dev/null +++ b/Roadie.Api.Library.Tests/InspectorTests.cs @@ -0,0 +1,53 @@ +using Roadie.Library.Inspect; +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Roadie.Library.Tests +{ + public class InspectorTests + { + + [Theory] + [InlineData("Bob Jones")] + [InlineData("Nancy Jones")] + public void Generate_Inspector_Artist_Token(string artist) + { + var token = Inspector.ArtistInspectorToken(new MetaData.Audio.AudioMetaData { Artist = artist }); + Assert.NotNull(token); + } + + [Fact] + public void Should_Generate_Same_Token_Value() + { + var md = new MetaData.Audio.AudioMetaData { Artist = "Omniversum Fractum", Release = "Paradigm Of The Elementals Essence" }; + var artistToken = Inspector.ArtistInspectorToken(md); + Assert.NotNull(artistToken); + + var releaseToken = Inspector.ReleaseInspectorToken(md); + Assert.NotNull(releaseToken); + Assert.NotEqual(artistToken, releaseToken); + + var secondReleaseToken = Inspector.ReleaseInspectorToken(md); + Assert.NotNull(releaseToken); + Assert.NotEqual(artistToken, releaseToken); + Assert.Equal(secondReleaseToken, releaseToken); + } + + [Fact] + public void Generate_Inspector_Tokens_Artist_And_Release_Unique() + { + var md = new MetaData.Audio.AudioMetaData { Artist = "Bob Jones", Release = "Bob's First Release" }; + var artistToken = Inspector.ArtistInspectorToken(md); + Assert.NotNull(artistToken); + + var releaseToken = Inspector.ReleaseInspectorToken(md); + Assert.NotNull(releaseToken); + + Assert.NotEqual(artistToken, releaseToken); + + + } + } +} diff --git a/Roadie.Api.Library.Tests/RenumberTests.cs b/Roadie.Api.Library.Tests/RenumberTests.cs new file mode 100644 index 0000000..a3c17cb --- /dev/null +++ b/Roadie.Api.Library.Tests/RenumberTests.cs @@ -0,0 +1,204 @@ +using Roadie.Library.MetaData.Audio; +using Roadie.Library.MetaData.ID3Tags; +using System.Collections.Generic; +using Xunit; + +namespace Roadie.Library.Tests +{ + public class RenumberTests + { + [Theory] + [InlineData(@"2003 - Accelerated Evolution\01 - Depth Charge.mp3")] + [InlineData(@"01 - Depth Charge.mp3")] + [InlineData(@"2003 - Accelerated Evolution\CD1\01 - Depth Charge.mp3")] + [InlineData(@"2003 - Accelerated Evolution\CD01\01 - Depth Charge.mp3")] + [InlineData(@"2003 - Accelerated Evolution\CD001\01 - Depth Charge.mp3")] + [InlineData(@"CD 1\01 - Depth Charge.mp3")] + [InlineData(@"2003 - Accelerated Evolution\CD 01\01 - Depth Charge.mp3")] + [InlineData(@"2003 - Accelerated Evolution\CD 001\01 - Depth Charge.mp3")] + [InlineData(@"Accelerated Evolution CD01\22 - Depth Charge.mp3")] + [InlineData(@"Accelerated Evolution CD1\22 - Depth Charge.mp3")] + public void Find_Disc_Number_Should_Be_One(string filename) + { + var n = ID3TagsHelper.DetermineDiscNumber(new AudioMetaData { Filename = filename }); + Assert.Equal(1, n); + } + + [Theory] + [InlineData(@"2003 - Accelerated Evolution\CD2\01 - Depth Charge.mp3")] + [InlineData(@"2003 - Accelerated Evolution\CD02\01 - Depth Charge.mp3")] + [InlineData(@"2003 - Accelerated Evolution\CD002\01 - Depth Charge.mp3")] + [InlineData(@"2003 - Accelerated Evolution\CD 2\02 - Depth Charge.mp3")] + [InlineData(@"2003 - Accelerated Evolution\CD 02\01 - Depth Charge.mp3")] + [InlineData(@"2003 - Accelerated Evolution\CD 002\22 - Depth Charge.mp3")] + [InlineData(@"Accelerated Evolution CD2\22 - Depth Charge.mp3")] + [InlineData(@"Accelerated Evolution CD02\22 - Depth Charge.mp3")] + public void Find_Disc_Number_Should_Be_Two(string filename) + { + var n = ID3TagsHelper.DetermineDiscNumber(new AudioMetaData { Filename = filename }); + Assert.Equal(2, n); + } + + [Fact] + public void Find_Total_Discs_Should_Be_One() + { + var three = new List + { + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\01 - Depth Charge.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\02 - Not A Depth Charge.mp3" + } + }; + var n = ID3TagsHelper.DetermineTotalDiscNumbers(three); + Assert.Equal(1, n); + + three = new List + { + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD1\01 - Depth Charge.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD1\02 - Not A Depth Charge.mp3" + } + }; + n = ID3TagsHelper.DetermineTotalDiscNumbers(three); + Assert.Equal(1, n); + } + + [Fact] + public void Find_Total_Discs_Should_Be_Ten() + { + var three = new List + { + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 01\01 - Depth Charge.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 02\02 - Not A Depth Charge.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 04\01 - First.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 06\02 - Second.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 10\01 - Depth Charge.mp3" + } + }; + var n = ID3TagsHelper.DetermineTotalDiscNumbers(three); + Assert.Equal(10, n); + } + + [Fact] + public void Find_Total_Discs_Should_Be_Three() + { + var three = new List + { + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 01\01 - Depth Charge.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 01\02 - Not A Depth Charge.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 02\01 - First.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 02\02 - Second.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 03\01 - Depth Charge.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 03\02 - Depth Charge.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 03\03 - Depth Charge.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 03\04 - Depth Charge.mp3" + } + }; + var n = ID3TagsHelper.DetermineTotalDiscNumbers(three); + Assert.Equal(3, n); + } + + [Fact] + public void Find_Total_Discs_Should_Be_Two() + { + var three = new List + { + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 01\01 - Depth Charge.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 01\02 - Not A Depth Charge.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 01\01 - First.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 02\02 - Second.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 01\01 - Depth Charge.mp3" + } + }; + var n = ID3TagsHelper.DetermineTotalDiscNumbers(three); + Assert.Equal(2, n); + + three = new List + { + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD0\01 - Depth Charge.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD2\02 - Not A Depth Charge.mp3" + } + }; + n = ID3TagsHelper.DetermineTotalDiscNumbers(three); + Assert.Equal(2, n); + + three = new List + { + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 1\01 - Depth Charge.mp3" + }, + new AudioMetaData + { + Filename = @"N:\Devin Townsend (1996 - 2016)\The Devin Townsend Band (2003 - 2006)\2003 - Accelerated Evolution\CD 2\02 - Not A Depth Charge.mp3" + } + }; + n = ID3TagsHelper.DetermineTotalDiscNumbers(three); + Assert.Equal(2, n); + } + } +} \ No newline at end of file diff --git a/Roadie.Api.Library.Tests/StringExtensionTests.cs b/Roadie.Api.Library.Tests/StringExtensionTests.cs index 9b0851b..dcc82ef 100644 --- a/Roadie.Api.Library.Tests/StringExtensionTests.cs +++ b/Roadie.Api.Library.Tests/StringExtensionTests.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Text; +using System.Text.RegularExpressions; using Xunit; namespace Roadie.Library.Tests @@ -143,12 +144,90 @@ namespace Roadie.Library.Tests t = "[1970] 022 # Plastic Ono Band"; Assert.Equal("[1970] 022 Plastic Ono Band", t.CleanString(this.Configuration)); - t = "11 Love_.Mp3".CleanString(this.Configuration); - Assert.Equal("11 Love.Mp3", t); + } - t = "Love_.Mp3".CleanString(this.Configuration); + [Fact] + public void CleanString_Track() + { + + var t = "11 Love.Mp3".CleanString(this.Configuration, this.Configuration.Processing.TrackRemoveStringsRegex); Assert.Equal("Love.Mp3", t); + t = "99 -Love.Mp3".CleanString(this.Configuration, this.Configuration.Processing.TrackRemoveStringsRegex); + Assert.Equal("Love.Mp3", t); + + t = "99_Love.Mp3".CleanString(this.Configuration, this.Configuration.Processing.TrackRemoveStringsRegex); + Assert.Equal("Love.Mp3", t); + + t = "99 _ Love.Mp3".CleanString(this.Configuration, this.Configuration.Processing.TrackRemoveStringsRegex); + Assert.Equal("Love.Mp3", t); + + t = "001 Love.Mp3".CleanString(this.Configuration, this.Configuration.Processing.TrackRemoveStringsRegex); + Assert.Equal("Love.Mp3", t); + + t = "01 - Love.Mp3".CleanString(this.Configuration, this.Configuration.Processing.TrackRemoveStringsRegex); + Assert.Equal("Love.Mp3", t); + + t = "01. Love.Mp3".CleanString(this.Configuration, this.Configuration.Processing.TrackRemoveStringsRegex); + Assert.Equal("Love.Mp3", t); + + t = "Love.Mp3".CleanString(this.Configuration, this.Configuration.Processing.TrackRemoveStringsRegex); + Assert.Equal("Love.Mp3", t); + + } + + [Theory] + [InlineData("Angie (Limited)")] + [InlineData("Angie CD1")] + [InlineData("Angie CD2")] + [InlineData("Angie CD23")] + [InlineData("Angie - CD1")] + [InlineData("Angie (Limited Edition)")] + [InlineData("Angie (Deluxe)")] + [InlineData("Angie (Deluxe")] + [InlineData("Angie (Remastered Deluxe Edition)")] + [InlineData("Angie (Remastered Deluxe)")] + [InlineData("Angie ( Deluxe )")] + [InlineData("Angie (Deluxe Edition)")] + [InlineData("Angie (Deluxe Expanded Edition)")] + [InlineData("Angie (2CD Deluxe Edition)")] + [InlineData("Angie (3CD Deluxe Edition)")] + [InlineData("Angie [2008 Remastered Edition]")] + [InlineData("Angie (Bonus CD)")] + [InlineData("Angie (DELUXE)")] + [InlineData("Angie (2013, Deluxe Expanded Edition, Disc 1)")] + [InlineData("Angie (Deluxe Edition, CD1)")] + [InlineData("Angie (20Th Anniversary Deluxe Edition Remastered)")] + [InlineData("Angie (Japanese Edition)")] + [InlineData("Angie (Asian Edition)")] + [InlineData("Angie (2008 Remastered Edition Digipack)")] + [InlineData("Angie (Re Release 2003)")] + [InlineData("Angie [2006, Self Released]")] + [InlineData("Angie (2002 Expanded Edition)")] + [InlineData("Angie (Japan Ltd Dig")] + public void CleanString_Release_Should_Be_Angie(string input) + { + var r = @"(\\s*(-\\s)*((CD[0-9][0-9]*)))|((\\(|\\[)+([0-9]|,|self|bonus|released|th|anniversary|re|release|cd|disc|deluxe|digipack|vinyl|japanese|asian|remastered|limited|expanded|edition|\\s)+(]|\\)))"; + var cleaned = input.CleanString(this.Configuration, r); + Assert.Equal("Angie", cleaned); + } + + + [Theory] + [InlineData("01 Batman Loves Robin")] + [InlineData("01 Batman Loves Robin")] + [InlineData("01 -Batman Loves Robin")] + [InlineData("01 - Batman Loves Robin")] + [InlineData("14 Batman Loves Robin")] + [InlineData("49 Batman Loves Robin")] + [InlineData("54 Batman Loves Robin")] + [InlineData("348 Batman Loves Robin")] + public void Test_Regex_String(string input) + { + var t1 = Regex.Replace(input, "^([0-9]+)(\\.|-|\\s)*", ""); + Assert.NotNull(t1); + Assert.Equal("Batman Loves Robin", t1); + } [Fact] diff --git a/Roadie.Api.Library.Tests/appsettings.test.json b/Roadie.Api.Library.Tests/appsettings.test.json index be913a8..652ab24 100644 --- a/Roadie.Api.Library.Tests/appsettings.test.json +++ b/Roadie.Api.Library.Tests/appsettings.test.json @@ -58,7 +58,7 @@ "MaximumArtistImagesToAdd": 12, "MaximumReleaseImagesToAdd": 12, "MaxImageWidth": 800, - "RemoveStringsRegex": "\\b[0-9]+\\s#\\s\\b", + "TrackRemoveStringsRegex": "^([0-9]+)(\\.|-|\\s)*", "ReplaceStrings": [ { "order": 1, diff --git a/Roadie.Api.Library/Configuration/Processing.cs b/Roadie.Api.Library/Configuration/Processing.cs index ab815f2..f06c281 100644 --- a/Roadie.Api.Library/Configuration/Processing.cs +++ b/Roadie.Api.Library/Configuration/Processing.cs @@ -21,12 +21,18 @@ namespace Roadie.Library.Configuration public int MaximumArtistImagesToAdd { get; set; } public int MaximumReleaseImagesToAdd { get; set; } public string RemoveStringsRegex { get; set; } + public string ArtistRemoveStringsRegex { get; set; } + public string ReleaseRemoveStringsRegex { get; set; } + public string TrackRemoveStringsRegex { get; set; } + public List ReplaceStrings { get; set; } public string UnknownFolder { get; set; } public Processing() { this.ReplaceStrings = new List(); + this.DoAudioCleanup = true; + this.DoClearComments = true; } } } \ No newline at end of file diff --git a/Roadie.Api.Library/Extensions/StringExt.cs b/Roadie.Api.Library/Extensions/StringExt.cs index 8fb95e8..c58e002 100644 --- a/Roadie.Api.Library/Extensions/StringExt.cs +++ b/Roadie.Api.Library/Extensions/StringExt.cs @@ -43,9 +43,9 @@ namespace Roadie.Library.Extensions return input; } - public static string CleanString(this string input, IRoadieSettings settings) + public static string CleanString(this string input, IRoadieSettings settings, string removeStringsRegex = null) { - if (string.IsNullOrEmpty(input) || settings == null) + if (string.IsNullOrEmpty(input) || settings == null) { return input; } @@ -55,14 +55,10 @@ namespace Roadie.Library.Extensions result = result.Replace(kvp.Key, kvp.ReplaceWith, StringComparison.OrdinalIgnoreCase); } result = result.Trim().ToTitleCase(false); - var removeStringsRegex = settings.Processing.RemoveStringsRegex; - if (!string.IsNullOrEmpty(removeStringsRegex)) + var rs = removeStringsRegex ?? settings.Processing.RemoveStringsRegex; + if (!string.IsNullOrEmpty(rs)) { - var regexParts = removeStringsRegex.Split('|'); - foreach (var regexPart in regexParts) - { - result = Regex.Replace(result, regexPart, ""); - } + result = Regex.Replace(result, rs, "", RegexOptions.IgnoreCase); } if (result.Length > 5) { diff --git a/Roadie.Api.Library/Inspect/Inspector.cs b/Roadie.Api.Library/Inspect/Inspector.cs new file mode 100644 index 0000000..db476d5 --- /dev/null +++ b/Roadie.Api.Library/Inspect/Inspector.cs @@ -0,0 +1,238 @@ +using HashidsNet; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Roadie.Library.Caching; +using Roadie.Library.Configuration; +using Roadie.Library.Extensions; +using Roadie.Library.Inspect.Plugins; +using Roadie.Library.MetaData.Audio; +using Roadie.Library.MetaData.ID3Tags; +using Roadie.Library.Processors; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; + +namespace Roadie.Library.Inspect +{ + public class Inspector + { + private static readonly string Salt = "6856F2EE-5965-4345-884B-2CCA457AAF59"; + + private IEnumerable _plugins = null; + + public IEnumerable Plugins + { + get + { + if (this._plugins == null) + { + var plugins = new List(); + try + { + var type = typeof(IInspectorPlugin); + var types = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(s => s.GetTypes()) + .Where(p => type.IsAssignableFrom(p)); + foreach (Type t in types) + { + if (t.GetInterface("IInspectorPlugin") != null && !t.IsAbstract && !t.IsInterface) + { + IInspectorPlugin plugin = Activator.CreateInstance(t, new object[] { this.Configuration, this.CacheManager, this.Logger }) as IInspectorPlugin; + plugins.Add(plugin); + } + } + } + catch (Exception ex) + { + this.Logger.LogError(ex); + } + this._plugins = plugins.ToArray(); + } + return this._plugins; + } + } + + private IEventMessageLogger MessageLogger { get; } + private ILogger Logger + { + get + { + return this.MessageLogger as ILogger; + } + } + + private ID3TagsHelper TagsHelper { get; } + + private IRoadieSettings Configuration { get; } + + public DictionaryCacheManager CacheManager { get; } + + + public Inspector() + { + Console.WriteLine("Roadie Media Inspector"); + + + this.MessageLogger = new EventMessageLogger(); + this.MessageLogger.Messages += MessageLogger_Messages; + + var settings = new RoadieSettings(); + IConfigurationBuilder configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddJsonFile("appsettings.json"); + IConfiguration configuration = configurationBuilder.Build(); + configuration.GetSection("RoadieSettings").Bind(settings); + settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection"); + this.Configuration = settings; + this.CacheManager = new DictionaryCacheManager(this.Logger, new CachePolicy(TimeSpan.FromHours(4))); + this.TagsHelper = new ID3TagsHelper(this.Configuration, this.CacheManager, this.Logger); + + } + + private void MessageLogger_Messages(object sender, EventMessage e) + { + Console.WriteLine($"Log Level [{ e.Level }] Log Message [{ e.Message }] "); + } + + public static string ArtistInspectorToken(AudioMetaData metaData) + { + var hashids = new Hashids(Inspector.Salt); + var artistId = 0; + var bytes = System.Text.Encoding.ASCII.GetBytes(metaData.Artist); + var looper = bytes.Length / 4; + for(var i = 0; i < looper; i++) + { + artistId += BitConverter.ToInt32(bytes, i * 4); + } + if (artistId < 0) + { + artistId = artistId * -1; + } + var token = hashids.Encode(artistId); + return token; + } + + public static string ReleaseInspectorToken(AudioMetaData metaData) + { + var hashids = new Hashids(Inspector.Salt); + var releaseId = 0; + var bytes = System.Text.Encoding.ASCII.GetBytes(metaData.Artist + metaData.Release); + var looper = bytes.Length / 4; + for (var i = 0; i < looper; i++) + { + releaseId += BitConverter.ToInt32(bytes, i * 4); + } + if (releaseId < 0) + { + releaseId = releaseId * -1; + } + var token = hashids.Encode(releaseId); + return token; + } + + public void Inspect(bool doCopy, string folder, string destination) + { + // Get all the directorys in the directory + var folderDirectories = Directory.GetDirectories(folder, "*.*", SearchOption.AllDirectories); + var directories = new List + { + folder + }; + directories.AddRange(folderDirectories); + foreach (var directory in directories) + { + Console.WriteLine($"╔ ░▒▓ Inspecting [{ directory }] ▓▒░"); + Console.WriteLine("╠═╗"); + // Get all the MP3 files in the folder + var files = Directory.GetFiles(directory, "*.mp3", SearchOption.TopDirectoryOnly); + if (files == null || !files.Any()) + { + continue; + } + Console.WriteLine($"Found [{ files.Length }] mp3 Files"); + Console.WriteLine("╠═╣"); + // Get audiometadata and output details including weight/validity + foreach (var file in files) + { + var tagLib = this.TagsHelper.MetaDataForFile(file); + Console.WriteLine(tagLib.Data); + } + Console.WriteLine("╠═╣"); + List fileMetaDatas = new List(); + List fileInfos = new List(); + foreach (var file in files) + { + var fileInfo = new FileInfo(file); + var tagLib = this.TagsHelper.MetaDataForFile(fileInfo.FullName); + var artistToken = ArtistInspectorToken(tagLib.Data); + var releaseToken = ReleaseInspectorToken(tagLib.Data); + var newFileName = $"{artistToken}_{releaseToken}_CD{ (tagLib.Data.Disk ?? ID3TagsHelper.DetermineDiscNumber(tagLib.Data)).ToString("000") }_{ tagLib.Data.TrackNumber.Value.ToString("0000") }.mp3"; + var subFolder = folder == destination ? fileInfo.DirectoryName : destination; + var newPath = Path.Combine(destination, subFolder, newFileName.ToFileNameFriendly()); + if (!doCopy) + { + if (fileInfo.FullName != newPath) + { + if (File.Exists(newPath)) + { + File.Delete(newPath); + } + fileInfo.MoveTo(newPath); + } + } + else + { + fileInfo.CopyTo(newPath, true); + } + tagLib.Data.Filename = fileInfo.FullName; + fileMetaDatas.Add(tagLib.Data); + fileInfos.Add(fileInfo); + } + // Perform InspectorPlugins + IEnumerable pluginMetaData = fileMetaDatas.OrderBy(x => x.Filename); + foreach (var plugin in this.Plugins.OrderBy(x => x.Order)) + { + Console.WriteLine($"╟ Running Plugin { plugin.Description }"); + OperationResult> pluginResult = null; + try + { + pluginResult = plugin.Process(pluginMetaData); + } + catch (Exception ex) + { + Console.WriteLine($"Plugin Error: [{ ex.ToString() }]"); + } + if (!pluginResult.IsSuccess) + { + Console.WriteLine($"Plugin Failed. Error [{ JsonConvert.SerializeObject(pluginResult)}]"); + } + pluginMetaData = pluginResult.Data; + } + // Save all plugin modifications to the MetaData + foreach (var metadata in pluginMetaData) + { + this.TagsHelper.WriteTags(metadata, metadata.Filename); + } + Console.WriteLine("╠═╣"); + // Get audiometadata and output details including weight/validity + foreach (var fileInfo in fileInfos) + { + var tagLib = this.TagsHelper.MetaDataForFile(fileInfo.FullName); + if (!tagLib.IsSuccess || !tagLib.Data.IsValid) + { + Console.WriteLine($"■■ INVALID: {tagLib.Data }"); + } + else + { + Console.WriteLine(tagLib.Data); + } + } + Console.WriteLine("╚═╝"); + } + } + + + } +} diff --git a/Roadie.Api.Library/Inspect/Plugins/CleanMetaData.cs b/Roadie.Api.Library/Inspect/Plugins/CleanMetaData.cs new file mode 100644 index 0000000..1a90f44 --- /dev/null +++ b/Roadie.Api.Library/Inspect/Plugins/CleanMetaData.cs @@ -0,0 +1,42 @@ +using Microsoft.Extensions.Logging; +using Roadie.Library.Caching; +using Roadie.Library.Configuration; +using Roadie.Library.Extensions; +using Roadie.Library.MetaData.Audio; +using Roadie.Library.MetaData.ID3Tags; +using System.Collections.Generic; +using System.Linq; + + +namespace Roadie.Library.Inspect.Plugins +{ + public class CleanMetaData : PluginBase + { + public override int Order => 2; + + public override string Description => "Clean: Clean the primary elements of MetaData"; + + public CleanMetaData(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger) + : base(configuration, cacheManager, logger) + { + } + + public override OperationResult> Process(IEnumerable metaDatas) + { + var result = new OperationResult>(); + if (this.Configuration.Processing.DoAudioCleanup) + { + foreach (var metaData in metaDatas) + { + metaData.Artist = metaData.Artist.CleanString(this.Configuration, this.Configuration.Processing.ArtistRemoveStringsRegex).ToTitleCase(doPutTheAtEnd: false); + metaData.Release = metaData.Release.CleanString(this.Configuration, this.Configuration.Processing.ReleaseRemoveStringsRegex).ToTitleCase(doPutTheAtEnd: false); + metaData.TrackArtist = metaData.TrackArtist.CleanString(this.Configuration, this.Configuration.Processing.ReleaseRemoveStringsRegex).ToTitleCase(doPutTheAtEnd: false); + metaData.Title = metaData.Title.CleanString(this.Configuration, this.Configuration.Processing.TrackRemoveStringsRegex).ToTitleCase(doPutTheAtEnd: false); + } + } + result.Data = metaDatas; + result.IsSuccess = true; + return result; + } + } +} diff --git a/Roadie.Api.Library/Inspect/Plugins/IInspectorPlugin.cs b/Roadie.Api.Library/Inspect/Plugins/IInspectorPlugin.cs new file mode 100644 index 0000000..0adbad3 --- /dev/null +++ b/Roadie.Api.Library/Inspect/Plugins/IInspectorPlugin.cs @@ -0,0 +1,14 @@ +using Roadie.Library.MetaData.Audio; +using System.Collections.Generic; +using System.IO; + +namespace Roadie.Library.Inspect.Plugins +{ + public interface IInspectorPlugin + { + string Description { get; } + int Order { get; } + + OperationResult> Process(IEnumerable metaDatas); + } +} \ No newline at end of file diff --git a/Roadie.Api.Library/Inspect/Plugins/PluginBase.cs b/Roadie.Api.Library/Inspect/Plugins/PluginBase.cs new file mode 100644 index 0000000..32c28b0 --- /dev/null +++ b/Roadie.Api.Library/Inspect/Plugins/PluginBase.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Logging; +using Roadie.Library.Caching; +using Roadie.Library.Configuration; +using Roadie.Library.MetaData.Audio; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Roadie.Library.Inspect.Plugins +{ + public abstract class PluginBase : IInspectorPlugin + { + protected IRoadieSettings Configuration { get; } + protected ICacheManager CacheManager { get; } + protected ILogger Logger { get; } + + public abstract int Order { get; } + public abstract string Description { get; } + + public PluginBase(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger) + { + this.Configuration = configuration; + this.CacheManager = cacheManager; + this.Logger = logger; + } + + public abstract OperationResult> Process(IEnumerable metaDatas); + + } +} diff --git a/Roadie.Api.Library/Inspect/Plugins/Renumber.cs b/Roadie.Api.Library/Inspect/Plugins/Renumber.cs new file mode 100644 index 0000000..155a8c1 --- /dev/null +++ b/Roadie.Api.Library/Inspect/Plugins/Renumber.cs @@ -0,0 +1,55 @@ +using Microsoft.Extensions.Logging; +using Roadie.Library.Caching; +using Roadie.Library.Configuration; +using Roadie.Library.MetaData.Audio; +using Roadie.Library.MetaData.ID3Tags; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Roadie.Library.Inspect.Plugins +{ + public class Renumber : PluginBase + { + public override string Description + { + get + { + return "Renumber: Renumber all given tracks sequentially and set maximum number of tracks"; + } + } + + public override int Order { get; } = 1; + + public Renumber(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger) + : base(configuration, cacheManager, logger) + { + } + + public override OperationResult> Process(IEnumerable metaDatas) + { + var result = new OperationResult>(); + var totalNumberOfMedia = ID3TagsHelper.DetermineTotalDiscNumbers(metaDatas); + var folders = metaDatas.GroupBy(x => x.FileInfo.DirectoryName); + foreach(var folder in folders) + { + short looper = 0; + foreach(var metaData in folder) + { + looper++; + metaData.TrackNumber = looper; + metaData.TotalTrackNumbers = ID3TagsHelper.DetermineTotalTrackNumbers(metaData.Filename) ?? folder.Count(); + metaData.Disk = ID3TagsHelper.DetermineDiscNumber(metaData); + metaData.TotalDiscCount = totalNumberOfMedia; + } + } + result.Data = metaDatas; + result.IsSuccess = true; + return result; + } + + + } +} \ No newline at end of file diff --git a/Roadie.Api.Library/Roadie.Library.csproj b/Roadie.Api.Library/Roadie.Library.csproj index 459ea1e..55005a2 100644 --- a/Roadie.Api.Library/Roadie.Library.csproj +++ b/Roadie.Api.Library/Roadie.Library.csproj @@ -11,6 +11,7 @@ + @@ -19,6 +20,7 @@ + @@ -27,8 +29,11 @@ + + + diff --git a/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudioMetaData.cs b/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudioMetaData.cs index 090329c..aea3ab3 100644 --- a/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudioMetaData.cs +++ b/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudioMetaData.cs @@ -106,6 +106,10 @@ namespace Roadie.Library.MetaData.Audio { result |= AudioMetaDataWeights.TrackNumber; } + if ((this.TotalTrackNumbers ?? 0) > 1) + { + result |= AudioMetaDataWeights.TrackTotalNumber; + } if (this.TotalSeconds > 1) { result |= AudioMetaDataWeights.Time; @@ -136,11 +140,25 @@ namespace Roadie.Library.MetaData.Audio /// public int? Disk { get; set; } + /// + /// TSST + /// + public string DiskSubTitle { get; set; } + /// /// Full filename to the file used to get this AudioMetaData /// public string Filename { get; set; } + private FileInfo _fileInfo = null; + public FileInfo FileInfo + { + get + { + return this._fileInfo ?? (this._fileInfo = new FileInfo(this.Filename)); + } + } + public ICollection Genres { get; set; } public IEnumerable Images { get; set; } @@ -228,6 +246,11 @@ namespace Roadie.Library.MetaData.Audio } } + /// + /// Total number of Discs for Media + /// + public int? TotalDiscCount { get; set; } + public double TotalSeconds { get @@ -288,7 +311,7 @@ namespace Roadie.Library.MetaData.Audio } if (!this._trackArtist.Contains(AudioMetaData.ArtistSplitCharacter.ToString())) { - if(string.IsNullOrEmpty(this.TrackArtist)) + if (string.IsNullOrEmpty(this.TrackArtist)) { return new string[0]; } @@ -375,16 +398,7 @@ namespace Roadie.Library.MetaData.Audio public override string ToString() { - return string.Format("IsValid: {0}{7}, ValidWeight {1}, Artist: {2}, Release: {3}, TrackNumber: {4}, Title: {5}, Year: {6}, Duration: {8}", - this.IsValid, - this.ValidWeight, - this.Artist, - this.Release, - this.TrackNumber, - this.Title, - this.Year, - this.IsSoundTrack ? " [SoundTrack ]" : string.Empty, - this.Time == null ? "-" : this.Time.Value.ToString()); + return string.Format($"IsValid: {this.IsValid}{ (this.IsSoundTrack ? " [SoundTrack ]" : string.Empty)}, ValidWeight {this.ValidWeight}, Artist: {this.Artist}, Release: {this.Release}, TrackNumber: {this.TrackNumber}, TrackTotal: {this.TotalTrackNumbers}, Title: {this.Title}, Year: {this.Year}, Duration: {(this.Time == null ? "-" : this.Time.Value.ToString())}"); } } } \ No newline at end of file diff --git a/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudioMetaDataImageType.cs b/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudioMetaDataImageType.cs index d5b8bc3..5c0f55d 100644 --- a/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudioMetaDataImageType.cs +++ b/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudioMetaDataImageType.cs @@ -24,4 +24,7 @@ BandLogo = 19, PublisherLogo = 20, } + + + } \ No newline at end of file diff --git a/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudoMetaDataWeights.cs b/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudoMetaDataWeights.cs index df77d22..b882ae6 100644 --- a/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudoMetaDataWeights.cs +++ b/Roadie.Api.Library/SearchEngines/MetaData/Audio/AudoMetaDataWeights.cs @@ -9,9 +9,10 @@ namespace Roadie.Library.MetaData.Audio Year = 1, Time = 2, TrackNumber = 4, - Release = 8, - Title = 16, - Artist = 32 + TrackTotalNumber = 8, + Release = 16, + Title = 32, + Artist = 64 } //Artist + Release + TrackTitle 56 diff --git a/Roadie.Api.Library/SearchEngines/MetaData/ID3Tags/ID3TagsHelper.cs b/Roadie.Api.Library/SearchEngines/MetaData/ID3Tags/ID3TagsHelper.cs index 456b420..79a41f0 100644 --- a/Roadie.Api.Library/SearchEngines/MetaData/ID3Tags/ID3TagsHelper.cs +++ b/Roadie.Api.Library/SearchEngines/MetaData/ID3Tags/ID3TagsHelper.cs @@ -15,6 +15,12 @@ using IdSharp.Tagging.ID3v1; using IdSharp.Tagging.ID3v2; using Newtonsoft.Json; +using ATL.AudioData; +using ATL; +using System.Text.RegularExpressions; +using ATL.CatalogDataReaders; +using ATL.PlaylistReaders; + namespace Roadie.Library.MetaData.ID3Tags { public class ID3TagsHelper : MetaDataProviderBase, IID3TagsHelper @@ -31,6 +37,11 @@ namespace Roadie.Library.MetaData.ID3Tags { return result; } + result = this.MetaDataForFileFromATL(fileName); + if (result.IsSuccess) + { + return result; + } return new OperationResult(); } @@ -61,22 +72,57 @@ namespace Roadie.Library.MetaData.ID3Tags { try { - // TODO + if(!metaData.IsValid) + { + this.Logger.LogWarning($"Invalid MetaData `{ metaData }` to save to file [{ filename }]"); + return false; + } + ID3v1Tag.RemoveTag(filename); + + var trackNumber = metaData.TrackNumber ?? 1; + var totalTrackNumber = metaData.TotalTrackNumbers ?? trackNumber; + + var disc = metaData.Disk ?? 1; + var discCount = metaData.TotalDiscCount ?? disc; + + IID3v2Tag id3v2 = new ID3v2Tag(filename) + { + Artist = metaData.Artist, + Album = metaData.Release, + Title = metaData.Title, + Year = metaData.Year.Value.ToString(), + TrackNumber = totalTrackNumber < 99 ? $"{trackNumber.ToString("00")}/{totalTrackNumber.ToString("00")}" : $"{trackNumber.ToString()}/{totalTrackNumber.ToString()}", + DiscNumber = discCount < 99 ? $"{disc.ToString("00")}/{discCount.ToString("00")}" : $"{disc.ToString()}/{discCount.ToString()}" + }; + if (metaData.TrackArtists.Any()) + { + id3v2.OriginalArtist = string.Join("/", metaData.TrackArtists); + } + if (this.Configuration.Processing.DoClearComments) + { + if (id3v2.CommentsList.Any()) + { + for (var i = 0; i < id3v2.CommentsList.Count; i++) + { + id3v2.CommentsList[i].Description = null; + id3v2.CommentsList[i].Value = null; + } + } + } + id3v2.Save(filename); + + //// Delete first embedded picture (let's say it exists) + //theTrack.EmbeddedPictures.RemoveAt(0); + + //// Add 'CD' embedded picture + //PictureInfo newPicture = new PictureInfo(Commons.ImageFormat.Gif, PictureInfo.PIC_TYPE.CD); + //newPicture.PictureData = System.IO.File.ReadAllBytes("E:/temp/_Images/pic1.gif"); + //theTrack.EmbeddedPictures.Add(newPicture); + + //// Save modifications on the disc + //theTrack.Save(); + - //var tagFile = TagLib.File.Create(filename); - //tagFile.Tag.AlbumArtists = null; - //tagFile.Tag.AlbumArtists = new[] { metaData.Artist }; - //tagFile.Tag.Performers = null; - //if (metaData.TrackArtists.Any()) - //{ - // tagFile.Tag.Performers = metaData.TrackArtists.ToArray(); - //} - //tagFile.Tag.Album = metaData.Release; - //tagFile.Tag.Title = metaData.Title; - //tagFile.Tag.Year = force ? (uint)(metaData.Year ?? 0) : tagFile.Tag.Year > 0 ? tagFile.Tag.Year : (uint)(metaData.Year ?? 0); - //tagFile.Tag.Track = force ? (uint)(metaData.TrackNumber ?? 0) : tagFile.Tag.Track > 0 ? tagFile.Tag.Track : (uint)(metaData.TrackNumber ?? 0); - //tagFile.Tag.TrackCount = force ? (uint)(metaData.TotalTrackNumbers ?? 0) : tagFile.Tag.TrackCount > 0 ? tagFile.Tag.TrackCount : (uint)(metaData.TotalTrackNumbers ?? 0); - //tagFile.Tag.Disc = force ? (uint)(metaData.Disk ?? 0) : tagFile.Tag.Disc > 0 ? tagFile.Tag.Disc : (uint)(metaData.Disk ?? 0); //tagFile.Tag.Pictures = metaData.Images == null ? null : metaData.Images.Select(x => new TagLib.Picture //{ // Data = new TagLib.ByteVector(x.Data), @@ -94,6 +140,54 @@ namespace Roadie.Library.MetaData.ID3Tags return false; } + private OperationResult MetaDataForFileFromATL(string fileName) + { + var sw = new Stopwatch(); + sw.Start(); + AudioMetaData result = new AudioMetaData(); + var isSuccess = false; + try + { + result.Filename = fileName; + var theTrack = new ATL.Track(fileName); + result.Release = theTrack.Album; + result.Artist = theTrack.AlbumArtist ?? theTrack.Artist; + result.ArtistRaw = theTrack.AlbumArtist ?? theTrack.Artist; + result.Genres = theTrack.Genre?.Split(new char[] { ',', '\\' }); + result.TrackArtist = theTrack.OriginalArtist ?? theTrack.Artist ?? theTrack.AlbumArtist; + result.TrackArtistRaw = theTrack.OriginalArtist; + result.AudioBitrate = (int?)theTrack.Bitrate; + result.AudioSampleRate = (int)theTrack.Bitrate; + result.Disk = theTrack.DiscNumber; + result.DiskSubTitle = theTrack.AdditionalFields["TSST"]; + result.Images = theTrack.EmbeddedPictures?.Select(x => new AudioMetaDataImage + { + Data = x.PictureData, + Description = x.Description, + MimeType = "image/jpg", + Type = x.PicType == PictureInfo.PIC_TYPE.Front || x.PicType == PictureInfo.PIC_TYPE.Generic ? AudioMetaDataImageType.FrontCover : AudioMetaDataImageType.Other + }).ToArray(); + result.Time = theTrack.DurationMs > 0 ? ((decimal?)theTrack.DurationMs).ToTimeSpan() : null; + result.Title = theTrack.Title.ToTitleCase(false); + result.TrackNumber = (short)theTrack.TrackNumber; + result.Year = theTrack.Year; + isSuccess = result.IsValid; + } + catch (Exception ex) + { + this.Logger.LogError(ex, "MetaDataForFileFromTagLib Filename [" + fileName + "] Error [" + ex.Serialize() + "]"); + } + sw.Stop(); + return new OperationResult + { + IsSuccess = isSuccess, + OperationTime = sw.ElapsedMilliseconds, + Data = result + }; + } + + + private OperationResult MetaDataForFileFromIdSharp(string fileName) { @@ -103,6 +197,7 @@ namespace Roadie.Library.MetaData.ID3Tags var isSuccess = false; try { + result.Filename = fileName; IAudioFile audioFile = AudioFile.Create(fileName, true); if (ID3v2Tag.DoesTagExist(fileName)) { @@ -117,6 +212,7 @@ namespace Roadie.Library.MetaData.ID3Tags result.AudioChannels = audioFile.Channels; result.AudioSampleRate = (int)audioFile.Bitrate; result.Disk = ID3TagsHelper.ParseDiscNumber(id3v2.DiscNumber); + result.DiskSubTitle = id3v2.SetSubtitle; result.Images = id3v2.PictureList?.Select(x => new AudioMetaDataImage { Data = x.PictureData, @@ -130,7 +226,7 @@ namespace Roadie.Library.MetaData.ID3Tags result.TotalTrackNumbers = ID3TagsHelper.ParseTotalTrackNumber(id3v2.TrackNumber); var year = id3v2.Year ?? id3v2.RecordingTimestamp ?? id3v2.ReleaseTimestamp ?? id3v2.OriginalReleaseTimestamp; result.Year = ID3TagsHelper.ParseYear(year); - isSuccess = true; + isSuccess = result.IsValid; } if (!isSuccess) @@ -149,7 +245,7 @@ namespace Roadie.Library.MetaData.ID3Tags result.TrackNumber = SafeParser.ToNumber(id3v1.TrackNumber); var date = SafeParser.ToDateTime(id3v1.Year); result.Year = date?.Year ?? SafeParser.ToNumber(id3v1.Year); - isSuccess = true; + isSuccess = result.IsValid; } } @@ -167,6 +263,79 @@ namespace Roadie.Library.MetaData.ID3Tags }; } + public static short? DetermineTotalTrackNumbers(string filename, string trackNumber = null) + { + short? result = null; + if(!string.IsNullOrEmpty(filename)) + { + var fileInfo = new FileInfo(filename); + var directoryName = fileInfo.DirectoryName; + + // See if CUE sheet exists if so read tracks from that and return latest track number + var cueFiles = Directory.GetFiles(directoryName, ("*.cue")); + if(cueFiles != null && cueFiles.Any()) + { + try + { + ICatalogDataReader theReader = CatalogDataReaderFactory.GetInstance().GetCatalogDataReader(cueFiles.First()); + result = (short)theReader.Tracks.Max(x => x.TrackNumber); + } + catch (Exception ex) + { + Trace.Write("Error Reading Cue: " + ex.ToString()); + } + } + if(!result.HasValue) + { + // See if M3U sheet exists if so read tracks from that and return latest track number + var m3uFiles = Directory.GetFiles(directoryName, ("*.m3u")); + if (m3uFiles != null && m3uFiles.Any()) + { + try + { + IPlaylistReader theReader = PlaylistReaderFactory.GetInstance().GetPlaylistReader(m3uFiles.First()); + result = (short)theReader.GetFiles().Count(); + + } + catch (Exception ex) + { + Trace.Write("Error Reading m3u: " + ex.ToString()); + } + } + } + } + // Try to parse from TrackNumber + if (!result.HasValue) + { + result = ID3TagsHelper.ParseTotalTrackNumber(trackNumber); + } + return result; + } + + public static int DetermineTotalDiscNumbers(IEnumerable metaDatas) + { + var result = 1; + foreach (var metaData in metaDatas.OrderBy(x => x.Filename)) + { + var n = DetermineDiscNumber(metaData); + result = result > n ? result : n; + } + return result; + } + + public static int DetermineDiscNumber(AudioMetaData metaData) + { + var maxDiscNumber = 500; // Damnit Karajan + for (var i = maxDiscNumber; i > 0; i--) + { + if (Regex.IsMatch(metaData.Filename, @"(cd\s*(0*" + i + "))", RegexOptions.IgnoreCase)) + { + return i; + } + } + return 1; + } + public static short? ParseYear(string input) { if(string.IsNullOrEmpty(input)) diff --git a/Roadie.sln b/Roadie.sln index a9b7be3..5b12482 100644 --- a/Roadie.sln +++ b/Roadie.sln @@ -18,6 +18,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Api.Services", "Road EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Api.Hubs", "Roadie.Api.Hubs\Roadie.Api.Hubs.csproj", "{E740C89E-3363-4577-873B-0871823E252C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inspector", "Inspector\Inspector.csproj", "{9A0831DC-343A-4E0C-8617-AF62426F3BA8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -66,6 +68,14 @@ Global {E740C89E-3363-4577-873B-0871823E252C}.Release|Any CPU.Build.0 = Release|Any CPU {E740C89E-3363-4577-873B-0871823E252C}.Release|x64.ActiveCfg = Release|x64 {E740C89E-3363-4577-873B-0871823E252C}.Release|x64.Build.0 = Release|x64 + {9A0831DC-343A-4E0C-8617-AF62426F3BA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A0831DC-343A-4E0C-8617-AF62426F3BA8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A0831DC-343A-4E0C-8617-AF62426F3BA8}.Debug|x64.ActiveCfg = Debug|Any CPU + {9A0831DC-343A-4E0C-8617-AF62426F3BA8}.Debug|x64.Build.0 = Debug|Any CPU + {9A0831DC-343A-4E0C-8617-AF62426F3BA8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A0831DC-343A-4E0C-8617-AF62426F3BA8}.Release|Any CPU.Build.0 = Release|Any CPU + {9A0831DC-343A-4E0C-8617-AF62426F3BA8}.Release|x64.ActiveCfg = Release|Any CPU + {9A0831DC-343A-4E0C-8617-AF62426F3BA8}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/libraries/IdSharp.AudioInfo-core.dll b/libraries/IdSharp.AudioInfo-core.dll index 3c98b40..213db7f 100644 Binary files a/libraries/IdSharp.AudioInfo-core.dll and b/libraries/IdSharp.AudioInfo-core.dll differ diff --git a/libraries/IdSharp.Common-core.dll b/libraries/IdSharp.Common-core.dll index ff46f36..6f158b9 100644 Binary files a/libraries/IdSharp.Common-core.dll and b/libraries/IdSharp.Common-core.dll differ diff --git a/libraries/IdSharp.Tagging-core.dll b/libraries/IdSharp.Tagging-core.dll index 01d3d53..0c40417 100644 Binary files a/libraries/IdSharp.Tagging-core.dll and b/libraries/IdSharp.Tagging-core.dll differ