fix(scanner): apply split exceptions when per-tag Split overrides participant tags

Per-tag Tags.<name>.Split makes the generic ingestion path split the tag
before participant mapping runs, bypassing the whitelist. Attach the
exceptions to participant tag mappings (including sort variants) in clean().
This commit is contained in:
Deluan 2026-07-01 22:01:24 -04:00
parent b847777e77
commit 63ae6f6d4b
3 changed files with 47 additions and 0 deletions

View File

@ -205,6 +205,7 @@ func clean(filePath string, tags model.RawTags) model.Tags {
cleaned := make(model.Tags, len(mappings))
for name, mapping := range mappings {
mapping = mapping.WithParticipantExceptions(name)
var values []string
switch mapping.Type {
case model.TagTypePair:

View File

@ -151,6 +151,33 @@ func ArtistSplitExceptionsRx() *regexp.Regexp {
return c.rx
}
// participantTagNames are the tags that hold artist names (or their sort
// values), where split exceptions apply.
var participantTagNames = sync.OnceValue(func() map[TagName]struct{} {
names := []TagName{
TagTrackArtist, TagTrackArtists, TagTrackArtistSort, TagTrackArtistsSort,
TagAlbumArtist, TagAlbumArtists, TagAlbumArtistSort, TagAlbumArtistsSort,
}
set := make(map[TagName]struct{}, len(names)+2*len(AllRoles))
for _, n := range names {
set[n] = struct{}{}
}
for role := range AllRoles {
set[TagName(role)] = struct{}{}
set[TagName(role+"sort")] = struct{}{}
}
return set
})
// WithParticipantExceptions returns the conf with the global artist split
// exceptions attached when name is a participant (artist/role) tag.
func (c TagConf) WithParticipantExceptions(name TagName) TagConf {
if _, ok := participantTagNames()[name]; ok {
c.ExceptionsRx = ArtistSplitExceptionsRx()
}
return c
}
type TagType string
const (

View File

@ -172,4 +172,23 @@ var _ = Describe("TagConf", func() {
Expect(second.MatchString("AC/DC")).To(BeTrue())
})
})
Describe("WithParticipantExceptions", func() {
BeforeEach(func() {
DeferCleanup(configtest.SetupConfig())
conf.Server.Scanner.ArtistSplitExceptions = []string{"Iron and Wine"}
})
It("attaches the exceptions regex to participant tags", func() {
for _, tag := range []TagName{"artist", "albumartist", "artists", "artistsort", "composer", "lyricist", "composersort"} {
Expect(TagConf{}.WithParticipantExceptions(tag).ExceptionsRx).ToNot(BeNil(), string(tag))
}
})
It("does not attach the exceptions regex to non-participant tags", func() {
for _, tag := range []TagName{"genre", "mood", "title", "releasetype"} {
Expect(TagConf{}.WithParticipantExceptions(tag).ExceptionsRx).To(BeNil(), string(tag))
}
})
})
})