fix(playlists): include co-credited album artists when adding an artist to a playlist - #6240 (#6241)

* fix(persistence): include co-credited album artists when adding an artist to a playlist - #6240

Signed-off-by: Dawid Krynski <188586034+DawidKrynski@users.noreply.github.com>

* test(persistence): cover first album artist and track-artist-only in AddArtists

The joint track now uses a track artist that is not an album artist, and
the AddArtists specs check all three cases: the first album artist still
matches, a co-credited album artist matches, and a track-artist-only ID
adds nothing. The last case guards against widening the role filter.

---------

Signed-off-by: Dawid Krynski <188586034+DawidKrynski@users.noreply.github.com>
Co-authored-by: Dawid Krynski <188586034+DawidKrynski@users.noreply.github.com>
Co-authored-by: Deluan <deluan@navidrome.org>
This commit is contained in:
DawidKrynski 2026-09-28 14:07:05 +02:00 • committed by GitHub
commit ab5869123d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 42 additions and 1 deletions

View file

@ -234,7 +234,9 @@ func (r *playlistTrackRepository) AddAlbums(ctx context.Context, albumIds []stri
}
func (r *playlistTrackRepository) AddArtists(ctx context.Context, artistIds []string) (int, error) {
return r.addMediaFileIds(ctx, Eq{"album_artist_id": artistIds})
// Match by album-artist participation, not the deprecated album_artist_id
// column, which only holds the first album artist.
return r.addMediaFileIds(ctx, ParticipantIDFilter("media_file", artistIds, model.RoleAlbumArtist))
}
func (r *playlistTrackRepository) AddDiscs(ctx context.Context, discs []model.DiscID) (int, error) {

View file

@ -219,6 +219,45 @@ var _ = Describe("PlaylistTrackRepository", func() {
})
})
Describe("AddArtists", func() {
var tracks model.PlaylistTrackRepository
var joint model.MediaFile
BeforeEach(func() {
mfRepo := NewMediaFileRepository(GetDBXBuilder())
joint = mf(model.MediaFile{ID: "pls-coartist-track", Title: "Joint Track", ArtistID: artistPunctuation.ID,
Artist: artistPunctuation.Name, AlbumID: "pls-coartist-album", Album: "Joint Album",
AlbumArtistID: artistKraftwerk.ID, AlbumArtist: artistKraftwerk.Name, Path: p("joint/track.mp3")})
joint.Participants[model.RoleAlbumArtist] = model.ParticipantList{
{Artist: artistKraftwerk},
{Artist: artistBeatles},
}
Expect(mfRepo.Put(ctx, &joint)).To(Succeed())
DeferCleanup(func() { _ = mfRepo.Delete(ctx, joint.ID) })
plsRepo := NewPlaylistRepository(GetDBXBuilder())
pls := model.Playlist{Name: "Co-album-artist", OwnerID: adminUser.ID, OwnerName: adminUser.UserName}
Expect(plsRepo.Put(ctx, &pls)).To(Succeed())
DeferCleanup(func() { _ = plsRepo.Delete(ctx, pls.ID) })
tracks = plsRepo.Tracks(ctx, pls.ID, false)
})
It("adds tracks where the artist is the first album artist", func() {
Expect(tracks.AddArtists(ctx, []string{artistKraftwerk.ID})).To(Equal(1))
Expect(tracks.GetMediaFileIDs(ctx)).To(ConsistOf(joint.ID))
})
It("adds tracks where the artist is not the first album artist", func() {
Expect(tracks.AddArtists(ctx, []string{artistBeatles.ID})).To(Equal(1))
Expect(tracks.GetMediaFileIDs(ctx)).To(ConsistOf(joint.ID))
})
It("does not add tracks where the artist is only the track artist", func() {
Expect(tracks.AddArtists(ctx, []string{artistPunctuation.ID})).To(Equal(0))
Expect(tracks.GetMediaFileIDs(ctx)).To(BeEmpty())
})
})
Describe("library access", func() {
var otherLib model.Library
var restrictedUser model.User