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