Fix movetv epg

This commit is contained in:
Nirvana
2026-04-04 17:18:02 +02:00
parent 4ca5394f1e
commit 5dbb7ee3cc
@@ -31,7 +31,41 @@ from .constants import MoveTVConfig
class MoveTvEpgManager:
"""Fetches and normalises EPG data from the MoveTV API."""
"""
Fetches and normalises EPG data from the MoveTV API.
Returns programme objects with the following fields:
Core identifiers:
epg_id, schedule_id, live_id, live_name, content_id
Title & Description:
title (cleaned), original_title, plot, episode_title
Episode Info (parsed from title):
season_num, episode_num, has_episode_info
Time:
start, end (ISO-8601 UTC strings)
start_ms, end_ms (millisecond timestamps)
Genre & Categories:
genre (name), genre_id (numeric)
categories (list of names)
category_ids (list of numeric IDs)
category_images (list of icon URLs)
Credits:
director, cast (list), producer
Metadata:
year, rating
Images:
thumbnail (background fanart) - legacy
images dict containing any of:
background, icon, poster, square_logo, poster_mark, original_title_logo
"""
def __init__(self, authenticator: Any) -> None:
"""
@@ -338,38 +372,71 @@ class MoveTvEpgManager:
"""
Normalise raw API programme objects into a consistent internal format.
Field mapping
-------------
title ← item["title"]
plot ← item["epgDesc"]
start ← item["start"] (ms epoch → ISO-8601 UTC)
end ← item["end"] (ms epoch → ISO-8601 UTC)
start_ms ← item["start"] (raw ms, kept for easy sorting / math)
end_ms ← item["end"] (raw ms)
genre ← item["tagInfo"]["name"] (e.g. "SERIJA", "INFO")
categories ← list of category name strings
thumbnail ← item["picture"]["background"] (absolute URL)
director ← item["director"]
cast ← item["actor"] split on ","
year ← item["year"]
rating ← item["rating"] (parental-advisory integer)
epg_id ← item["epgId"]
schedule_id ← item["scheduleId"]
live_id ← item["liveId"]
live_name ← item["liveName"]
Field mapping (enhanced)
------------------------
Basic info:
epg_id, schedule_id, live_id, live_name, content_id
title, original_title, plot
Time:
start, end (ISO-8601 UTC)
start_ms, end_ms (raw milliseconds)
Episode (parsed from title):
season_num, episode_num
episode_title (title without season/episode suffix)
Genre & Categories:
genre (name from tagInfo)
genre_id (numeric from tagInfo)
categories (list of category names)
category_ids (list of category IDs)
category_images (list of category icon URLs)
Credits:
director, cast (list), producer
Metadata:
year, rating
Images (all available):
thumbnail (background) - primary for backward compatibility
images dict containing:
- background (wide fanart)
- icon (small channel/program icon)
- poster (movie/show poster)
- square_logo (square logo)
- poster_mark (promotional image)
- original_title_logo (title logo)
"""
import re
parsed: List[Dict[str, Any]] = []
for item in items:
start_ms: Optional[int] = item.get("start")
end_ms: Optional[int] = item.get("end")
end_ms: Optional[int] = item.get("end")
categories: List[str] = [
cat["name"]
for cat in item.get("categories", [])
if cat.get("name")
]
# ------------------------------------------------------------
# Parse categories with their IDs and images
# ------------------------------------------------------------
categories = []
category_ids = []
category_images = []
for cat in item.get("categories", []):
if cat.get("name"):
categories.append(cat["name"])
if cat.get("categoryId"):
category_ids.append(cat["categoryId"])
if cat.get("picture", {}).get("icon"):
category_images.append(
MoveTVConfig.build_image_url(cat["picture"]["icon"])
)
# ------------------------------------------------------------
# Parse cast (split by comma)
# ------------------------------------------------------------
actor_raw: Optional[str] = item.get("actor")
cast: List[str] = (
[a.strip() for a in actor_raw.split(",") if a.strip()]
@@ -377,28 +444,109 @@ class MoveTvEpgManager:
else []
)
parsed.append(
{
"epg_id": item.get("epgId"),
"schedule_id": item.get("scheduleId"),
"live_id": item.get("liveId"),
"live_name": item.get("liveName"),
"title": item.get("title"),
"plot": item.get("epgDesc"),
"start": self._ms_to_utc_str(start_ms),
"end": self._ms_to_utc_str(end_ms),
"start_ms": start_ms,
"end_ms": end_ms,
"genre": item.get("tagInfo", {}).get("name"),
"categories": categories,
"rating": item.get("rating", 0),
"year": item.get("year"),
"director": item.get("director"),
"cast": cast,
"thumbnail": MoveTVConfig.build_image_url(
item.get("picture", {}).get("background")
),
}
)
# ------------------------------------------------------------
# Parse season and episode from title
# Supports multiple patterns:
# - "Title S6:E2" (most common)
# - "Title S6E2"
# - "Title S6 E2"
# - "Title (S6, E2)"
# - "Title - Season 6 Episode 2"
# ------------------------------------------------------------
raw_title = item.get("title", "")
title = raw_title
season_num = None
episode_num = None
# Pattern 1: S6:E2 (MoveTV format)
match = re.search(r'S(\d+):E(\d+)', title, re.IGNORECASE)
if not match:
# Pattern 2: S6E2
match = re.search(r'S(\d+)E(\d+)', title, re.IGNORECASE)
if not match:
# Pattern 3: S6 E2
match = re.search(r'S(\d+)\s+E(\d+)', title, re.IGNORECASE)
if not match:
# Pattern 4: (S6, E2) or (S6 E2)
match = re.search(r'\(S(\d+)[,\s]+E(\d+)\)', title, re.IGNORECASE)
if not match:
# Pattern 5: Season 6 Episode 2
match = re.search(r'Season\s+(\d+)\s+Episode\s+(\d+)', title, re.IGNORECASE)
if match:
season_num = int(match.group(1))
episode_num = int(match.group(2))
# Remove season/episode from title for cleaner display
title = re.sub(r'\s*S\d+:[Ee]\d+\s*', '', title)
title = re.sub(r'\s*S\d+[Ee]\d+\s*', '', title)
title = re.sub(r'\s*\(S\d+[,\s]+E\d+\)\s*', '', title)
title = re.sub(r'\s*Season\s+\d+\s+Episode\s+\d+\s*', '', title)
title = title.strip()
# ------------------------------------------------------------
# Collect all available images
# ------------------------------------------------------------
picture = item.get("picture", {})
images = {
"background": MoveTVConfig.build_image_url(picture.get("background")),
"icon": MoveTVConfig.build_image_url(picture.get("icon")),
"poster": MoveTVConfig.build_image_url(picture.get("poster")),
"square_logo": MoveTVConfig.build_image_url(picture.get("squareLogo")),
"poster_mark": MoveTVConfig.build_image_url(picture.get("posterMark")),
"original_title_logo": MoveTVConfig.build_image_url(
picture.get("originalTitleLogo")
),
}
# Remove None values
images = {k: v for k, v in images.items() if v}
# ------------------------------------------------------------
# Build the programme object
# ------------------------------------------------------------
parsed.append({
# Core identifiers
"epg_id": item.get("epgId"),
"schedule_id": item.get("scheduleId"),
"live_id": item.get("liveId"),
"live_name": item.get("liveName"),
"content_id": item.get("contentId"),
# Title and description
"title": title,
"original_title": item.get("originalTitle"),
"plot": item.get("epgDesc"),
"episode_title": raw_title if season_num else None, # Original with S/E
# Episode information (CRITICAL for PVR)
"season_num": season_num,
"episode_num": episode_num,
"has_episode_info": season_num is not None,
# Time information
"start": self._ms_to_utc_str(start_ms),
"end": self._ms_to_utc_str(end_ms),
"start_ms": start_ms,
"end_ms": end_ms,
# Genre and categories (enhanced)
"genre": item.get("tagInfo", {}).get("name"),
"genre_id": item.get("tagInfo", {}).get("tagId"),
"categories": categories,
"category_ids": category_ids,
"category_images": category_images if category_images else None,
# Rating and year
"rating": item.get("rating", 0),
"year": item.get("year"),
# Credits
"director": item.get("director"),
"cast": cast,
"producer": item.get("producer"),
# Images (enhanced)
"thumbnail": images.get("background"), # Keep for backward compatibility
"images": images if images else None,
})
return parsed