fix: handle Japanese artist name order in Tidal/Qobuz matching

This commit is contained in:
zarzet
2026-01-13 20:31:05 +07:00
parent 1ec190bfe7
commit 6a886c5276
3 changed files with 91 additions and 0 deletions
+5
View File
@@ -27,6 +27,11 @@
- Now uses `firstArtist` + `otherArtists` instead of deprecated `artists.items`
- Logs correctly show "Fetched track: {title} by {artist}"
- **Japanese Artist Name Order**: Fixed artist mismatch for Japanese names with different order
- "Sawano Hiroyuki" vs "Hiroyuki Sawano" now correctly matches
- Added `sameWordsUnordered` check to both Tidal and Qobuz artist matching
- Handles Japanese name order (family name first) vs Western name order (given name first)
---
## [3.0.0-beta.1] - 2026-01-13
+43
View File
@@ -84,6 +84,12 @@ func qobuzArtistsMatch(expectedArtist, foundArtist string) bool {
return true
}
// Check if same words in different order (e.g., "Sawano Hiroyuki" vs "Hiroyuki Sawano")
if qobuzSameWordsUnordered(expectedFirst, foundFirst) {
GoLog("[Qobuz] Artist names have same words in different order, assuming match: '%s' vs '%s'\n", expectedArtist, foundArtist)
return true
}
// If scripts are TRULY different (Latin vs CJK/Arabic/Cyrillic), assume match (transliteration)
// Don't treat Latin Extended (Polish, French, etc.) as different script
expectedLatin := qobuzIsLatinScript(expectedArtist)
@@ -96,6 +102,43 @@ func qobuzArtistsMatch(expectedArtist, foundArtist string) bool {
return false
}
// qobuzSameWordsUnordered checks if two strings have the same words regardless of order
// Useful for Japanese names: "Sawano Hiroyuki" vs "Hiroyuki Sawano"
func qobuzSameWordsUnordered(a, b string) bool {
wordsA := strings.Fields(a)
wordsB := strings.Fields(b)
// Must have same number of words
if len(wordsA) != len(wordsB) || len(wordsA) == 0 {
return false
}
// Sort and compare
sortedA := make([]string, len(wordsA))
sortedB := make([]string, len(wordsB))
copy(sortedA, wordsA)
copy(sortedB, wordsB)
// Simple bubble sort (usually just 2-3 words)
for i := 0; i < len(sortedA)-1; i++ {
for j := i + 1; j < len(sortedA); j++ {
if sortedA[i] > sortedA[j] {
sortedA[i], sortedA[j] = sortedA[j], sortedA[i]
}
if sortedB[i] > sortedB[j] {
sortedB[i], sortedB[j] = sortedB[j], sortedB[i]
}
}
}
for i := range sortedA {
if sortedA[i] != sortedB[i] {
return false
}
}
return true
}
// qobuzTitlesMatch checks if track titles are similar enough
func qobuzTitlesMatch(expectedTitle, foundTitle string) bool {
normExpected := strings.ToLower(strings.TrimSpace(expectedTitle))
+43
View File
@@ -1273,6 +1273,12 @@ func artistsMatch(spotifyArtist, tidalArtist string) bool {
return true
}
// Check if same words in different order (e.g., "Sawano Hiroyuki" vs "Hiroyuki Sawano")
if sameWordsUnordered(spotifyFirst, tidalFirst) {
GoLog("[Tidal] Artist names have same words in different order, assuming match: '%s' vs '%s'\n", spotifyArtist, tidalArtist)
return true
}
// If scripts are TRULY different (Latin vs CJK/Arabic/Cyrillic), assume match (transliteration)
// Don't treat Latin Extended (Polish, French, etc.) as different script
// This handles cases like "鈴木雅之" vs "Masayuki Suzuki"
@@ -1286,6 +1292,43 @@ func artistsMatch(spotifyArtist, tidalArtist string) bool {
return false
}
// sameWordsUnordered checks if two strings have the same words regardless of order
// Useful for Japanese names: "Sawano Hiroyuki" vs "Hiroyuki Sawano"
func sameWordsUnordered(a, b string) bool {
wordsA := strings.Fields(a)
wordsB := strings.Fields(b)
// Must have same number of words
if len(wordsA) != len(wordsB) || len(wordsA) == 0 {
return false
}
// Sort and compare
sortedA := make([]string, len(wordsA))
sortedB := make([]string, len(wordsB))
copy(sortedA, wordsA)
copy(sortedB, wordsB)
// Simple bubble sort (usually just 2-3 words)
for i := 0; i < len(sortedA)-1; i++ {
for j := i + 1; j < len(sortedA); j++ {
if sortedA[i] > sortedA[j] {
sortedA[i], sortedA[j] = sortedA[j], sortedA[i]
}
if sortedB[i] > sortedB[j] {
sortedB[i], sortedB[j] = sortedB[j], sortedB[i]
}
}
}
for i := range sortedA {
if sortedA[i] != sortedB[i] {
return false
}
}
return true
}
// titlesMatch checks if track titles are similar enough
func titlesMatch(expectedTitle, foundTitle string) bool {
normExpected := strings.ToLower(strings.TrimSpace(expectedTitle))