fix(scanner): Apple Music playlists import for songs with accented characters (#4385)

* fix: resolve playlist import issues with Unicode character paths

Fixes #3332 where songs with accented characters failed to import from Apple Music M3U playlists. The issue occurred because Apple Music exports use NFC Unicode normalization while macOS filesystem stores paths in NFD normalization.

Added normalizePathForComparison() function that normalizes both filesystem and M3U playlist paths to NFC form before comparison. This ensures consistent path matching regardless of the Unicode normalization form used.

Changes include comprehensive test coverage for Unicode normalization scenarios with both NFC and NFD character representations.

* address comments

Signed-off-by: Deluan <deluan@navidrome.org>

* fix(tests): add check for unequal original Unicode paths in playlist normalization tests

Signed-off-by: Deluan <deluan@navidrome.org>

---------

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan Quintão
2025-07-26 11:27:35 -04:00
committed by GitHub
parent 1eef2e554c
commit d28a282de4
2 changed files with 59 additions and 2 deletions
+10 -2
View File
@@ -21,6 +21,7 @@ import (
"github.com/navidrome/navidrome/model/criteria"
"github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/utils/slice"
"golang.org/x/text/unicode/norm"
)
type Playlists interface {
@@ -203,10 +204,10 @@ func (s *playlists) parseM3U(ctx context.Context, pls *model.Playlist, folder *m
}
existing := make(map[string]int, len(found))
for idx := range found {
existing[strings.ToLower(found[idx].Path)] = idx
existing[normalizePathForComparison(found[idx].Path)] = idx
}
for _, path := range paths {
idx, ok := existing[strings.ToLower(path)]
idx, ok := existing[normalizePathForComparison(path)]
if ok {
mfs = append(mfs, found[idx])
} else {
@@ -223,6 +224,13 @@ func (s *playlists) parseM3U(ctx context.Context, pls *model.Playlist, folder *m
return nil
}
// normalizePathForComparison normalizes a file path to NFC form and converts to lowercase
// for consistent comparison. This fixes Unicode normalization issues on macOS where
// Apple Music creates playlists with NFC-encoded paths but the filesystem uses NFD.
func normalizePathForComparison(path string) string {
return strings.ToLower(norm.NFC.String(path))
}
// TODO This won't work for multiple libraries
func (s *playlists) normalizePaths(ctx context.Context, pls *model.Playlist, folder *model.Folder, lines []string) ([]string, error) {
libRegex, err := s.compileLibraryPaths(ctx)