From 61ab2e292bb9b158c39c4ebaa61e58cf3bbebedf Mon Sep 17 00:00:00 2001 From: Nirvana Date: Wed, 4 Mar 2026 11:41:00 +0100 Subject: [PATCH] Add discovery --- .../base/vod_operations.py | 11 +++ routes/vod.py | 84 +++++++++++++++---- 2 files changed, 81 insertions(+), 14 deletions(-) diff --git a/lib/streaming_providers/base/vod_operations.py b/lib/streaming_providers/base/vod_operations.py index 716fe26..5020cf6 100644 --- a/lib/streaming_providers/base/vod_operations.py +++ b/lib/streaming_providers/base/vod_operations.py @@ -128,6 +128,17 @@ class VodOperations: ) return manifest_url + def get_vod_drm_configs( + self, provider_name: str, vod_id: str, **kwargs + ): + """ + Get DRM configs for a specific VOD item. + + Delegates directly to provider.get_drm(content_id). + """ + provider = self._get_provider(provider_name) + return provider.get_drm(content_id=vod_id, **kwargs) + def get_all_vod_roots(self) -> Dict[str, List[Union[VodCategory, VodItem]]]: """ Get root VOD entries from all enabled providers that implement VOD. diff --git a/routes/vod.py b/routes/vod.py index 66dc63e..37cc621 100644 --- a/routes/vod.py +++ b/routes/vod.py @@ -12,19 +12,18 @@ GET /api/providers//vod/ Navigates the VOD tree by URL-safe name slugs. e.g. /api/providers/myprovider/vod/sports/golf/pga/tournament_x - Response: - { - "provider": "myprovider", - "path": "sports/golf/pga", - "entries": [ - {"type": "vod_category", "id": "...", "name": "...", "slug": "..."}, - {"type": "vod", "id": "...", "name": "...", "slug": "..."} - ], - "count": 12 - } +Stream/manifest/DRM endpoints +------------------------------ +GET /api/providers//vod//manifest + Returns the manifest URL for the VOD item whose id is the last + segment of . + e.g. /api/providers/discovery_de/vod/sports/nordic-combined/6bbea4ab-.../manifest -Stream/manifest/DRM endpoints for VodItem are registered in streams.py -following the identical pattern used for channels and events. +GET /api/providers//vod//drm + Returns DRM configs for the VOD item whose id is the last segment of . + +Route registration order matters: the explicit /manifest and /drm routes are +registered BEFORE the generic route so Bottle matches them first. """ from bottle import response @@ -36,10 +35,14 @@ def setup_vod_routes(app, manager): def _serialize(entries) -> list: return [e.to_dict() for e in entries] + # ------------------------------------------------------------------ + # Root + # ------------------------------------------------------------------ + @app.route("/api/providers//vod", method="GET") def get_vod_root(provider): try: - entries = manager.vod_ops.get_vod_node(provider_name=provider, slug_segments=[]) + entries = manager.get_vod_node(provider_name=provider, slug_segments=[]) except ValueError as e: response.status = 404 return {"error": "Provider not found", "message": str(e), "provider": provider} @@ -51,13 +54,66 @@ def setup_vod_routes(app, manager): response.status = 200 return {"provider": provider, "path": "", "entries": serialized, "count": len(serialized)} + # ------------------------------------------------------------------ + # Manifest & DRM — must be registered BEFORE the generic path route + # so Bottle matches /manifest and /drm suffixes here first. + # captures everything before the suffix, e.g. + # "sports/nordic-combined/6bbea4ab-735c-469b-89d2-2a38c195ce07" + # The last segment of that path is the vod_id (edit_id / content_id). + # ------------------------------------------------------------------ + + @app.route("/api/providers//vod//manifest", method="GET") + def get_vod_item_manifest(provider, path): + segments = [s for s in path.split("/") if s] + if not segments: + response.status = 400 + return {"error": "Missing VOD item id", "provider": provider} + vod_id = segments[-1] + try: + manifest_url = manager.get_vod_manifest(provider_name=provider, vod_id=vod_id) + except ValueError as e: + response.status = 404 + return {"error": "Not found", "message": str(e), "provider": provider, "vod_id": vod_id} + except Exception as e: + logger.error(f"Failed to get VOD manifest: {e}") + response.status = 500 + return {"error": "Failed to get manifest", "message": str(e), "provider": provider, "vod_id": vod_id} + if not manifest_url: + response.status = 404 + return {"error": "No manifest available", "provider": provider, "vod_id": vod_id} + response.status = 200 + return {"provider": provider, "vod_id": vod_id, "manifest_url": manifest_url} + + @app.route("/api/providers//vod//drm", method="GET") + def get_vod_item_drm(provider, path): + segments = [s for s in path.split("/") if s] + if not segments: + response.status = 400 + return {"error": "Missing VOD item id", "provider": provider} + vod_id = segments[-1] + try: + drm_configs = manager.get_vod_drm_configs(provider_name=provider, vod_id=vod_id) + except ValueError as e: + response.status = 404 + return {"error": "Not found", "message": str(e), "provider": provider, "vod_id": vod_id} + except Exception as e: + logger.error(f"Failed to get VOD DRM configs: {e}") + response.status = 500 + return {"error": "Failed to get DRM configs", "message": str(e), "provider": provider, "vod_id": vod_id} + response.status = 200 + return {"provider": provider, "vod_id": vod_id, "drm": [d.to_dict() for d in (drm_configs or [])]} + + # ------------------------------------------------------------------ + # Generic browse — registered last so /manifest and /drm win above + # ------------------------------------------------------------------ + @app.route("/api/providers//vod/", method="GET") def get_vod_path(provider, path): slug_segments = [s for s in path.split("/") if s] if not slug_segments: return get_vod_root(provider) try: - entries = manager.vod_ops.get_vod_node(provider_name=provider, slug_segments=slug_segments) + entries = manager.get_vod_node(provider_name=provider, slug_segments=slug_segments) except ValueError as e: response.status = 404 return {"error": "Not found", "message": str(e), "provider": provider, "path": path}