feat(scanner): honor artist split exceptions for participant tags

Applies Scanner.ArtistSplitExceptions to artist, albumartist and role tag
splitting. Generic tags (genre, mood, ...) are unaffected.
This commit is contained in:
Deluan 2026-07-01 21:45:30 -04:00
parent 2b0fc5762a
commit b847777e77
2 changed files with 48 additions and 0 deletions

View File

@ -182,6 +182,7 @@ func (md Metadata) getRoleValues(role model.TagName) []string {
conf = model.TagRolesConf()
}
if len(conf.Split) > 0 {
conf.ExceptionsRx = model.ArtistSplitExceptionsRx()
values = conf.SplitTagValue(values)
return filterDuplicatedOrEmptyValues(values)
}
@ -203,6 +204,7 @@ func (md Metadata) getArtistValues(single, multi model.TagName) []string {
conf = model.TagArtistsConf()
}
if len(conf.Split) > 0 {
conf.ExceptionsRx = model.ArtistSplitExceptionsRx()
vSingle = conf.SplitTagValue(vSingle)
return filterDuplicatedOrEmptyValues(vSingle)
}

View File

@ -4,6 +4,8 @@ import (
"os"
"github.com/google/uuid"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/conf/configtest"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/metadata"
@ -802,4 +804,48 @@ var _ = Describe("Participants", func() {
}
})
})
Describe("Artist split exceptions", func() {
BeforeEach(func() {
DeferCleanup(configtest.SetupConfig())
})
It("does not split a whitelisted artist name on the default separators", func() {
// " feat. " is a default artists separator (mappings.yaml)
conf.Server.Scanner.ArtistSplitExceptions = []string{"Someone feat. Else"}
mf = toMediaFile(model.RawTags{
"ARTIST": {"Artist Name feat. Someone feat. Else"},
})
artists := mf.Participants[model.RoleArtist]
Expect(artists).To(HaveLen(2))
Expect(artists[0].Name).To(Equal("Artist Name"))
Expect(artists[1].Name).To(Equal("Someone feat. Else"))
})
It("does not split a whitelisted name in role tags", func() {
// "/" is a default roles separator (mappings.yaml)
conf.Server.Scanner.ArtistSplitExceptions = []string{"AC/DC"}
mf = toMediaFile(model.RawTags{
"COMPOSER": {"AC/DC/John Doe"},
})
composers := mf.Participants[model.RoleComposer]
Expect(composers).To(HaveLen(2))
Expect(composers[0].Name).To(Equal("AC/DC"))
Expect(composers[1].Name).To(Equal("John Doe"))
})
It("splits normally when the exception does not match", func() {
conf.Server.Scanner.ArtistSplitExceptions = []string{"Iron and Wine"}
mf = toMediaFile(model.RawTags{
"ARTIST": {"Artist Name feat. Someone Else"},
})
artists := mf.Participants[model.RoleArtist]
Expect(artists).To(HaveLen(2))
Expect(artists[0].Name).To(Equal("Artist Name"))
Expect(artists[1].Name).To(Equal("Someone Else"))
})
})
})