diff --git a/src/.idea/.idea.AudioTagger/.idea/.gitignore b/src/.idea/.idea.AudioTagger/.idea/.gitignore new file mode 100644 index 0000000..1bcedf4 --- /dev/null +++ b/src/.idea/.idea.AudioTagger/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/.idea.AudioTagger.iml +/modules.xml +/projectSettingsUpdater.xml +/contentModel.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/src/.idea/.idea.AudioTagger/.idea/.name b/src/.idea/.idea.AudioTagger/.idea/.name new file mode 100644 index 0000000..9722465 --- /dev/null +++ b/src/.idea/.idea.AudioTagger/.idea/.name @@ -0,0 +1 @@ +AudioTagger \ No newline at end of file diff --git a/src/.idea/.idea.AudioTagger/.idea/indexLayout.xml b/src/.idea/.idea.AudioTagger/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/src/.idea/.idea.AudioTagger/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.AudioTagger/.idea/vcs.xml b/src/.idea/.idea.AudioTagger/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/src/.idea/.idea.AudioTagger/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/AudioTagger.Console/Operations/MediaFileRenamer.cs b/src/AudioTagger.Console/Operations/MediaFileRenamer.cs index 67ae3bf..8c7a634 100644 --- a/src/AudioTagger.Console/Operations/MediaFileRenamer.cs +++ b/src/AudioTagger.Console/Operations/MediaFileRenamer.cs @@ -60,12 +60,22 @@ public void Start( return; } + var normalizationForm = settings.Renaming.NormalizationForm.Trim() switch + { + "D" => NormalizationForm.FormD, + "KD" => NormalizationForm.FormKD, + "KC" => NormalizationForm.FormKC, + _ => NormalizationForm.FormC + }; + printer.Print($"Filename normalization form is {normalizationForm}."); + RenameFiles( eligibleMediaFiles, workingDirectory, printer, settings.Renaming.Patterns, - settings.Renaming.UseAlbumDirectories); + settings.Renaming.UseAlbumDirectories, + normalizationForm); var deletedDirs = DeleteEmptySubDirectories(workingDirectory.FullName, printer); PrintDeletedDirectories(deletedDirs, printer); @@ -96,7 +106,8 @@ private static void RenameFiles( DirectoryInfo workingDirectory, IPrinter printer, IEnumerable renamePatterns, - bool useAlbumDirectories) + bool useAlbumDirectories, + NormalizationForm normalizationForm) { var isCancelRequested = false; var doConfirm = true; @@ -106,7 +117,7 @@ private static void RenameFiles( for (int i = 0; i < mediaFiles.Count; i++) { - MediaFile file = mediaFiles.ElementAt(i); + var file = mediaFiles.ElementAt(i); if (file.Title.Length == 0) { @@ -131,6 +142,7 @@ private static void RenameFiles( workingDirectory.FullName, useArtistDirectory, useAlbumDirectories, + normalizationForm, ref doConfirm, renamePatterns); } @@ -181,6 +193,7 @@ private static bool RenameSingleFile( string workingPath, bool useArtistDirectory, bool useAlbumDirectory, + NormalizationForm normalizationForm, ref bool doConfirm, IEnumerable renamePatterns) { @@ -207,16 +220,17 @@ private static bool RenameSingleFile( return false; } - MediaFilePathInfo oldPathInfo = new(workingPath, file.FileInfo.FullName); + var oldPathInfo = new MediaFilePathInfo(workingPath, file.FileInfo.FullName); string newArtistDir = useArtistDirectory - ? GenerateSafeDirectoryName(file) + ? GenerateSafeDirectoryName(file).Normalize(normalizationForm) : string.Empty; string newAlbumDir = useAlbumDirectory && useArtistDirectory && file.Album.HasText() - ? IoUtilities.SanitizePath(file.Album) + ? IoUtilities.SanitizePath(file.Album).Normalize(normalizationForm) : string.Empty; - string newFileName = GenerateFileNameUsingPattern(file, populatedTagNames, matchedRenamePattern); - MediaFilePathInfo newPathInfo = new(workingPath, [newArtistDir, newAlbumDir], newFileName); + string newFileName = GenerateFileName(file, populatedTagNames, matchedRenamePattern) + .Normalize(normalizationForm); + var newPathInfo = new MediaFilePathInfo(workingPath, [newArtistDir, newAlbumDir], newFileName); if (oldPathInfo.FullFilePath(true) == newPathInfo.FullFilePath(true)) { @@ -271,7 +285,7 @@ private static bool RenameSingleFile( /// Generates and returns a new filename by replacing placeholders within the rename /// pattern (e.g., `%ALBUM%`) with actual tag data from the `MediaFile`. /// - static string GenerateFileNameUsingPattern( + static string GenerateFileName( MediaFile file, ICollection fileTagNames, string renamePattern) diff --git a/src/AudioTagger.Library/UserSettings/Settings.cs b/src/AudioTagger.Library/UserSettings/Settings.cs index 9945ccc..b926933 100644 --- a/src/AudioTagger.Library/UserSettings/Settings.cs +++ b/src/AudioTagger.Library/UserSettings/Settings.cs @@ -99,6 +99,17 @@ public sealed record Renaming [JsonPropertyName("useAlbumDirectories")] public bool UseAlbumDirectories { get; init; } = false; + /// + /// The Unicode normalization form to use for renamed filenames. + /// Valid values are `C`, `D`, `KC`, and `KD`. + /// `C` is used by default is no valid value is provided. + /// + /// Reference: https://unicode.org/reports/tr15/ + /// Reference: https://en.wikipedia.org/wiki/Unicode_equivalence + /// + [JsonPropertyName("normalizationForm")] + public string NormalizationForm { get; init; } = "C"; + [JsonPropertyName("ignoredDirectories")] public ImmutableList? IgnoredDirectories { get; init; } }