From 4f4e15e67f47be19127553b3bf24db2468dfab2c Mon Sep 17 00:00:00 2001 From: Nirvana Date: Wed, 17 Jun 2026 16:39:06 +0200 Subject: [PATCH] restructure epg --- .../base/models/epg_models.py | 59 ++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/lib/streaming_providers/base/models/epg_models.py b/lib/streaming_providers/base/models/epg_models.py index 6decd97..49fabd9 100644 --- a/lib/streaming_providers/base/models/epg_models.py +++ b/lib/streaming_providers/base/models/epg_models.py @@ -8,7 +8,7 @@ Based on Kodi PVR EPG Tag specification (ETSI EN 300 468 DVB-SI standard) from dataclasses import dataclass from datetime import datetime from enum import IntEnum -from typing import List, Optional +from typing import List, Optional, Union # EPG Constants from Kodi PVR specification EPG_TAG_INVALID_UID = 0 @@ -24,6 +24,61 @@ EPG_STRING_TOKEN_SEPARATOR = "," """Separator for multiple values in string fields (cast, directors, writers).""" +def _coerce_timestamp(value: Union[int, str, None], field_name: str) -> Optional[int]: + """ + Coerce a raw timestamp value (int, numeric string, or ISO-8601 string) + into a Unix timestamp (int seconds). + + This is the single source of truth for timestamp parsing used by + EPGEntry.__post_init__. Upstream providers are inconsistent about + whether they send epoch ints, epoch strings, or ISO-8601 datetimes, + so all three forms are accepted here. + + Args: + value: Raw timestamp value to coerce. + field_name: Name of the field being coerced, used only for error + messages (e.g. "start", "end"). + + Returns: + Unix timestamp as int, or None if value was None. + + Raises: + ValueError: If value is a non-empty string/other type that cannot + be parsed as either a numeric epoch or an ISO-8601 datetime. + """ + if value is None: + return None + + # Already an int (bools are ints too, but that's not a realistic + # input here so no special-casing). + if isinstance(value, int): + return value + + if isinstance(value, str): + stripped = value.strip() + if not stripped: + return None + + # Numeric epoch string, e.g. "1750000000" or "-1" + if stripped.lstrip("-").isdigit(): + return int(stripped) + + # ISO-8601 datetime string, e.g. "2026-06-17T16:28:00Z" + try: + dt = datetime.fromisoformat(stripped.replace("Z", "+00:00")) + return int(dt.timestamp()) + except (ValueError, AttributeError) as e: + raise ValueError( + f"{field_name} could not be parsed as a timestamp: " + f"{value!r} ({e})" + ) from e + + raise ValueError( + f"{field_name} must be an int, numeric string, or ISO-8601 string, " + f"got {type(value)}: {value!r}" + ) + + class EPGEventState(IntEnum): """ EPG event states for event lifecycle callbacks. @@ -824,4 +879,4 @@ __all__ = [ "EPGFlags", "EPGGenre", "EPGGenreSubtype", -] +] \ No newline at end of file