From 2bf26e5e6501cf0bc067d071dfd67d6deba7f8ad Mon Sep 17 00:00:00 2001 From: Nirvana Date: Mon, 6 Jul 2026 14:04:18 +0200 Subject: [PATCH] movetv fixes --- .../base/models/epg_models.py | 37 +++++++++++++--- .../providers/movetv/epg_manager.py | 44 ++++++++++++++----- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/lib/streaming_providers/base/models/epg_models.py b/lib/streaming_providers/base/models/epg_models.py index 4dbdfed..d3130fa 100644 --- a/lib/streaming_providers/base/models/epg_models.py +++ b/lib/streaming_providers/base/models/epg_models.py @@ -170,6 +170,16 @@ class EPGEntry: writers: Optional[List[str]] = None """List of writer names. C++ expects 'writers' array.""" + producers: Optional[List[str]] = None + """ + List of producer names. Note: unlike cast/directors/writers, Kodi's + PVREPGTag C++ interface has no dedicated producer slot, so this field + is not rendered by the Kodi frontend today. It is still captured here + for JSON/API consumers and providers (e.g. MoveTV's channel-based EPG + endpoint) that return producer data as part of a single one-shot + fetch with no separate enrichment pass to fall back on. + """ + # Optional fields - Genre/Category genre: Optional[int] = None """ @@ -191,6 +201,14 @@ class EPGEntry: Used when genre=EPGGenre.USE_STRING or for custom genres not in DVB-SI standard. """ + genres: Optional[List[str]] = None + """ + List of text genre/category labels (e.g. ["Komedija", "Drama", "Romansa"]), + distinct from the single numeric DVB-SI `genre` field and from the single + `genre_description` string above. Named to match EPGProgramDetails.genres + so the two line up if this ever gets pulled into a shared merge field. + """ + # Optional fields - Episode Information season_number: Optional[int] = None """Season/series number (1-based). C++ expects 'season_number' key.""" @@ -278,8 +296,10 @@ class EPGEntry: "cast", "directors", "writers", + "producers", "genre", "genre_description", + "genres", "season_number", "episode_number", "episode_part_number", @@ -803,12 +823,17 @@ def merge_content(entry: "EPGEntry", details: "EPGProgramDetails") -> "EPGEntry" this title) will not clobber a value already present on `entry`. Fields that exist on EPGProgramDetails but NOT in EPGContent (e.g. - provider_vod_id, genres, backdrop/poster, producers/presenter/ - composers/contributors, the *_details enriched-person fields) are - intentionally NOT copied here - EPGEntry has no matching field for - them. Callers that need that richer data should keep the - EPGProgramDetails instance itself rather than expecting it to appear - on the merged EPGEntry. + provider_vod_id, genres, backdrop/poster, presenter/composers/ + contributors, the *_details enriched-person fields) are intentionally + NOT copied here - EPGEntry has no matching field for them. Callers + that need that richer data should keep the EPGProgramDetails instance + itself rather than expecting it to appear on the merged EPGEntry. + + Note: EPGEntry.producers is the one exception - it exists directly on + EPGEntry (not via EPGContent/merge) for providers like MoveTV that + return producer data in a single one-shot grid fetch with no separate + detail-fetch step to enrich later. It is set at parse time, not + merged in here. A new EPGEntry is returned (rather than mutating in place) because EPGEntry is not frozen, but merge_content should behave predictably diff --git a/lib/streaming_providers/providers/movetv/epg_manager.py b/lib/streaming_providers/providers/movetv/epg_manager.py index 5c69a22..743a804 100644 --- a/lib/streaming_providers/providers/movetv/epg_manager.py +++ b/lib/streaming_providers/providers/movetv/epg_manager.py @@ -44,27 +44,34 @@ class MoveTvEpgManager: so the provider can still be recovered from the broadcast_id alone for catchup lookups) title (cleaned) -> title - title (raw, S/E) -> episode_name (only when season/episode parsed) + (raw title's S/E marker is now fully captured by season_number/ + episode_number below, so episode_name is left None — MoveTV + doesn't provide a distinct episode subtitle) originalTitle -> original_title epgDesc -> description start / end (ms) -> start / end (seconds) tagInfo.name -> genre_description + categories[].name -> genres (list) director -> directors (single-item list) actor -> cast (list, comma-split) + producer -> producers (list, comma-split) year -> year - rating -> star_rating + rating -> parental_rating (age/content classification, + e.g. 12, 0=unrestricted — not a 0-10 star score) picture.background-> icon season/episode -> season_number / episode_number (+ IS_SERIES flag) Known lossy fields ------------------ EPGEntry has no slots for: schedule_id, live_id, live_name, content_id, - producer, multiple categories/category_ids/category_images, genre_id, - or the secondary images (poster, square_logo, poster_mark, - original_title_logo). These were present in the old dict-based return - value and are now dropped. Confirm nothing else in the MoveTV pipeline - (e.g. catchup/manifest matching) reads those keys before relying on - this contract. + category_ids, category_images/genre_id (per-category numeric IDs and + their picture objects — always null in observed data), or the + secondary images (poster, square_logo, poster_mark, + original_title_logo) — confirmed always null in observed MoveTV + responses, so not wired up. These were present in the old dict-based + return value and are now dropped. Confirm nothing else in the MoveTV + pipeline (e.g. catchup/manifest matching) reads those keys before + relying on this contract. """ def __init__(self, authenticator: Any) -> None: @@ -374,8 +381,9 @@ class MoveTvEpgManager: # Native backwards/forwards — anchor to now return datetime.now(tz=timezone.utc), backwards, forwards + @staticmethod def _parse_items( - self, items: List[Dict[str, Any]], channel_id: str + items: List[Dict[str, Any]], channel_id: str ) -> List[EPGEntry]: """ Normalise raw API programme objects into EPGEntry objects. @@ -414,6 +422,18 @@ class MoveTvEpgManager: else [] ) + producer_raw: Optional[str] = item.get("producer") + producers: List[str] = ( + [p.strip() for p in producer_raw.split(",") if p.strip()] + if producer_raw + else [] + ) + + categories_raw: List[Dict[str, Any]] = item.get("categories") or [] + genres: List[str] = [ + c["name"] for c in categories_raw if c.get("name") + ] + # ------------------------------------------------------------ # Parse season and episode from title. # Supports multiple patterns: @@ -480,16 +500,18 @@ class MoveTvEpgManager: end=end_s, program_id=str(epg_id_raw) if epg_id_raw else None, description=item.get("epgDesc"), - episode_name=raw_title if season_num else None, + episode_name=None, original_title=item.get("originalTitle"), year=item.get("year"), icon=icon, cast=cast, directors=[item["director"]] if item.get("director") else [], + producers=producers, genre_description=(item.get("tagInfo") or {}).get("name"), + genres=genres, season_number=season_num, episode_number=episode_num, - star_rating=item.get("rating") or None, + parental_rating=item.get("rating") or None, ) if season_num is not None: