From c82cd6fb8eb2d92eeab58cd88714a5700d533736 Mon Sep 17 00:00:00 2001 From: Nirvana Date: Thu, 18 Jun 2026 12:00:29 +0200 Subject: [PATCH] restructure epg --- .../base/models/epg_models.py | 21 ++++-- .../providers/magentaeu/epg_manager.py | 65 ++++++++----------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/lib/streaming_providers/base/models/epg_models.py b/lib/streaming_providers/base/models/epg_models.py index afd79b4..15349f9 100644 --- a/lib/streaming_providers/base/models/epg_models.py +++ b/lib/streaming_providers/base/models/epg_models.py @@ -138,6 +138,8 @@ class EPGEntry: end: int """End time as Unix timestamp (seconds since epoch).""" + program_id: Optional[str] = None + # Optional fields - Program Information description: Optional[str] = None """Full program description/plot. C++ expects 'description' key.""" @@ -238,11 +240,16 @@ class EPGEntry: def to_dict(self) -> dict: """ - Convert EPGEntry to dictionary format expected by C++ frontend. - Only includes non-None values to minimize data transfer. + Convert EPGEntry to dictionary format for frontend consumption. - Returns: - Dictionary with EPG data + This format is used by: + - Kodi PVR frontend (C++) + - Web UI / API endpoints + - Other frontend clients + + Note: The dict includes both broadcast_id (for Kodi) and program_id + (for provider-specific operations). Frontends that only need the + integer ID can ignore program_id. """ result = { "broadcast_id": self.broadcast_id, @@ -251,7 +258,11 @@ class EPGEntry: "end": self.end, } - # Add optional fields only if they have values + # Include program_id if available (multi-platform support) + if self.program_id is not None: + result["program_id"] = self.program_id + + # Add all optional fields optional_fields = [ "description", "plot_outline", diff --git a/lib/streaming_providers/providers/magentaeu/epg_manager.py b/lib/streaming_providers/providers/magentaeu/epg_manager.py index 74e9fb4..2fa2fcb 100644 --- a/lib/streaming_providers/providers/magentaeu/epg_manager.py +++ b/lib/streaming_providers/providers/magentaeu/epg_manager.py @@ -549,17 +549,6 @@ class MagentaEUEpgManager: ) -> Optional[EPGEntry]: """ Convert a single bifrost schedule item into an EPGEntry object. - - If fetch_details=True, programme details (description, credits, images) - are fetched and merged into the EPGEntry. Otherwise, only schedule data - is used (faster, fewer API calls). - - Args: - item: Raw schedule item from bifrost API - channel_id: Channel identifier (station ID) - - Returns: - EPGEntry object, or None if start/end cannot be parsed """ # Parse time range - required fields start = self._parse_timestamp(item.get("start_time")) @@ -567,8 +556,29 @@ class MagentaEUEpgManager: if start is None or end is None or end <= start: return None + # Get the original program_id from the API program_id = item.get("program_id") + # Generate broadcast_id (integer for Kodi) + if program_id: + try: + # If program_id is numeric, use it directly + broadcast_id = int(program_id) + except (ValueError, TypeError): + # Otherwise encode deterministically with provider info + broadcast_id = EPGEntry.encode_broadcast_id( + self._country, # provider name + channel_id, # channel ID + start # start timestamp + ) + else: + # No program_id - encode from channel + start + broadcast_id = EPGEntry.encode_broadcast_id( + self._country, + channel_id, + start + ) + # Fetch programme details if enabled details = {} credit_map = { @@ -598,8 +608,7 @@ class MagentaEUEpgManager: except (ValueError, TypeError): year = None - # Season / episode — the API returns strings, and Gracenote encodes - # "no real season" as a large placeholder (e.g. "20230000", "39170000") + # Season / episode season_number = self._parse_episode_number( item.get("season_number"), max_valid=9998 ) @@ -607,37 +616,17 @@ class MagentaEUEpgManager: item.get("episode_number"), max_valid=None ) - # Use program_id as broadcast_id where possible. - # The bifrost API always returns program_id as a string; EPGEntry - # requires an int (EPGEntry.__post_init__ compares broadcast_id - # against EPG_TAG_INVALID_UID with <=, which raises TypeError if - # broadcast_id is still a str). Coerce numeric IDs to int and fall - # back to a hash-derived id for missing/non-numeric program_ids. - broadcast_id: Optional[int] = None - if program_id: - try: - broadcast_id = int(program_id) - except (ValueError, TypeError): - logger.debug( - f"[MagentaEUEpgManager/{self._country}] " - f"non-numeric program_id {program_id!r} for channel " - f"{channel_id} — falling back to hash-derived broadcast_id" - ) - if broadcast_id is None: - broadcast_id = hash(f"{channel_id}_{start}") - # EPGEntry requires broadcast_id > EPG_TAG_INVALID_UID (0); Python's - # hash() can return negative or zero values, so fold into a - # strictly-positive range. - broadcast_id = (broadcast_id % 2_147_483_647) + 1 - - # Build EPGEntry with all available data + # Build EPGEntry with both IDs return EPGEntry( # Required fields - broadcast_id=broadcast_id, + broadcast_id=broadcast_id, # Integer for Kodi title=item.get("description") or item.get("title") or "Unknown", start=start, end=end, + # API identifier (stored but not sent to Kodi) + program_id=program_id, # Original string from API + # Optional fields - Programme Information description=(details.get("details") or {}).get("description") if details else None, plot_outline=None, # Not available from bifrost API