feat: add normalizeLooseArtistName with diacritic folding for resilient artist matching

Use Unicode NFD decomposition to strip combining marks so variants like
"Özkent" and "Ozkent" are treated as equivalent. Apply the new helper
in both tidal.go and qobuz.go artistsMatch functions.
This commit is contained in:
zarzet
2026-03-22 22:42:33 +07:00
parent 23cab16471
commit 2df8fd6282
3 changed files with 37 additions and 4 deletions
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+33
View File
@@ -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) {