fix(jellyfin): set ArtistItems/AlbumArtists on songs (Now Playing artist)

SongToBaseItem only set Artists (names) and AlbumArtist, not the structured
ArtistItems/AlbumArtists. Finamp's Now Playing screen reads ArtistItems and
shows 'Unknown Artist' when it's absent. Populate both (track artist and
album artist) as name+id pairs, mirroring AlbumToBaseItem.
This commit is contained in:
Deluan 2026-07-06 09:18:22 -04:00
commit 1fda149e4e
3 changed files with 34 additions and 0 deletions

View file

@ -108,6 +108,15 @@ func SongToBaseItem(mf model.MediaFile) BaseItemDto {
UserData: UserData(mf.Annotations, mf.ID),
MediaSources: []MediaSourceInfo{MediaSourceFromMediaFile(mf)},
}
// Structured artist links: clients (e.g. Finamp's Now Playing screen) read ArtistItems for the
// displayed artist and fall back to "Unknown Artist" when it's absent, even though Artists
// carries the same name. ArtistItems is the track artist; AlbumArtists the album artist.
if mf.ArtistID != "" {
item.ArtistItems = []NameGuidPair{{Name: mf.Artist, Id: EncodeID(mf.ArtistID)}}
}
if mf.AlbumArtistID != "" {
item.AlbumArtists = []NameGuidPair{{Name: mf.AlbumArtist, Id: EncodeID(mf.AlbumArtistID)}}
}
if mf.Year > 0 {
item.ProductionYear = new(mf.Year)
}

View file

@ -53,6 +53,21 @@ var _ = Describe("mappers", func() {
Expect(SongToBaseItem(model.MediaFile{ID: "s1", Title: "Song"}).DateCreated).To(BeEmpty())
})
It("sets ArtistItems and AlbumArtists (encoded ids) from the track and album artist", func() {
mf := model.MediaFile{
ID: "s1", Title: "Song",
Artist: "The Band", ArtistID: "ar-1",
AlbumArtist: "Various", AlbumArtistID: "ar-2",
}
item := SongToBaseItem(mf)
Expect(item.ArtistItems).To(Equal([]NameGuidPair{{Name: "The Band", Id: EncodeID("ar-1")}}))
Expect(item.AlbumArtists).To(Equal([]NameGuidPair{{Name: "Various", Id: EncodeID("ar-2")}}))
})
It("omits ArtistItems when the track has no artist id", func() {
Expect(SongToBaseItem(model.MediaFile{ID: "s1", Title: "Song", Artist: "X"}).ArtistItems).To(BeNil())
})
It("builds a MediaSourceInfo from a media file", func() {
mf := model.MediaFile{ID: "s1", Size: 5242880, Suffix: "mp3", BitRate: 320, Duration: 100}
src := MediaSourceFromMediaFile(mf)

View file

@ -174,6 +174,16 @@ var _ = Describe("Browsing", func() {
Expect(err).ToNot(HaveOccurred())
})
It("includes structured ArtistItems and AlbumArtists on a song (now-playing artist)", func() {
var item dto.BaseItemDto
parseInto(get("/Items/"+enc(songID("So What"))), &item)
Expect(item.ArtistItems).ToNot(BeEmpty())
Expect(item.ArtistItems[0].Name).To(Equal("Miles Davis"))
Expect(item.ArtistItems[0].Id).ToNot(BeEmpty())
Expect(item.AlbumArtists).ToNot(BeEmpty())
Expect(item.AlbumArtists[0].Name).To(Equal("Miles Davis"))
})
It("resolves an artist", func() {
var item dto.BaseItemDto
parseInto(get("/Items/"+enc(artistID("Miles Davis"))), &item)