diff --git a/server/filter/filters.go b/server/filter/filters.go index e149dbced..5fa53daba 100644 --- a/server/filter/filters.go +++ b/server/filter/filters.go @@ -189,6 +189,11 @@ func ByGenreID(genreIds []string) Sqlizer { return genreTagFilter(Eq{"value": genreIds}) } +// ByAlbumID matches media files belonging to any of the given albums. +func ByAlbumID(albumIds []string) Sqlizer { + return Eq{"album_id": albumIds} +} + // ArtistsByGenreID matches artists credited as album artist on an album with any of the given // genre tag ids. Non-correlated semi-join: the correlated EXISTS form rescans albums per artist row. func ArtistsByGenreID(genreIds []string) Sqlizer { diff --git a/server/jellyfin/e2e/browsing_test.go b/server/jellyfin/e2e/browsing_test.go index 0b2769856..18565c635 100644 --- a/server/jellyfin/e2e/browsing_test.go +++ b/server/jellyfin/e2e/browsing_test.go @@ -179,6 +179,27 @@ var _ = Describe("Browsing", func() { }) }) + // Feishin fetches an album's tracks with AlbumIds=&IncludeItemTypes=Audio&Recursive=true. + Describe("album filtering (AlbumIds)", func() { + It("filters songs by AlbumIds", func() { + q := queryResult(get("/Items?IncludeItemTypes=Audio&Recursive=true&AlbumIds=" + enc(albumID("Abbey Road")))) + Expect(names(q.Items)).To(ConsistOf("Come Together", "Something")) + Expect(q.TotalRecordCount).To(Equal(2)) + }) + + It("matches any of multiple comma-separated AlbumIds", func() { + q := queryResult(get("/Items?IncludeItemTypes=Audio&Recursive=true&AlbumIds=" + enc(albumID("Abbey Road")) + "," + enc(albumID("IV")))) + Expect(names(q.Items)).To(ConsistOf("Come Together", "Something", "Stairway To Heaven")) + Expect(q.TotalRecordCount).To(Equal(3)) + }) + + It("returns nothing for an unknown album id", func() { + q := queryResult(get("/Items?IncludeItemTypes=Audio&Recursive=true&AlbumIds=" + enc("no-such-album"))) + Expect(q.Items).To(BeEmpty()) + Expect(q.TotalRecordCount).To(Equal(0)) + }) + }) + // Finamp's genre screen sends ParentId= (scoping) plus GenreIds=. Describe("genre filtering (GenreIds)", func() { lib1 := enc("1") diff --git a/server/jellyfin/items.go b/server/jellyfin/items.go index 43c8de12c..b9372c397 100644 --- a/server/jellyfin/items.go +++ b/server/jellyfin/items.go @@ -221,6 +221,7 @@ type itemsQuery struct { artistId string contributingOnly bool genreIds []string + albumIds []string } // parseItemsQuery also resolves the entity types (inferring them from the parent when @@ -243,6 +244,8 @@ func (api *Router) parseItemsQuery(ctx context.Context, r *http.Request) itemsQu parentId: dto.DecodeID(p.StringOr("parentid", "")), // Finamp's genre screen sends ParentId= for scoping plus GenreIds for the genre. genreIds: decodedQueryIDs(r, "genreids"), + // Feishin fetches an album's tracks with AlbumIds instead of ParentId. + albumIds: decodedQueryIDs(r, "albumids"), } // An artist's page filters by artist, not ParentId: Finamp sends ParentId= for scoping // plus AlbumArtistIds/ArtistIds/contributingArtistIds for the artist. @@ -528,6 +531,9 @@ func (api *Router) listSongs(ctx context.Context, opts model.QueryOptions, q ite default: filters = append(filters, notMissing) } + if len(q.albumIds) > 0 { + filters = append(filters, filter.ByAlbumID(q.albumIds)) + } if len(q.genreIds) > 0 { filters = append(filters, filter.ByGenreID(q.genreIds)) }