diff --git a/services/api/internal/library/meta/meta_test.go b/services/api/internal/library/meta/meta_test.go index 0ad64ea..fe01dd7 100644 --- a/services/api/internal/library/meta/meta_test.go +++ b/services/api/internal/library/meta/meta_test.go @@ -33,6 +33,8 @@ func TestSplitArtists(t *testing.T) { assert.Equal(t, []string{"main:Tom", "main:Jerry"}, names(SplitArtists("Tom & Jerry", known))) assert.Equal(t, []string{"main:A", "main:B"}, names(SplitArtists("A; B", nil))) assert.Equal(t, []string{"main:A", "main:B", "main:C"}, names(SplitArtists("A, B, C", nil))) + assert.Equal(t, []string{"main:A", "main:B"}, names(SplitArtists("A, B", func(string) bool { return true })), "already glued name is still split") + assert.Equal(t, []string{"main:Tyler, the Creator"}, names(SplitArtists("Tyler, the Creator", nil))) assert.Equal(t, []string{"main:A", "feat:B", "feat:C"}, names(SplitArtists("A feat. B & C", nil))) assert.Equal(t, []string{"main:A"}, names(SplitArtists("A / a", nil)), "duplicates by key are dropped") assert.Empty(t, SplitArtists(" ", nil)) diff --git a/services/api/internal/library/meta/normalize.go b/services/api/internal/library/meta/normalize.go index 8a8b6d7..38246f5 100644 --- a/services/api/internal/library/meta/normalize.go +++ b/services/api/internal/library/meta/normalize.go @@ -57,7 +57,7 @@ type ArtistCredit struct { Role string // main | feat } -// SplitArtists разбирает поле исполнителя на отдельных. « & » и «, » делят имя, только если составного +// SplitArtists разбирает поле исполнителя на отдельных. « & » делит имя, только если составного // исполнителя нет в библиотеке (known), — иначе «Simon & Garfunkel» распался бы на двоих. // Всё после feat./ft. — приглашённые исполнители. func SplitArtists(s string, known func(key string) bool) []ArtistCredit { @@ -85,13 +85,15 @@ func SplitArtists(s string, known func(key string) bool) []ArtistCredit { } } for _, part := range splitAny(main, artistSeparators) { - if (strings.Contains(part, " & ") || strings.Contains(part, ", ")) && (known == nil || !known(Key(part))) { - for _, p := range splitAny(part, []string{" & ", ", "}) { - add(p, "main") + for _, seg := range splitComma(part) { + if strings.Contains(seg, " & ") && (known == nil || !known(Key(seg))) { + for _, p := range strings.Split(seg, " & ") { + add(p, "main") + } + continue } - continue + add(seg, "main") } - add(part, "main") } for _, part := range splitAny(feat, append(artistSeparators, " & ", ", ")) { add(part, "feat") @@ -99,6 +101,20 @@ func SplitArtists(s string, known func(key string) bool) []ArtistCredit { return out } +// splitComma делит по «, » без оглядки на библиотеку: иначе уже склеенное имя («A, B») навсегда +// остаётся «известным». Продолжение «the …» — часть имени («Tyler, the Creator»). +func splitComma(s string) []string { + var out []string + for _, p := range strings.Split(s, ", ") { + if n := len(out); n > 0 && strings.HasPrefix(strings.ToLower(p), "the ") { + out[n-1] += ", " + p + continue + } + out = append(out, p) + } + return out +} + func splitAny(s string, seps []string) []string { parts := []string{s} for _, sep := range seps {