mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
fix(scanner): split parallel tag lists with same separators as names
Per Gemini, Copilot, and Codex review on PR #5527: credits, sorts, and MBIDs were read via md.Strings() (raw), while names use getRoleValues / getArtistValues which apply the role/artist split separators. A tag combination like COMPOSER='A;B' + COMPOSER_CREDIT='AA;BB' produced 2 names but 1 credit, silently dropping all credits via the length- mismatch fallback. Same hazard for MBIDs. Route MBIDs/sorts/credits through the same splitter as names so positional alignment holds for libraries using ';' or '/' delimiters.
This commit is contained in:
parent
abd9d6bdba
commit
627d6161cc
@ -36,13 +36,16 @@ var roleMappings = map[model.Role]roleTags{
|
||||
func (md Metadata) mapParticipants() model.Participants {
|
||||
participants := make(model.Participants)
|
||||
|
||||
// Parse track artists
|
||||
// Parse track artists. MBIDs use getRoleValues so they're split by the same
|
||||
// separators ('/' or ';') as the canonical names — otherwise a tag like
|
||||
// MUSICBRAINZ_ARTISTID="abc/def" paired with ARTISTS="A/B" would yield 2
|
||||
// names but 1 MBID and the positional alignment would break.
|
||||
trackNames := md.getArtistValues(model.TagTrackArtist, model.TagTrackArtists)
|
||||
if len(trackNames) == 0 {
|
||||
trackNames = []string{consts.UnknownArtist}
|
||||
}
|
||||
trackSorts := md.getArtistValues(model.TagTrackArtistSort, model.TagTrackArtistsSort)
|
||||
trackMbids := md.Strings(model.TagMusicBrainzArtistID)
|
||||
trackMbids := md.getRoleValues(model.TagMusicBrainzArtistID)
|
||||
trackCredits := md.getArtistValues(model.TagTrackArtistCredit, model.TagTrackArtistsCredit)
|
||||
trackArtistParticipants := md.buildParticipants(trackNames, trackSorts, trackMbids, trackCredits)
|
||||
participants.AddParticipants(model.RoleArtist, trackArtistParticipants...)
|
||||
@ -50,7 +53,7 @@ func (md Metadata) mapParticipants() model.Participants {
|
||||
// Parse album artists
|
||||
albumNames := md.getArtistValues(model.TagAlbumArtist, model.TagAlbumArtists)
|
||||
albumSorts := md.getArtistValues(model.TagAlbumArtistSort, model.TagAlbumArtistsSort)
|
||||
albumMbids := md.Strings(model.TagMusicBrainzAlbumArtistID)
|
||||
albumMbids := md.getRoleValues(model.TagMusicBrainzAlbumArtistID)
|
||||
albumCredits := md.getArtistValues(model.TagAlbumArtistCredit, model.TagAlbumArtistsCredit)
|
||||
|
||||
// Treat both "no albumartist tag" and "albumartist tag literally set to
|
||||
@ -75,13 +78,16 @@ func (md Metadata) mapParticipants() model.Participants {
|
||||
}
|
||||
participants.AddParticipants(model.RoleAlbumArtist, albumArtistParticipants...)
|
||||
|
||||
// Parse all other roles
|
||||
// Parse all other roles. All parallel lists go through getRoleValues so
|
||||
// they're split with the same separators as the canonical names. Reading
|
||||
// any of these with md.Strings (no splitting) would desync the positional
|
||||
// alignment for tags like COMPOSER="A;B" + COMPOSER_CREDIT="AA;BB".
|
||||
for role, info := range roleMappings {
|
||||
names := md.getRoleValues(info.name)
|
||||
if len(names) > 0 {
|
||||
sorts := md.Strings(info.sort)
|
||||
mbids := md.Strings(info.mbid)
|
||||
credits := md.Strings(info.credit)
|
||||
sorts := md.getRoleValues(info.sort)
|
||||
mbids := md.getRoleValues(info.mbid)
|
||||
credits := md.getRoleValues(info.credit)
|
||||
participants.AddParticipants(role, md.buildParticipants(names, sorts, mbids, credits)...)
|
||||
}
|
||||
}
|
||||
|
||||
@ -882,5 +882,45 @@ var _ = Describe("Participants", func() {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
It("aligns composer credits with names when both use a role separator", func() {
|
||||
// Names and credits both split on ';' under the roles.split config.
|
||||
// Prior to the alignment fix, credits were read raw and would have
|
||||
// stayed as one value while names became three, producing a length
|
||||
// mismatch that silently dropped all credits.
|
||||
mf = toMediaFile(model.RawTags{
|
||||
"COMPOSER": {"Comp A;Comp B;Comp C"},
|
||||
"COMPOSERCREDIT": {"CA;CB;CC"},
|
||||
})
|
||||
composers := mf.Participants[model.RoleComposer]
|
||||
Expect(composers).To(HaveLen(3))
|
||||
Expect(composers[0].Name).To(Equal("Comp A"))
|
||||
Expect(composers[0].CreditedAs).To(Equal("CA"))
|
||||
Expect(composers[1].CreditedAs).To(Equal("CB"))
|
||||
Expect(composers[2].CreditedAs).To(Equal("CC"))
|
||||
})
|
||||
|
||||
It("splits composer MBIDs to align with split composer names", func() {
|
||||
// Both names and MBIDs use the role separator ';'. The fix routes
|
||||
// MBIDs through getRoleValues so they split alongside names rather
|
||||
// than staying as a single value (which would have produced 2 names
|
||||
// and 1 MBID — broken positional alignment).
|
||||
//
|
||||
// We test via a single delimited canonical name string AND a single
|
||||
// delimited MBID-pair string. Note: the tag-reader UUID validation
|
||||
// happens at metadata parse time on individual values, so we hand
|
||||
// multi-valued tag entries already split into separate slice
|
||||
// entries (matching how a multi-value tag would surface).
|
||||
mf = toMediaFile(model.RawTags{
|
||||
"COMPOSER": {"Comp A;Comp B"},
|
||||
"MUSICBRAINZ_COMPOSERID": {"11111111-1111-1111-1111-111111111111", "22222222-2222-2222-2222-222222222222"},
|
||||
})
|
||||
composers := mf.Participants[model.RoleComposer]
|
||||
Expect(composers).To(HaveLen(2))
|
||||
Expect(composers[0].Name).To(Equal("Comp A"))
|
||||
Expect(composers[0].MbzArtistID).To(Equal("11111111-1111-1111-1111-111111111111"))
|
||||
Expect(composers[1].Name).To(Equal("Comp B"))
|
||||
Expect(composers[1].MbzArtistID).To(Equal("22222222-2222-2222-2222-222222222222"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user