Browse Source

Initial commit

master
Laura Hausmann 1 year ago
commit
f056499a1e
Signed by: zotan GPG Key ID: 5EC1D38FFC321311
  1. 5
      .gitignore
  2. 13
      .idea/.idea.AutoTag/.idea/.gitignore
  3. 4
      .idea/.idea.AutoTag/.idea/encodings.xml
  4. 8
      .idea/.idea.AutoTag/.idea/indexLayout.xml
  5. 6
      .idea/.idea.AutoTag/.idea/misc.xml
  6. 6
      .idea/.idea.AutoTag/.idea/vcs.xml
  7. 149
      AutoTag.cli/AlphaNumComparer.cs
  8. 12
      AutoTag.cli/AutoTag.cli.csproj
  9. 116
      AutoTag.cli/AutoTag.cs
  10. 16
      AutoTag.sln

5
.gitignore

@ -0,0 +1,5 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/

13
.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/

4
.idea/.idea.AutoTag/.idea/encodings.xml

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

8
.idea/.idea.AutoTag/.idea/indexLayout.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ContentModelUserStore">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

6
.idea/.idea.AutoTag/.idea/misc.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

6
.idea/.idea.AutoTag/.idea/vcs.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

149
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 <jonathan.ruckwood@gmail.com>
*
* Adapted by Dominik Hurnaus <dominik.hurnaus@gmail.com> 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<string>
{
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;
}
}
}

12
AutoTag.cli/AutoTag.cli.csproj

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TagLibSharp" Version="2.2.0" />
</ItemGroup>
</Project>

116
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 <albums/playlists> <inputfolder> <outputfolder>");
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<string> AllowedFileTypes = new List<string> {".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);
}
}
}

16
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
Loading…
Cancel
Save