From 847cac73445cffa8de9ec87647683047a9016fbb Mon Sep 17 00:00:00 2001 From: Deluan Date: Thu, 2 Jul 2026 00:20:01 -0400 Subject: [PATCH] feat(scanner): split performer names and honor split exceptions Performer pair values were never split; multiple names in one PERFORMER value stayed a single artist. Split them with the roles separators, using the same whitelist protection as other participant tags. --- model/metadata/map_participants.go | 22 ++++++++++++------- model/metadata/map_participants_test.go | 29 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/model/metadata/map_participants.go b/model/metadata/map_participants.go index 62ed0a4a9..d8ebfc55d 100644 --- a/model/metadata/map_participants.go +++ b/model/metadata/map_participants.go @@ -94,18 +94,24 @@ func (md Metadata) processPerformers(participants model.Participants, rolesMbzId roleIdx[role] = 0 } + conf := model.TagRolesConf() + conf.ExceptionsRx = model.ArtistSplitExceptionsRx() titleCaser := cases.Title(language.Und) for _, performer := range md.Pairs(model.TagPerformer) { - name := performer.Value() subRole := titleCaser.String(performer.Key()) - - artist := model.Artist{ - ID: md.artistID(name), - Name: name, - OrderArtistName: str.SanitizeFieldForSortingNoArticle(name), - MbzArtistID: md.getPerformerMbid(subRole, rolesMbzIdMap, roleIdx), + names := []string{performer.Value()} + if len(conf.Split) > 0 { + names = filterDuplicatedOrEmptyValues(conf.SplitTagValue(names)) + } + for _, name := range names { + artist := model.Artist{ + ID: md.artistID(name), + Name: name, + OrderArtistName: str.SanitizeFieldForSortingNoArticle(name), + MbzArtistID: md.getPerformerMbid(subRole, rolesMbzIdMap, roleIdx), + } + participants.AddWithSubRole(model.RolePerformer, subRole, artist) } - participants.AddWithSubRole(model.RolePerformer, subRole, artist) } } diff --git a/model/metadata/map_participants_test.go b/model/metadata/map_participants_test.go index c66752253..d81b45842 100644 --- a/model/metadata/map_participants_test.go +++ b/model/metadata/map_participants_test.go @@ -565,6 +565,23 @@ var _ = Describe("Participants", func() { matchPerformer("Tim Carmon", "tim carmon", "Hammond Organ"), )) }) + + It("should split multiple names in a single value", func() { + mf = toMediaFile(model.RawTags{ + "PERFORMER:GUITAR": {"Eric Clapton/B.B. King"}, + "PERFORMER:BASS": {"Nathan East"}, + }) + + participants := mf.Participants + Expect(participants).To(HaveKeyWithValue(model.RolePerformer, HaveLen(3))) + + p := participants[model.RolePerformer] + Expect(p).To(ContainElements( + matchPerformer("Eric Clapton", "eric clapton", "Guitar"), + matchPerformer("B.B. King", "b.b. king", "Guitar"), + matchPerformer("Nathan East", "nathan east", "Bass"), + )) + }) }) When("MUSICBRAINZ_PERFORMERID tag is set", func() { @@ -847,5 +864,17 @@ var _ = Describe("Participants", func() { Expect(artists[0].Name).To(Equal("Artist Name")) Expect(artists[1].Name).To(Equal("Someone Else")) }) + + It("does not split a whitelisted name in performer tags", func() { + conf.Server.Scanner.ArtistSplitExceptions = []string{"AC/DC"} + mf = toMediaFile(model.RawTags{ + "PERFORMER:GUITAR": {"AC/DC/Brian Johnson"}, + }) + + performers := mf.Participants[model.RolePerformer] + Expect(performers).To(HaveLen(2)) + Expect(performers[0].Name).To(Equal("AC/DC")) + Expect(performers[1].Name).To(Equal("Brian Johnson")) + }) }) })