fix(catalog): use season posters for episode cards
This commit is contained in:
@@ -28,8 +28,8 @@ const episodeCatalogSelectBody = `(
|
|||||||
COALESCE(e.imdb_id, '') AS imdb_id,
|
COALESCE(e.imdb_id, '') AS imdb_id,
|
||||||
COALESCE(e.tmdb_id, '') AS tmdb_id,
|
COALESCE(e.tmdb_id, '') AS tmdb_id,
|
||||||
COALESCE(e.tvdb_id, '') AS tvdb_id,
|
COALESCE(e.tvdb_id, '') AS tvdb_id,
|
||||||
COALESCE(e.still_path, '') AS poster_path,
|
COALESCE(NULLIF(s.poster_path, ''), NULLIF(si.poster_path, ''), NULLIF(e.still_path, ''), '') AS poster_path,
|
||||||
COALESCE(e.still_thumbhash, '') AS poster_thumbhash,
|
COALESCE(NULLIF(s.poster_thumbhash, ''), NULLIF(si.poster_thumbhash, ''), NULLIF(e.still_thumbhash, ''), '') AS poster_thumbhash,
|
||||||
COALESCE(si.backdrop_path, '') AS backdrop_path,
|
COALESCE(si.backdrop_path, '') AS backdrop_path,
|
||||||
COALESCE(si.backdrop_thumbhash, '') AS backdrop_thumbhash,
|
COALESCE(si.backdrop_thumbhash, '') AS backdrop_thumbhash,
|
||||||
COALESCE(si.logo_path, '') AS logo_path,
|
COALESCE(si.logo_path, '') AS logo_path,
|
||||||
@@ -58,6 +58,7 @@ const episodeCatalogSelectBody = `(
|
|||||||
e.updated_at
|
e.updated_at
|
||||||
FROM episodes e
|
FROM episodes e
|
||||||
JOIN media_items si ON si.content_id = e.series_id
|
JOIN media_items si ON si.content_id = e.series_id
|
||||||
|
LEFT JOIN seasons s ON s.content_id = e.season_id
|
||||||
WHERE %s
|
WHERE %s
|
||||||
) mi`
|
) mi`
|
||||||
|
|
||||||
|
|||||||
@@ -2153,7 +2153,7 @@ func (f *Fetcher) fetchEpisodeTargetsByContentIDs(ctx context.Context, contentID
|
|||||||
|
|
||||||
effectiveLibraryIDs := effectiveFetchLibraryIDs(libraryIDs, filter)
|
effectiveLibraryIDs := effectiveFetchLibraryIDs(libraryIDs, filter)
|
||||||
|
|
||||||
fromClause := "episodes e JOIN media_items si ON e.series_id = si.content_id"
|
fromClause := "episodes e JOIN media_items si ON e.series_id = si.content_id LEFT JOIN seasons s ON s.content_id = e.season_id"
|
||||||
if libraryID != nil || effectiveLibraryIDs != nil {
|
if libraryID != nil || effectiveLibraryIDs != nil {
|
||||||
fromClause += " JOIN media_item_libraries mil ON si.content_id = mil.content_id"
|
fromClause += " JOIN media_item_libraries mil ON si.content_id = mil.content_id"
|
||||||
}
|
}
|
||||||
@@ -2187,16 +2187,16 @@ func (f *Fetcher) fetchEpisodeTargetsByContentIDs(ctx context.Context, contentID
|
|||||||
e.overview,
|
e.overview,
|
||||||
e.runtime,
|
e.runtime,
|
||||||
e.rating_imdb,
|
e.rating_imdb,
|
||||||
e.still_path,
|
COALESCE(NULLIF(s.poster_path, ''), NULLIF(si.poster_path, ''), NULLIF(e.still_path, ''), '') AS poster_path,
|
||||||
e.still_thumbhash,
|
COALESCE(NULLIF(s.poster_thumbhash, ''), NULLIF(si.poster_thumbhash, ''), NULLIF(e.still_thumbhash, ''), '') AS poster_thumbhash,
|
||||||
e.season_number,
|
e.season_number,
|
||||||
e.episode_number,
|
e.episode_number,
|
||||||
e.air_date,
|
e.air_date,
|
||||||
si.title,
|
si.title,
|
||||||
si.genres,
|
si.genres,
|
||||||
si.content_rating,
|
si.content_rating,
|
||||||
si.backdrop_path,
|
COALESCE(NULLIF(e.still_path, ''), NULLIF(si.backdrop_path, ''), '') AS backdrop_path,
|
||||||
si.backdrop_thumbhash,
|
COALESCE(NULLIF(e.still_thumbhash, ''), NULLIF(si.backdrop_thumbhash, ''), '') AS backdrop_thumbhash,
|
||||||
si.logo_path,
|
si.logo_path,
|
||||||
si.status
|
si.status
|
||||||
FROM %s
|
FROM %s
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ vi.mock("@/playback/watchPlaybackContext", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
describe("ContinueWatchingCard", () => {
|
describe("ContinueWatchingCard", () => {
|
||||||
it("prefers the poster image for episodes (poster_url is the horizontal still)", () => {
|
it("prefers the backdrop image for section episodes (backdrop_url is the horizontal still)", () => {
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
const markup = renderToStaticMarkup(
|
const markup = renderToStaticMarkup(
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
@@ -34,7 +34,7 @@ describe("ContinueWatchingCard", () => {
|
|||||||
position_seconds: 120,
|
position_seconds: 120,
|
||||||
duration_seconds: 3600,
|
duration_seconds: 3600,
|
||||||
progress_updated_at: "2026-03-07T00:00:00Z",
|
progress_updated_at: "2026-03-07T00:00:00Z",
|
||||||
poster_url: "/episode-poster.jpg",
|
poster_url: "/season-poster.jpg",
|
||||||
poster_thumbhash: "",
|
poster_thumbhash: "",
|
||||||
backdrop_url: "/episode-backdrop.jpg",
|
backdrop_url: "/episode-backdrop.jpg",
|
||||||
backdrop_thumbhash: "",
|
backdrop_thumbhash: "",
|
||||||
@@ -45,8 +45,8 @@ describe("ContinueWatchingCard", () => {
|
|||||||
</QueryClientProvider>,
|
</QueryClientProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(markup).toContain('src="/episode-poster.jpg"');
|
expect(markup).toContain('src="/episode-backdrop.jpg"');
|
||||||
expect(markup).not.toContain('src="/episode-backdrop.jpg"');
|
expect(markup).not.toContain('src="/season-poster.jpg"');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("prefers the backdrop image for movies (poster_url is a vertical poster)", () => {
|
it("prefers the backdrop image for movies (poster_url is a vertical poster)", () => {
|
||||||
|
|||||||
@@ -144,19 +144,23 @@ export default function ContinueWatchingCard(props: ContinueWatchingCardProps) {
|
|||||||
? "w-[140px] shrink-0 sm:w-[160px] lg:w-[185px]"
|
? "w-[140px] shrink-0 sm:w-[160px] lg:w-[185px]"
|
||||||
: "w-[260px] shrink-0 sm:w-[315px]";
|
: "w-[260px] shrink-0 sm:w-[315px]";
|
||||||
const imageAspect = isPoster ? "aspect-[2/3]" : "aspect-video";
|
const imageAspect = isPoster ? "aspect-[2/3]" : "aspect-video";
|
||||||
// Episodes store the horizontal still in poster_url (see episode_catalog_source.go);
|
const isSectionEpisode = "sectionItem" in props && props.sectionItem?.type === "episode";
|
||||||
// wide-variant movies/series/seasons need the backdrop for the 16:9 card.
|
// Section episode payloads use poster_url for vertical season/series artwork
|
||||||
// Poster variant always wants the vertical poster.
|
// and backdrop_url for the episode still used by wide cards.
|
||||||
const imagePrimary = isPoster
|
const imagePrimary = isPoster
|
||||||
? card.posterUrl
|
? card.posterUrl
|
||||||
: card.type === "episode"
|
: isSectionEpisode
|
||||||
? card.posterUrl
|
? card.backdropUrl
|
||||||
: card.backdropUrl;
|
: card.type === "episode"
|
||||||
|
? card.posterUrl
|
||||||
|
: card.backdropUrl;
|
||||||
const imageFallback = isPoster
|
const imageFallback = isPoster
|
||||||
? card.backdropUrl
|
? card.backdropUrl
|
||||||
: card.type === "episode"
|
: isSectionEpisode
|
||||||
? card.backdropUrl
|
? card.posterUrl
|
||||||
: card.posterUrl;
|
: card.type === "episode"
|
||||||
|
? card.backdropUrl
|
||||||
|
: card.posterUrl;
|
||||||
const imageSrc = imagePrimary || imageFallback;
|
const imageSrc = imagePrimary || imageFallback;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user