video discover: preferred-languages filter (keep Bollywood etc. out of general feeds)
The general/curated rails (Popular/Trending/Top Rated + genre/decade) pull TMDB's GLOBAL lists, flooding feeds with foreign-language titles (Bollywood). Add a multi-language preference: - _disc_map now carries original_language (+ popularity) on each item. - discover_languages setting (default 'en'); /discover/list post-filters general/curated rails to it (dropping known non-preferred-language titles) and pages deeper to keep rails full. Rails with an explicit lang (the dedicated foreign rails) bypass the filter. - GET/POST /discover/languages to read/set the preference. - Removed the hardcoded lang=en on general rails (the setting drives it now). Default 'en' immediately fixes the Bollywood flood; UI to pick languages next.
This commit is contained in:
parent
e7b1a239b4
commit
28fe3d2b0b
3 changed files with 56 additions and 16 deletions
|
|
@ -176,6 +176,29 @@ def register_routes(bp):
|
|||
tr = None
|
||||
return jsonify({"trailer": tr or None})
|
||||
|
||||
@bp.route("/discover/languages", methods=["GET", "POST"])
|
||||
def video_discover_languages():
|
||||
"""Get/set the preferred original-languages for general rails (ISO-639-1 codes).
|
||||
POST {languages: ['en','ko']} (or 'en,ko'); GET returns the current list."""
|
||||
from . import get_video_db
|
||||
db = get_video_db()
|
||||
try:
|
||||
if request.method == "POST":
|
||||
body = request.get_json(silent=True) or {}
|
||||
langs = body.get("languages")
|
||||
if isinstance(langs, list):
|
||||
val = ",".join(str(c).strip().lower() for c in langs if str(c).strip())
|
||||
else:
|
||||
val = ",".join(c.strip().lower() for c in str(langs or "").split(",") if c.strip())
|
||||
db.set_setting("discover_languages", val or "en")
|
||||
return jsonify({"success": True,
|
||||
"languages": [c for c in (val or "en").split(",") if c]})
|
||||
raw = db.get_setting("discover_languages", "en") or "en"
|
||||
return jsonify({"languages": [c.strip() for c in raw.split(",") if c.strip()]})
|
||||
except Exception:
|
||||
logger.exception("discover languages get/set failed")
|
||||
return jsonify({"languages": ["en"]})
|
||||
|
||||
@bp.route("/discover/genres", methods=["GET"])
|
||||
def video_discover_genres():
|
||||
"""Genre id→name maps for both kinds (powers the genre rails + filter)."""
|
||||
|
|
@ -211,8 +234,19 @@ def register_routes(bp):
|
|||
decade = request.args.get("decade") or None
|
||||
providers = request.args.get("providers") or None
|
||||
sort = request.args.get("sort") or "popularity.desc"
|
||||
lang = (request.args.get("lang") or "").strip() or None # original-language filter
|
||||
lang = (request.args.get("lang") or "").strip() or None # explicit (foreign rail)
|
||||
hide_owned = (request.args.get("hide_owned") or "") in ("1", "true", "yes")
|
||||
# Preferred original-languages (multi) for GENERAL/curated rails — so the feeds
|
||||
# aren't flooded with foreign titles (e.g. Bollywood in Popular/Trending). A rail
|
||||
# with an explicit `lang` (a dedicated foreign rail) bypasses this. Default 'en'.
|
||||
prefer_langs = None
|
||||
if not lang:
|
||||
try:
|
||||
from . import get_video_db
|
||||
raw = get_video_db().get_setting("discover_languages", "en") or "en"
|
||||
prefer_langs = {c.strip().lower() for c in raw.split(",") if c.strip()} or None
|
||||
except Exception:
|
||||
prefer_langs = {"en"}
|
||||
|
||||
def fetch(p):
|
||||
if key == "trending":
|
||||
|
|
@ -224,11 +258,11 @@ def register_routes(bp):
|
|||
|
||||
try:
|
||||
items, seen = [], set()
|
||||
# Hiding owned + a huge library means most popular titles are already owned, so
|
||||
# page DEEPER and drop owned server-side until the rail has enough un-owned to
|
||||
# look full (instead of the client CSS-hiding most of a 2-page batch to nothing).
|
||||
target = 24 if hide_owned else 0
|
||||
max_pages = 8 if hide_owned else pages
|
||||
# When filtering (hide-owned or language), page DEEPER and drop items server-side
|
||||
# until the rail has ~enough to look full — instead of returning a half-empty rail.
|
||||
need_fill = hide_owned or bool(prefer_langs)
|
||||
target = 24 if need_fill else 0
|
||||
max_pages = 8 if need_fill else pages
|
||||
for offset in range(max_pages):
|
||||
batch = fetch(page + offset) or []
|
||||
for it in batch:
|
||||
|
|
@ -238,13 +272,17 @@ def register_routes(bp):
|
|||
seen.add(dk)
|
||||
if hide_owned and it.get("library_id") is not None:
|
||||
continue
|
||||
if prefer_langs:
|
||||
ol = (it.get("original_language") or "").lower()
|
||||
if ol and ol not in prefer_langs:
|
||||
continue # known foreign language not in your preference
|
||||
items.append(it)
|
||||
if key == "trending" or not batch:
|
||||
break # trending is a fixed list; empty batch = TMDB ran out
|
||||
if target and len(items) >= target:
|
||||
break # enough un-owned collected
|
||||
if not hide_owned and offset + 1 >= pages:
|
||||
break # normal mode: respect the requested page count
|
||||
break # enough collected
|
||||
if not target and offset + 1 >= pages:
|
||||
break # no filtering: respect the requested page count
|
||||
return jsonify({"items": items, "page": page})
|
||||
except Exception:
|
||||
logger.exception("discover list failed (key=%s)", key)
|
||||
|
|
|
|||
|
|
@ -482,6 +482,8 @@ class TMDBClient:
|
|||
"year": ((it.get("release_date") if is_movie else it.get("first_air_date")) or "")[:4] or None,
|
||||
"rating": it.get("vote_average") or None,
|
||||
"overview": it.get("overview") or None,
|
||||
"original_language": it.get("original_language") or None, # for the language filter
|
||||
"popularity": it.get("popularity") or None, # for blended ranking
|
||||
"poster": (self.POSTER_W + it["poster_path"]) if it.get("poster_path") else None,
|
||||
"backdrop": (self.BACKDROP_W + it["backdrop_path"]) if it.get("backdrop_path") else None,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -72,10 +72,10 @@
|
|||
'talk': '148, 163, 184', 'news': '148, 163, 184',
|
||||
};
|
||||
var DECADE_RAILS = [
|
||||
{ title: 'Best of the 2010s', q: 'kind=movie&decade=2010&sort=vote_average.desc&lang=en' },
|
||||
{ title: '2000s Favorites', q: 'kind=movie&decade=2000&sort=vote_average.desc&lang=en' },
|
||||
{ title: '’90s Classics', q: 'kind=movie&decade=1990&sort=vote_average.desc&lang=en' },
|
||||
{ title: 'Retro ’80s', q: 'kind=movie&decade=1980&sort=vote_average.desc&lang=en' },
|
||||
{ title: 'Best of the 2010s', q: 'kind=movie&decade=2010&sort=vote_average.desc' },
|
||||
{ title: '2000s Favorites', q: 'kind=movie&decade=2000&sort=vote_average.desc' },
|
||||
{ title: '’90s Classics', q: 'kind=movie&decade=1990&sort=vote_average.desc' },
|
||||
{ title: 'Retro ’80s', q: 'kind=movie&decade=1980&sort=vote_average.desc' },
|
||||
];
|
||||
// Dedicated foreign-language rails so non-English titles live HERE rather than
|
||||
// leaking into the general genre/decade rails (which are pinned to lang=en).
|
||||
|
|
@ -93,17 +93,17 @@
|
|||
// personalized first — seeded from what you actually own
|
||||
(state.taste.movie || []).slice(0, 3).forEach(function (name) {
|
||||
var id = gm[name.toLowerCase()];
|
||||
if (id != null) { out.push({ title: 'Because you like ' + name, q: 'kind=movie&genre=' + id + '&sort=popularity.desc&lang=en' }); used['m:' + name.toLowerCase()] = 1; }
|
||||
if (id != null) { out.push({ title: 'Because you like ' + name, q: 'kind=movie&genre=' + id + '&sort=popularity.desc' }); used['m:' + name.toLowerCase()] = 1; }
|
||||
});
|
||||
(state.taste.show || []).slice(0, 2).forEach(function (name) {
|
||||
var id = gs[name.toLowerCase()];
|
||||
if (id != null) { out.push({ title: 'More ' + name + ' shows', q: 'kind=show&genre=' + id + '&sort=popularity.desc&lang=en' }); }
|
||||
if (id != null) { out.push({ title: 'More ' + name + ' shows', q: 'kind=show&genre=' + id + '&sort=popularity.desc' }); }
|
||||
});
|
||||
out = out.concat(CURATED);
|
||||
GENRE_RAILS.forEach(function (name) {
|
||||
var id = gm[name.toLowerCase()];
|
||||
if (id != null && !used['m:' + name.toLowerCase()])
|
||||
out.push({ title: name, q: 'kind=movie&genre=' + id + '&sort=popularity.desc&lang=en' });
|
||||
out.push({ title: name, q: 'kind=movie&genre=' + id + '&sort=popularity.desc' });
|
||||
});
|
||||
return out.concat(DECADE_RAILS).concat(FOREIGN_RAILS);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue