From 87cd3f1037d02170990db9eb56f328e7cdfb7548 Mon Sep 17 00:00:00 2001 From: Deluan Date: Sun, 24 May 2026 21:37:01 -0300 Subject: [PATCH] fix(scanner): treat explicit '[Unknown Artist]' ALBUMARTIST as missing The old parseArtists function (removed in this branch) substituted consts.UnknownArtist when albumartist tags were empty, so the downstream compilation/fallback check matched both cases. The inlined replacement only checked len(albumNames)==0, narrowing the condition: files explicitly tagged ALBUMARTIST='[Unknown Artist]' (emitted by some rippers, and matching what Navidrome itself stores for untagged files) stopped routing to Various Artists for compilations. Restore the original semantics by treating len==1 with the literal UnknownArtist string as equivalent to no tag. --- model/metadata/map_participants.go | 10 +++++++++- model/metadata/map_participants_test.go | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/model/metadata/map_participants.go b/model/metadata/map_participants.go index b5c6e0c38..4c9c0de01 100644 --- a/model/metadata/map_participants.go +++ b/model/metadata/map_participants.go @@ -53,8 +53,16 @@ func (md Metadata) mapParticipants() model.Participants { albumMbids := md.Strings(model.TagMusicBrainzAlbumArtistID) albumCredits := md.getArtistValues(model.TagAlbumArtistCredit, model.TagAlbumArtistsCredit) + // Treat both "no albumartist tag" and "albumartist tag literally set to + // the UnknownArtist placeholder" as missing — some rippers emit the + // literal '[Unknown Artist]' string, and the original parseArtists path + // (replaced in this branch) substituted UnknownArtist on its own so the + // downstream check matched either case. + albumArtistMissing := len(albumNames) == 0 || + (len(albumNames) == 1 && albumNames[0] == consts.UnknownArtist) + var albumArtistParticipants []model.Participant - if len(albumNames) == 0 { + if albumArtistMissing { if md.Bool(model.TagCompilation) { albumArtistParticipants = md.buildParticipants( []string{consts.VariousArtists}, nil, diff --git a/model/metadata/map_participants_test.go b/model/metadata/map_participants_test.go index 7ef6183ee..3467bb99f 100644 --- a/model/metadata/map_participants_test.go +++ b/model/metadata/map_participants_test.go @@ -460,6 +460,26 @@ var _ = Describe("Participants", func() { }) }) + When("the COMPILATION tag is true and ALBUMARTIST is the UnknownArtist placeholder", func() { + BeforeEach(func() { + // Some rippers emit the literal '[Unknown Artist]' string + // when no album artist is set. The fallback must still + // route to Various Artists for compilations. + mf = toMediaFile(model.RawTags{ + "COMPILATION": {"1"}, + "ALBUMARTIST": {consts.UnknownArtist}, + }) + }) + + It("should substitute Various Artists as the album artist", func() { + participants := mf.Participants + Expect(participants).To(HaveKeyWithValue(model.RoleAlbumArtist, HaveLen(1))) + albumArtist := participants[model.RoleAlbumArtist][0] + Expect(albumArtist.Name).To(Equal("Various Artists")) + Expect(albumArtist.MbzArtistID).To(Equal(consts.VariousArtistsMbzId)) + }) + }) + When("the COMPILATION tag is true and there are ALBUMARTIST tags", func() { BeforeEach(func() { mf = toMediaFile(model.RawTags{