From f056499a1e3188da89dafcb662e55f989098f421 Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Mon, 17 Aug 2020 01:06:06 +0200 Subject: [PATCH] Initial commit --- .gitignore | 5 + .idea/.idea.AutoTag/.idea/.gitignore | 13 ++ .idea/.idea.AutoTag/.idea/encodings.xml | 4 + .idea/.idea.AutoTag/.idea/indexLayout.xml | 8 ++ .idea/.idea.AutoTag/.idea/misc.xml | 6 + .idea/.idea.AutoTag/.idea/vcs.xml | 6 + AutoTag.cli/AlphaNumComparer.cs | 149 ++++++++++++++++++++++ AutoTag.cli/AutoTag.cli.csproj | 12 ++ AutoTag.cli/AutoTag.cs | 116 +++++++++++++++++ AutoTag.sln | 16 +++ 10 files changed, 335 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.idea.AutoTag/.idea/.gitignore create mode 100644 .idea/.idea.AutoTag/.idea/encodings.xml create mode 100644 .idea/.idea.AutoTag/.idea/indexLayout.xml create mode 100644 .idea/.idea.AutoTag/.idea/misc.xml create mode 100644 .idea/.idea.AutoTag/.idea/vcs.xml create mode 100644 AutoTag.cli/AlphaNumComparer.cs create mode 100644 AutoTag.cli/AutoTag.cli.csproj create mode 100644 AutoTag.cli/AutoTag.cs create mode 100644 AutoTag.sln diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..add57be --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ \ No newline at end of file diff --git a/.idea/.idea.AutoTag/.idea/.gitignore b/.idea/.idea.AutoTag/.idea/.gitignore new file mode 100644 index 0000000..06bb4f1 --- /dev/null +++ b/.idea/.idea.AutoTag/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/.idea.AutoTag.iml +/contentModel.xml +/projectSettingsUpdater.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/.idea.AutoTag/.idea/encodings.xml b/.idea/.idea.AutoTag/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.AutoTag/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.AutoTag/.idea/indexLayout.xml b/.idea/.idea.AutoTag/.idea/indexLayout.xml new file mode 100644 index 0000000..27ba142 --- /dev/null +++ b/.idea/.idea.AutoTag/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.AutoTag/.idea/misc.xml b/.idea/.idea.AutoTag/.idea/misc.xml new file mode 100644 index 0000000..28a804d --- /dev/null +++ b/.idea/.idea.AutoTag/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.AutoTag/.idea/vcs.xml b/.idea/.idea.AutoTag/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.AutoTag/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/AutoTag.cli/AlphaNumComparer.cs b/AutoTag.cli/AlphaNumComparer.cs new file mode 100644 index 0000000..acfdebb --- /dev/null +++ b/AutoTag.cli/AlphaNumComparer.cs @@ -0,0 +1,149 @@ +/* + * The Alphanum Algorithm is an improved sorting algorithm for strings + * containing numbers. Instead of sorting numbers in ASCII order like + * a standard sort, this algorithm sorts numbers in numeric order. + * + * The Alphanum Algorithm is discussed at http://www.DaveKoelle.com + * + * Based on the Java implementation of Dave Koelle's Alphanum algorithm. + * Contributed by Jonathan Ruckwood + * + * Adapted by Dominik Hurnaus to + * - correctly sort words where one word starts with another word + * - have slightly better performance + * + * Released under the MIT License - https://opensource.org/licenses/MIT + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +using System; +using System.Collections.Generic; +using System.Text; + +/* + * Please compare against the latest Java version at http://www.DaveKoelle.com + * to see the most recent modifications + */ +namespace AutoTag.cli +{ + public class AlphanumComparator : IComparer + { + private enum ChunkType {Alphanumeric, Numeric}; + private bool InChunk(char ch, char otherCh) + { + var type = ChunkType.Alphanumeric; + + if (char.IsDigit(otherCh)) + { + type = ChunkType.Numeric; + } + + if (type == ChunkType.Alphanumeric && char.IsDigit(ch) + || type == ChunkType.Numeric && !char.IsDigit(ch)) + { + return false; + } + + return true; + } + + public int Compare(string x, string y) + { + var s1 = x; + var s2 = y; + if (s1 == null || s2 == null) + { + return 0; + } + + var thisMarker = 0; + var thatMarker = 0; + + while (thisMarker < s1.Length || thatMarker < s2.Length) + { + if (thisMarker >= s1.Length) + { + return -1; + } + else if (thatMarker >= s2.Length) + { + return 1; + } + var thisCh = s1[thisMarker]; + var thatCh = s2[thatMarker]; + + var thisChunk = new StringBuilder(); + var thatChunk = new StringBuilder(); + + while (thisMarker < s1.Length && (thisChunk.Length==0 ||InChunk(thisCh, thisChunk[0]))) + { + thisChunk.Append(thisCh); + thisMarker++; + + if (thisMarker < s1.Length) + { + thisCh = s1[thisMarker]; + } + } + + while (thatMarker < s2.Length && (thatChunk.Length==0 ||InChunk(thatCh, thatChunk[0]))) + { + thatChunk.Append(thatCh); + thatMarker++; + + if (thatMarker < s2.Length) + { + thatCh = s2[thatMarker]; + } + } + + var result = 0; + // If both chunks contain numeric characters, sort them numerically + if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0])) + { + var thisNumericChunk = Convert.ToInt32(thisChunk.ToString()); + var thatNumericChunk = Convert.ToInt32(thatChunk.ToString()); + + if (thisNumericChunk < thatNumericChunk) + { + result = -1; + } + + if (thisNumericChunk > thatNumericChunk) + { + result = 1; + } + } + else + { + result = string.Compare(thisChunk.ToString(), thatChunk.ToString(), StringComparison.Ordinal); + } + + if (result != 0) + { + return result; + } + } + + return 0; + } + } +} \ No newline at end of file diff --git a/AutoTag.cli/AutoTag.cli.csproj b/AutoTag.cli/AutoTag.cli.csproj new file mode 100644 index 0000000..295c322 --- /dev/null +++ b/AutoTag.cli/AutoTag.cli.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp3.1 + + + + + + + diff --git a/AutoTag.cli/AutoTag.cs b/AutoTag.cli/AutoTag.cs new file mode 100644 index 0000000..b3b86ea --- /dev/null +++ b/AutoTag.cli/AutoTag.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using static AutoTag.cli.Helpers; +using TagFile = TagLib.File; + +namespace AutoTag.cli { + internal static class AutoTag { + + private static void Main(string[] args) { + if (args.Length != 3 || (args[0] != "albums" && args[0] != "playlists")) { + Console.WriteLine("Usage: autotag "); + Environment.Exit(1); + } + + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + var type = args[0] == "albums" ? FolderType.Albums : FolderType.Playlists; + + var inputDir = new Folder(args[1], type); + var outputDir = args[2]; + + switch (inputDir.Type) { + case FolderType.Albums: { + foreach (var track in Directory.EnumerateFiles(inputDir.Path, "*.*", SearchOption.AllDirectories).Where(IsAllowed)) { + Console.WriteLine("<- " + track); + var tagFile = TagFile.Create(track); + if (tagFile.Tag.AlbumArtists.Length == 0) + tagFile.Tag.AlbumArtists = new[] {tagFile.Tag.Performers[0]}; + var outputFileDirectory = Path.Combine(outputDir, CleanFileName(tagFile.Tag.AlbumArtists[0]), CleanFileName(tagFile.Tag.Album)); + Directory.CreateDirectory(outputFileDirectory); + var outputFilePath = Path.Combine(outputDir, CleanFileName(tagFile.Tag.AlbumArtists[0]), CleanFileName(tagFile.Tag.Album), + CleanFileName($"{tagFile.Tag.Track}. {tagFile.Tag.Performers[0]} - {tagFile.Tag.Title}{Path.GetExtension(track)}")); + if (tagFile.Tag.Disc != 0 && tagFile.Tag.Disc != 1) { + outputFilePath = Path.Combine(outputDir, CleanFileName(tagFile.Tag.AlbumArtists[0]), CleanFileName(tagFile.Tag.Album), + CleanFileName($"Disc{tagFile.Tag.Disc} - {tagFile.Tag.Track}. {tagFile.Tag.Performers[0]} - {tagFile.Tag.Title}{Path.GetExtension(track)}")); + } + + File.Copy(track, outputFilePath, true); + var newTagFile = TagFile.Create(outputFilePath); + newTagFile.Tag.Comment = null; + newTagFile.Tag.Genres = null; + newTagFile.Save(); + Console.WriteLine("-> " + outputFilePath); + Console.WriteLine(); + } + + break; + } + case FolderType.Playlists: { + foreach (var playlist in Directory.GetDirectories(inputDir.Path)) { + var tracks = Directory.EnumerateFiles(playlist, "*.*", SearchOption.AllDirectories).Where(IsAllowed).OrderBy(s => s, new AlphanumComparator()); + + uint i = 1; + var trackCount = tracks.Count(); + var playlistName = Path.GetFileName(playlist); + + foreach (var track in tracks) { + Console.WriteLine("<- " + track); + var tagFile = TagFile.Create(track); + var outputFileDirectory = Path.Combine(outputDir, "Various Artists", CleanFileName(playlistName)); + Directory.CreateDirectory(outputFileDirectory); + + var outputFilePath = Path.Combine(outputDir, "Various Artists", CleanFileName(playlistName), + CleanFileName((tagFile.Tag.Performers.Length == 0 ? $"{i}. {tagFile.Tag.Title}{Path.GetExtension(track)}" : $"{i}. {tagFile.Tag.Performers[0]} - {tagFile.Tag.Title}{Path.GetExtension(track)}"))); + File.Copy(track, outputFilePath, true); + var newTagFile = TagFile.Create(outputFilePath); + newTagFile.Tag.Comment = null; + newTagFile.Tag.Genres = null; + newTagFile.Tag.Album = playlistName; + newTagFile.Tag.Track = i++; + newTagFile.Tag.TrackCount = (uint) trackCount; + newTagFile.Tag.AlbumArtists = new[] {"Various Artists"}; + + newTagFile.Save(); + Console.WriteLine("-> " + outputFilePath); + Console.WriteLine(); + } + } + + break; + } + default: throw new ArgumentOutOfRangeException(); + } + } + } + + internal class Folder { + public readonly string Path; + public readonly FolderType Type; + + public Folder(string path, FolderType type) { + Path = path; + Type = type; + } + } + + internal enum FolderType { + Albums, + Playlists + } + + internal static class Helpers { + private static readonly List AllowedFileTypes = new List {".flac", ".opus", ".mp3", ".m4a"}; + + internal static bool IsAllowed(string filename) => AllowedFileTypes.Any(filename.EndsWith); + + internal static string CleanFileName(string fileName) { + return Path.GetInvalidFileNameChars().Aggregate(fileName, (current, c) => current.Replace(c.ToString(), string.Empty)); + //var tempBytes = Encoding.GetEncoding("ISO-8859-8").GetBytes(str); + //return Encoding.UTF8.GetString(tempBytes); + } + } +} \ No newline at end of file diff --git a/AutoTag.sln b/AutoTag.sln new file mode 100644 index 0000000..fc52da3 --- /dev/null +++ b/AutoTag.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTag.cli", "AutoTag.cli\AutoTag.cli.csproj", "{5F302C27-DE60-4496-96AE-97FB6C223D4E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5F302C27-DE60-4496-96AE-97FB6C223D4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F302C27-DE60-4496-96AE-97FB6C223D4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F302C27-DE60-4496-96AE-97FB6C223D4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F302C27-DE60-4496-96AE-97FB6C223D4E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal