diff --git a/api/video/enrichment.py b/api/video/enrichment.py index 59f6b8bd..615006f1 100644 --- a/api/video/enrichment.py +++ b/api/video/enrichment.py @@ -47,6 +47,7 @@ def register_routes(bp): "tvdb_api_key": db.get_setting("tvdb_api_key") or "", "omdb_api_key": db.get_setting("omdb_api_key") or "", "billboard_autoplay": (db.get_setting("billboard_autoplay") or "1") == "1", + "watch_region": (db.get_setting("watch_region") or "US").upper(), }) @bp.route("/prefs", methods=["GET"]) @@ -54,7 +55,10 @@ def register_routes(bp): # Lightweight UI prefs for the detail page (no API keys). from . import get_video_db db = get_video_db() - return jsonify({"billboard_autoplay": (db.get_setting("billboard_autoplay") or "1") == "1"}) + return jsonify({ + "billboard_autoplay": (db.get_setting("billboard_autoplay") or "1") == "1", + "watch_region": (db.get_setting("watch_region") or "US").upper(), + }) @bp.route("/enrichment/config", methods=["POST"]) def video_enrichment_config_save(): @@ -67,6 +71,9 @@ def register_routes(bp): db.set_setting("tvdb_api_key", body.get("tvdb_api_key") or "") if "billboard_autoplay" in body: db.set_setting("billboard_autoplay", "1" if body.get("billboard_autoplay") else "0") + if "watch_region" in body: + region = (body.get("watch_region") or "US").strip().upper()[:2] or "US" + db.set_setting("watch_region", region) if "omdb_api_key" in body: new_key = body.get("omdb_api_key") or "" changed = new_key != (db.get_setting("omdb_api_key") or "") diff --git a/core/video/enrichment/clients.py b/core/video/enrichment/clients.py index 1278cca5..fc565f10 100644 --- a/core/video/enrichment/clients.py +++ b/core/video/enrichment/clients.py @@ -466,7 +466,7 @@ class TMDBClient: "poster": (self.POSTER_W + it["poster_path"]) if it.get("poster_path") else None}) return out[:20] - def full_detail(self, kind, tmdb_id): + def full_detail(self, kind, tmdb_id, region="US"): """Complete detail for a TMDB title NOT in the library — shaped like the library detail payload but with direct image URLs (so the same detail UI renders it). Seasons carry counts; episodes load lazily per season.""" @@ -503,7 +503,7 @@ class TMDBClient: for p in cmeta.get("cast") or []], "crew": [{"name": p["name"], "job": p.get("job"), "tmdb_id": p.get("tmdb_id")} for p in cmeta.get("crew") or []], - "_extras": self._parse_extras(kind, dr), + "_extras": self._parse_extras(kind, dr, region), } self._fill_collection(out["_extras"]) if kind == "movie": diff --git a/core/video/enrichment/engine.py b/core/video/enrichment/engine.py index dd60a5ec..d91c98e0 100644 --- a/core/video/enrichment/engine.py +++ b/core/video/enrichment/engine.py @@ -38,6 +38,12 @@ class VideoEnrichmentEngine: for w in self.workers.values(): w.restore_paused() + def _region(self): + try: + return (self.db.get_setting("watch_region") or "US").upper() + except Exception: + return "US" + def _cache_get(self, key): return self._cache.get(key) @@ -171,11 +177,12 @@ class VideoEnrichmentEngine: info = (self.db.movie_match_info(item_id) if kind == "movie" else self.db.show_match_info(item_id)) if info and info.get("tmdb_id"): - key = ("extras", kind, info["tmdb_id"]) + region = self._region() + key = ("extras", kind, info["tmdb_id"], region) cached = self._cache_get(key) if cached is None: try: - cached = w.client.extras(kind, info["tmdb_id"]) or {} + cached = w.client.extras(kind, info["tmdb_id"], region=region) or {} self._cache_put(key, cached) except Exception: logger.exception("item_extras failed for %s %s", kind, item_id) @@ -304,11 +311,12 @@ class VideoEnrichmentEngine: lib_id = self.db.library_id_for_tmdb(kind, tmdb_id) if lib_id: return {"redirect": {"source": "library", "kind": kind, "id": lib_id}} - cached = self._cache_get(("detail", kind, tmdb_id)) + region = self._region() + cached = self._cache_get(("detail", kind, tmdb_id, region)) if cached is not None: return dict(cached) try: - d = w.client.full_detail(kind, tmdb_id) + d = w.client.full_detail(kind, tmdb_id, region=region) except Exception: logger.exception("tmdb_detail failed for %s %s", kind, tmdb_id) return None @@ -336,7 +344,7 @@ class VideoEnrichmentEngine: d["episode_total"] = sum(s["episode_total"] for s in seasons) d["episode_owned"] = 0 self._fill_tmdb_ratings(d) - self._cache_put(("detail", kind, tmdb_id), d) + self._cache_put(("detail", kind, tmdb_id, region), d) return d def _fill_tmdb_ratings(self, d) -> None: diff --git a/tests/test_video_api.py b/tests/test_video_api.py index 2ce69ed6..d1ca6218 100644 --- a/tests/test_video_api.py +++ b/tests/test_video_api.py @@ -268,14 +268,17 @@ def test_enrichment_config_save_load(tmp_path, monkeypatch): client = app.test_client() try: assert client.get("/api/video/enrichment/config").get_json() == { - "tmdb_api_key": "", "tvdb_api_key": "", "omdb_api_key": "", "billboard_autoplay": True} + "tmdb_api_key": "", "tvdb_api_key": "", "omdb_api_key": "", + "billboard_autoplay": True, "watch_region": "US"} client.post("/api/video/enrichment/config", json={"tmdb_api_key": "abc", "tvdb_api_key": "xyz", "omdb_api_key": "om", - "billboard_autoplay": False}) + "billboard_autoplay": False, "watch_region": "gb"}) assert client.get("/api/video/enrichment/config").get_json() == { - "tmdb_api_key": "abc", "tvdb_api_key": "xyz", "omdb_api_key": "om", "billboard_autoplay": False} + "tmdb_api_key": "abc", "tvdb_api_key": "xyz", "omdb_api_key": "om", + "billboard_autoplay": False, "watch_region": "GB"} assert db.get_setting("tmdb_api_key") == "abc" and db.get_setting("omdb_api_key") == "om" - assert client.get("/api/video/prefs").get_json() == {"billboard_autoplay": False} + assert client.get("/api/video/prefs").get_json() == { + "billboard_autoplay": False, "watch_region": "GB"} finally: videoapi._video_db = None diff --git a/tests/test_video_enrichment.py b/tests/test_video_enrichment.py index a48c6477..6955f3d3 100644 --- a/tests/test_video_enrichment.py +++ b/tests/test_video_enrichment.py @@ -217,7 +217,7 @@ def test_engine_tmdb_detail_redirects_when_owned(db): class Tmdb: enabled = True - def full_detail(self, kind, tid): raise AssertionError("must not fetch an owned title") + def full_detail(self, kind, tid, region="US"): raise AssertionError("must not fetch an owned title") eng = VideoEnrichmentEngine(db, {"tmdb": Tmdb()}) assert eng.tmdb_detail("movie", 77) == {"redirect": {"source": "library", "kind": "movie", "id": mid}} @@ -225,7 +225,7 @@ def test_engine_tmdb_detail_redirects_when_owned(db): def test_engine_tmdb_detail_assembles_show(db): class Tmdb: enabled = True - def full_detail(self, kind, tid): + def full_detail(self, kind, tid, region="US"): return {"kind": "show", "tmdb_id": tid, "title": "Loki", "imdb_id": None, "poster_url": "http://p", "backdrop_url": None, "cast": [], "crew": [], "_extras": {"similar": [{"title": "X", "tmdb_id": 5, "kind": "show"}]}, @@ -643,7 +643,7 @@ def test_item_extras_caches_tmdb_call(db, monkeypatch): calls = [] class Tmdb: enabled = True - def extras(self, kind, tid): calls.append(tid); return {"keywords": ["x"]} + def extras(self, kind, tid, region="US"): calls.append(tid); return {"keywords": ["x"]} eng = VideoEnrichmentEngine(db, {"tmdb": Tmdb()}) a = eng.item_extras("movie", mid) b = eng.item_extras("movie", mid) diff --git a/webui/index.html b/webui/index.html index 76b458e2..db72e38f 100644 --- a/webui/index.html +++ b/webui/index.html @@ -5205,6 +5205,30 @@