diff --git a/api/video/discover.py b/api/video/discover.py index d941a527..81a9681c 100644 --- a/api/video/discover.py +++ b/api/video/discover.py @@ -322,7 +322,11 @@ def register_routes(bp): if key == "trending": return eng.trending() if key == "trending_today": - return eng.trending(window="day") # the ranked 'Top 10 today' chart + return eng.trending(window="day") # the ranked 'Top 10 today' chart (mixed) + if key == "trending_movies_today": + return eng.trending(window="day", kind="movie") # split 'Top 10 Movies Today' + if key == "trending_tv_today": + return eng.trending(window="day", kind="show") # split 'Top 10 TV Shows Today' if key: return eng.discover_curated(key, page=p) return eng.discover_filter( @@ -360,7 +364,7 @@ def register_routes(bp): # thread-safe) so digging 20 pages deep costs ~4 page-latencies, not 20. need_fill = hide_owned or bool(prefer_langs) target = 20 if need_fill else 0 - if key in ("trending", "trending_today"): + if key in ("trending", "trending_today", "trending_movies_today", "trending_tv_today"): consume(fetch(page)) # a single fixed chart; never paged elif not need_fill: for offset in range(pages): # no filtering: respect requested page count diff --git a/core/video/enrichment/clients.py b/core/video/enrichment/clients.py index a049716b..b729a2fb 100644 --- a/core/video/enrichment/clients.py +++ b/core/video/enrichment/clients.py @@ -447,16 +447,22 @@ class TMDBClient: "poster": (self.PROFILE + it["profile_path"]) if it.get("profile_path") else None}) return out - def trending(self, window="week"): - """Trending movies + shows this week — fills the search page when idle and - the Discover hero slideshow (hence backdrops via _disc_map).""" + def trending(self, window="week", kind=None): + """Trending titles. ``kind`` None = mixed movies + shows (/trending/all — the + search-idle filler + Discover hero slideshow, hence backdrops via _disc_map). + ``kind`` 'movie' / 'show' hit the dedicated single-type charts (/trending/movie, + /trending/tv) that power the split 'Top 10 Movies / TV Shows Today' rails. + Single-type endpoints omit media_type, so the kind is forced into _disc_map.""" if not self.api_key: return [] import requests - r = requests.get(self.BASE + "/trending/all/" + window, - params={"api_key": self.api_key}, timeout=15) + path = ("/trending/movie/" if kind == "movie" + else "/trending/tv/" if kind == "show" + else "/trending/all/") + window + r = requests.get(self.BASE + path, params={"api_key": self.api_key}, timeout=15) r.raise_for_status() - return self._disc_map((r.json() or {}).get("results"), None)[:20] + forced = kind if kind in ("movie", "show") else None + return self._disc_map((r.json() or {}).get("results"), forced)[:20] # ── discover (browse TMDB by curated list / genre / year / decade) ──────── def _disc_map(self, results, kind): diff --git a/core/video/enrichment/engine.py b/core/video/enrichment/engine.py index 626e5b62..f02bbb0b 100644 --- a/core/video/enrichment/engine.py +++ b/core/video/enrichment/engine.py @@ -310,19 +310,21 @@ class VideoEnrichmentEngine: return [] return self._stamp_owned(results) - def trending(self, window="week") -> list: + def trending(self, window="week", kind=None) -> list: """Trending titles, annotated owned/not. ``window='day'`` is the real-time - daily chart (the iconic 'Top 10 today' row); 'week' is the steadier hero/idle - feed. Order is preserved, so the caller can render rank numbers.""" + daily chart (the iconic 'Top 10 today' rows); 'week' is the steadier hero/idle + feed. ``kind`` None = mixed; 'movie'/'show' = the dedicated single-type charts + (split 'Top 10 Movies / TV Shows Today'). Order is preserved for rank numbers.""" w = self.workers.get("tmdb") if not w or not w.enabled: return [] window = "day" if window == "day" else "week" - ck = ("trending", window) + kind = kind if kind in ("movie", "show") else None + ck = ("trending", window, kind) cached = self._cache_get(ck) if cached is None: try: - cached = w.client.trending(window=window) or [] + cached = w.client.trending(window=window, kind=kind) or [] self._cache_put(ck, cached, ttl=3600) except Exception: logger.exception("video trending failed") diff --git a/webui/static/video/video-discover.js b/webui/static/video/video-discover.js index c802b15a..0b824911 100644 --- a/webui/static/video/video-discover.js +++ b/webui/static/video/video-discover.js @@ -49,8 +49,11 @@ } // ── the rail stack (personalized + curated + genre + decade) ────────────── - // The iconic ranked "Top 10 today" row — daily TMDB trending, rendered with rank numbers. - var TOP10 = { title: 'Top 10 Today', q: 'key=trending_today&lang=any', ranked: true }; + // The iconic ranked "Top 10 today" rows — split Movies / TV like Netflix, so TV + // isn't drowned out by the movie-heavy mixed chart. Daily TMDB trending, rank numbers. + // (Titles are terse because the group header already says "Top 10 today".) + var TOP10_MOVIES = { title: 'Movies', q: 'key=trending_movies_today&lang=any', ranked: true }; + var TOP10_TV = { title: 'TV Shows', q: 'key=trending_tv_today&lang=any', ranked: true }; var CURATED = [ { title: 'Trending This Week', q: 'key=trending' }, { title: 'Popular Movies', q: 'key=popular_movies' }, @@ -155,7 +158,7 @@ // keep those group ids. Order of this array IS the on-screen order. return [ { id: 'foryou', label: 'For you', rails: foryou }, - { id: 'topten', label: 'Top 10 today', rails: [TOP10] }, + { id: 'topten', label: 'Top 10 today', rails: [TOP10_MOVIES, TOP10_TV] }, { id: 'new', label: 'New & noteworthy', rails: NEW_RAILS }, { id: 'collection', label: 'Finish your collection', rails: [] }, { id: 'taste', label: 'More of what you like', rails: taste },