movetv fixes

This commit is contained in:
Nirvana
2026-07-06 14:04:18 +02:00
parent f543e403de
commit 2bf26e5e65
2 changed files with 64 additions and 17 deletions
@@ -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
@@ -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: