diff --git a/model/participants.go b/model/participants.go index 5c476fe96..772bea97a 100644 --- a/model/participants.go +++ b/model/participants.go @@ -92,6 +92,21 @@ func (p ParticipantList) Join(sep string) string { }), sep) } +// JoinCredited joins the credited names of the participants with sep. +// Falls back to Name when CreditedAs is empty. +func (p ParticipantList) JoinCredited(sep string) string { + return strings.Join(slice.Map(p, func(part Participant) string { + n := part.CreditedAs + if n == "" { + n = part.Name + } + if part.SubRole != "" { + return n + " (" + part.SubRole + ")" + } + return n + }), sep) +} + type Participants map[Role]ParticipantList // Add adds the artists to the role, ignoring duplicates. diff --git a/server/subsonic/helpers.go b/server/subsonic/helpers.go index e4c39e373..da5c134bb 100644 --- a/server/subsonic/helpers.go +++ b/server/subsonic/helpers.go @@ -271,7 +271,7 @@ func osChildFromMediaFile(ctx context.Context, mf model.MediaFile) *responses.Op child.DisplayAlbumArtist = mf.AlbumArtist child.AlbumArtists = artistRefs(mf.Participants[model.RoleAlbumArtist]) var contributors []responses.Contributor - child.DisplayComposer = mf.Participants[model.RoleComposer].Join(consts.ArtistJoiner) + child.DisplayComposer = mf.Participants[model.RoleComposer].JoinCredited(consts.ArtistJoiner) for role, participants := range mf.Participants { if role == model.RoleArtist || role == model.RoleAlbumArtist { continue @@ -282,7 +282,7 @@ func osChildFromMediaFile(ctx context.Context, mf model.MediaFile) *responses.Op SubRole: participant.SubRole, Artist: responses.ArtistID3Ref{ Id: participant.ID, - Name: participant.Name, + Name: participantDisplayName(participant), }, }) } @@ -296,11 +296,20 @@ func artistRefs(participants model.ParticipantList) []responses.ArtistID3Ref { return slice.Map(participants, func(p model.Participant) responses.ArtistID3Ref { return responses.ArtistID3Ref{ Id: p.ID, - Name: p.Name, + Name: participantDisplayName(p), } }) } +// participantDisplayName returns CreditedAs if set, otherwise the canonical Name. +// Legacy rows (pre-rescan) have empty CreditedAs and continue to show canonical. +func participantDisplayName(p model.Participant) string { + if p.CreditedAs != "" { + return p.CreditedAs + } + return p.Name +} + func fakePath(mf model.MediaFile) string { builder := strings.Builder{} diff --git a/server/subsonic/helpers_test.go b/server/subsonic/helpers_test.go index 2ae6eb28e..684cac838 100644 --- a/server/subsonic/helpers_test.go +++ b/server/subsonic/helpers_test.go @@ -317,6 +317,45 @@ var _ = Describe("helpers", func() { Expect(child.Title).To(Equal("")) }) }) + + It("returns CreditedAs as the contributor name when present, with canonical artist ID", func() { + mf := model.MediaFile{ + ID: "song-1", + Participants: model.Participants{ + model.RoleArtist: model.ParticipantList{ + {Artist: model.Artist{ID: "canon-1", Name: "Planetary Assault Systems"}, CreditedAs: "PAS"}, + }, + model.RoleComposer: model.ParticipantList{ + {Artist: model.Artist{ID: "canon-2", Name: "Real Composer"}, CreditedAs: "R. Composer"}, + }, + }, + } + child := childFromMediaFile(context.Background(), mf) + Expect(child.OpenSubsonicChild).NotTo(BeNil()) + Expect(child.OpenSubsonicChild.Artists).To(HaveLen(1)) + Expect(child.OpenSubsonicChild.Artists[0].Id).To(Equal("canon-1")) + Expect(child.OpenSubsonicChild.Artists[0].Name).To(Equal("PAS")) + + Expect(child.OpenSubsonicChild.Contributors).To(HaveLen(1)) + Expect(child.OpenSubsonicChild.Contributors[0].Artist.Id).To(Equal("canon-2")) + Expect(child.OpenSubsonicChild.Contributors[0].Artist.Name).To(Equal("R. Composer")) + + Expect(child.OpenSubsonicChild.DisplayComposer).To(Equal("R. Composer")) + }) + + It("falls back to canonical Name when CreditedAs is empty (legacy participant rows)", func() { + mf := model.MediaFile{ + ID: "song-2", + Participants: model.Participants{ + model.RoleArtist: model.ParticipantList{ + {Artist: model.Artist{ID: "canon-3", Name: "Some Artist"}}, // no CreditedAs + }, + }, + } + child := childFromMediaFile(context.Background(), mf) + Expect(child.OpenSubsonicChild).NotTo(BeNil()) + Expect(child.OpenSubsonicChild.Artists[0].Name).To(Equal("Some Artist")) + }) }) Describe("osChildFromMediaFile", func() {