summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/src/meshbay_node/ops.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-24 14:33:38 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-24 14:33:38 +0200
commit317f09328ed8bf20148b707470c9b0fe82e59575 (patch)
treeba8daf5dcf050d44b7b0e766babbfda8fadac59f /packages/meshbay-node/src/meshbay_node/ops.py
parentb6e2dcea65124673da047f9b3c92bc5e25980d63 (diff)
parent0b0da86f1f9d6f0b1a27b5e1e1658c42de9f356a (diff)
downloadmeshbay-317f09328ed8bf20148b707470c9b0fe82e59575.tar.gz
Merge branch 'docs/mediacenter-videos-app': Videos group app
Poster grid / flat list browsing, TMDB metadata enrichment, thumbnail generation and caching, season-specific overviews, manual match correction, and the create-group wizard's app-selection step. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LAmyXtc6dAADsH23ydXQpY
Diffstat (limited to 'packages/meshbay-node/src/meshbay_node/ops.py')
-rw-r--r--packages/meshbay-node/src/meshbay_node/ops.py62
1 files changed, 62 insertions, 0 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/ops.py b/packages/meshbay-node/src/meshbay_node/ops.py
index 154813d..6848e3a 100644
--- a/packages/meshbay-node/src/meshbay_node/ops.py
+++ b/packages/meshbay-node/src/meshbay_node/ops.py
@@ -731,6 +731,68 @@ async def set_enabled_apps(state: dict, group_id: str, apps: list[str]) -> dict:
return {"apps": apps, "group_id": group_id}
+# ── TMDB config (Videos app) ─────────────────────────────────────────────────
+
+async def set_tmdb_config(state: dict, enabled: bool, token: str | None = None,
+ language: str | None = None) -> dict:
+ """
+ Whether the node calls TMDB at all, whether it uses a custom API token
+ instead of the shipped default, and in what language it queries TMDB
+ (docs/mediacenter.md §5.5).
+
+ Node-wide (roster.py group_settings, group_id="") rather than per-group
+ like set_member_upload/set_enabled_apps: TMDB is one operator's budget,
+ one credential and one shared cache, not a per-group or per-viewer
+ concern. `token=""` explicitly clears a previously-set custom token
+ (reverts to the shipped default); `token=None` leaves whatever was
+ there unchanged. Same discipline for `language`.
+ """
+ roster = _roster(state)
+ await roster.set_tmdb_config(enabled, token, language, set_by=state.get("node_user_id", ""))
+ state["tmdb_enabled"] = enabled
+ # `token=None` means "leave whatever was there" (§ set_tmdb_config's own
+ # docstring) — so the customized flag only changes when a value (a real
+ # token, or "" to clear one) was actually given.
+ if token is not None:
+ state["tmdb_token_customized"] = bool(token)
+ if language is not None:
+ state["tmdb_language"] = language
+ log.info("TMDB config: enabled=%s custom_token=%s language=%s",
+ enabled, bool(token), language or state.get("tmdb_language", ""))
+ return {
+ "enabled": enabled,
+ "token_customized": state.get("tmdb_token_customized", False),
+ "language": state.get("tmdb_language", ""),
+ }
+
+
+async def set_video_root(state: dict, group_id: str, path: str) -> dict:
+ """
+ Which folder (possibly a subfolder of a shared root) is the Videos app's
+ entry point for this group. Same shape as set_enabled_apps: lives on the
+ node (roster.db), takes effect without a restart, signed by the operator.
+ `path=""` clears it — the Videos tab then asks for one to be chosen
+ before anything (including TMDB enrichment, docs/mediacenter.md §5.2)
+ runs, rather than defaulting to the whole shared index.
+
+ A non-empty path fires (never awaits) a sweep of whatever that folder
+ already contains: the ordinary per-change enrichment path only ever
+ looks at files new since the last broadcast, so anything already sitting
+ in a folder before it became the video_root would otherwise never be
+ picked up.
+ """
+ roster = _roster(state)
+ ctx = _group_ctx(state, group_id)
+ await roster.set_video_root(group_id, path, set_by=state.get("node_user_id", ""))
+ ctx["video_root"] = path
+ log.info("Videos root for group %s: %r", group_id[:8], path)
+ if path:
+ enrich_fn = state.get("enrich_video_root_fn")
+ if enrich_fn:
+ asyncio.ensure_future(enrich_fn(group_id))
+ return {"path": path, "group_id": group_id}
+
+
# ── Scan settings ────────────────────────────────────────────────────────────
async def set_scan_settings(state: dict, group_id: str, reconcile_interval_secs: float,