diff --git a/go_backend/qobuz.go b/go_backend/qobuz.go index 8a87cd28..2a4d35c8 100644 --- a/go_backend/qobuz.go +++ b/go_backend/qobuz.go @@ -479,8 +479,8 @@ func parseQobuzURL(input string) (string, string, error) { } func qobuzArtistsMatch(expectedArtist, foundArtist string) bool { - normExpected := strings.ToLower(strings.TrimSpace(expectedArtist)) - normFound := strings.ToLower(strings.TrimSpace(foundArtist)) + normExpected := normalizeLooseArtistName(expectedArtist) + normFound := normalizeLooseArtistName(foundArtist) if normExpected == normFound { return true diff --git a/go_backend/tidal.go b/go_backend/tidal.go index bcbca0f9..ea286f22 100644 --- a/go_backend/tidal.go +++ b/go_backend/tidal.go @@ -1629,8 +1629,8 @@ type TidalDownloadResult struct { } func artistsMatch(spotifyArtist, tidalArtist string) bool { - normSpotify := strings.ToLower(strings.TrimSpace(spotifyArtist)) - normTidal := strings.ToLower(strings.TrimSpace(tidalArtist)) + normSpotify := normalizeLooseArtistName(spotifyArtist) + normTidal := normalizeLooseArtistName(tidalArtist) if normSpotify == normTidal { return true diff --git a/go_backend/title_match_utils.go b/go_backend/title_match_utils.go index 22b0eebd..cecf462d 100644 --- a/go_backend/title_match_utils.go +++ b/go_backend/title_match_utils.go @@ -3,6 +3,8 @@ package gobackend import ( "strings" "unicode" + + "golang.org/x/text/unicode/norm" ) // normalizeLooseTitle collapses separators/punctuation so titles like @@ -33,6 +35,37 @@ func normalizeLooseTitle(title string) string { return strings.Join(strings.Fields(b.String()), " ") } +// normalizeLooseArtistName folds diacritics and common separators so artist +// verification is resilient to variants like "Ă–zkent" vs "Ozkent". +func normalizeLooseArtistName(name string) string { + trimmed := strings.TrimSpace(strings.ToLower(name)) + if trimmed == "" { + return "" + } + + decomposed := norm.NFD.String(trimmed) + + var b strings.Builder + b.Grow(len(decomposed)) + + for _, r := range decomposed { + switch { + case unicode.Is(unicode.Mn, r), unicode.Is(unicode.Mc, r), unicode.Is(unicode.Me, r): + continue + case unicode.IsLetter(r), unicode.IsNumber(r): + b.WriteRune(r) + case unicode.IsSpace(r): + b.WriteByte(' ') + case r == '/', r == '\\', r == '_', r == '-', r == '|', r == '.', r == '&', r == '+': + b.WriteByte(' ') + default: + // Drop remaining punctuation/symbols for loose artist matching. + } + } + + return strings.Join(strings.Fields(b.String()), " ") +} + func hasAlphaNumericRunes(value string) bool { for _, r := range value { if unicode.IsLetter(r) || unicode.IsNumber(r) {