mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-10-04 06:52:34 +02:00
Improve EPG
This commit is contained in:
@@ -311,4 +311,4 @@ class EPGManager:
|
||||
Returns:
|
||||
True if mapping exists
|
||||
"""
|
||||
return self.mapping.has_mapping(provider_name, channel_id)
|
||||
return self.mapping.has_mapping(provider_name, channel_id)
|
||||
|
||||
@@ -2,12 +2,36 @@
|
||||
# streaming_providers/base/models/epg_models.py
|
||||
"""
|
||||
EPG Models - Data classes for Electronic Program Guide entries
|
||||
Based on Kodi PVR EPG Tag specification
|
||||
Based on Kodi PVR EPG Tag specification (ETSI EN 300 468 DVB-SI standard)
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
from enum import IntEnum
|
||||
|
||||
# EPG Constants from Kodi PVR specification
|
||||
EPG_TAG_INVALID_UID = 0
|
||||
"""Special broadcast ID value to indicate invalid/unset EPG event UID."""
|
||||
|
||||
EPG_TAG_INVALID_SERIES_EPISODE = -1
|
||||
"""Special value for series/episode/part numbers to indicate not applicable."""
|
||||
|
||||
EPG_TIMEFRAME_UNLIMITED = -1
|
||||
"""Special timeframe value to indicate no time restrictions."""
|
||||
|
||||
EPG_STRING_TOKEN_SEPARATOR = ","
|
||||
"""Separator for multiple values in string fields (cast, directors, writers)."""
|
||||
|
||||
|
||||
class EPGEventState(IntEnum):
|
||||
"""
|
||||
EPG event states for event lifecycle callbacks.
|
||||
Used with EpgEventStateChange() callback in C++ PVR client.
|
||||
"""
|
||||
CREATED = 0 # Event created
|
||||
UPDATED = 1 # Event updated
|
||||
DELETED = 2 # Event deleted
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -119,8 +143,18 @@ class EPGEntry:
|
||||
flags: Optional[int] = None
|
||||
"""
|
||||
Bit field flags for EPG entry properties.
|
||||
Common flags: IS_SERIES=0x01, IS_NEW=0x02, IS_PREMIERE=0x04,
|
||||
IS_FINALE=0x08, IS_LIVE=0x10
|
||||
Combine flags using bitwise OR: EPGFlags.IS_SERIES | EPGFlags.IS_NEW
|
||||
|
||||
Available flags:
|
||||
- EPGFlags.UNDEFINED (0x00): Nothing special
|
||||
- EPGFlags.IS_SERIES (0x01): Part of a series
|
||||
- EPGFlags.IS_NEW (0x02): New episode/content
|
||||
- EPGFlags.IS_PREMIERE (0x04): Premiere episode
|
||||
- EPGFlags.IS_FINALE (0x08): Finale episode
|
||||
- EPGFlags.IS_LIVE (0x10): Live broadcast
|
||||
|
||||
Example:
|
||||
flags = EPGFlags.IS_SERIES | EPGFlags.IS_NEW
|
||||
"""
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
@@ -223,10 +257,121 @@ class EPGEntry:
|
||||
# and doesn't start after range ends
|
||||
return self.end > start_time and self.start < end_time
|
||||
|
||||
def has_flag(self, flag: int) -> bool:
|
||||
"""
|
||||
Check if a specific flag is set.
|
||||
|
||||
Args:
|
||||
flag: Flag to check (use EPGFlags constants)
|
||||
|
||||
Returns:
|
||||
True if flag is set
|
||||
|
||||
Example:
|
||||
if entry.has_flag(EPGFlags.IS_LIVE):
|
||||
print("Live broadcast")
|
||||
"""
|
||||
if self.flags is None:
|
||||
return False
|
||||
return EPGFlags.has_flag(self.flags, flag)
|
||||
|
||||
def set_flag(self, flag: int) -> None:
|
||||
"""
|
||||
Set a specific flag (adds to existing flags).
|
||||
|
||||
Args:
|
||||
flag: Flag to set (use EPGFlags constants)
|
||||
|
||||
Example:
|
||||
entry.set_flag(EPGFlags.IS_NEW)
|
||||
"""
|
||||
if self.flags is None:
|
||||
self.flags = flag
|
||||
else:
|
||||
self.flags |= flag
|
||||
|
||||
def clear_flag(self, flag: int) -> None:
|
||||
"""
|
||||
Clear a specific flag (removes from existing flags).
|
||||
|
||||
Args:
|
||||
flag: Flag to clear (use EPGFlags constants)
|
||||
|
||||
Example:
|
||||
entry.clear_flag(EPGFlags.IS_NEW)
|
||||
"""
|
||||
if self.flags is not None:
|
||||
self.flags &= ~flag
|
||||
|
||||
@property
|
||||
def is_series(self) -> bool:
|
||||
"""Check if this entry is part of a series."""
|
||||
return self.has_flag(EPGFlags.IS_SERIES)
|
||||
|
||||
@property
|
||||
def is_new(self) -> bool:
|
||||
"""Check if this entry is flagged as new."""
|
||||
return self.has_flag(EPGFlags.IS_NEW)
|
||||
|
||||
@property
|
||||
def is_premiere(self) -> bool:
|
||||
"""Check if this entry is a premiere."""
|
||||
return self.has_flag(EPGFlags.IS_PREMIERE)
|
||||
|
||||
@property
|
||||
def is_finale(self) -> bool:
|
||||
"""Check if this entry is a finale."""
|
||||
return self.has_flag(EPGFlags.IS_FINALE)
|
||||
|
||||
@property
|
||||
def is_live(self) -> bool:
|
||||
"""Check if this entry is a live broadcast."""
|
||||
return self.has_flag(EPGFlags.IS_LIVE)
|
||||
|
||||
@staticmethod
|
||||
def join_string_list(items: List[str]) -> str:
|
||||
"""
|
||||
Join list of strings using EPG_STRING_TOKEN_SEPARATOR.
|
||||
Useful for cast, directors, writers fields when converting from lists.
|
||||
|
||||
Args:
|
||||
items: List of strings to join
|
||||
|
||||
Returns:
|
||||
Joined string using EPG separator
|
||||
|
||||
Example:
|
||||
cast_str = EPGEntry.join_string_list(["Actor 1", "Actor 2", "Actor 3"])
|
||||
# Returns: "Actor 1,Actor 2,Actor 3"
|
||||
"""
|
||||
return EPG_STRING_TOKEN_SEPARATOR.join(items)
|
||||
|
||||
@staticmethod
|
||||
def split_string_list(text: str) -> List[str]:
|
||||
"""
|
||||
Split string using EPG_STRING_TOKEN_SEPARATOR.
|
||||
Useful for parsing cast, directors, writers fields.
|
||||
|
||||
Args:
|
||||
text: String to split
|
||||
|
||||
Returns:
|
||||
List of strings
|
||||
|
||||
Example:
|
||||
cast = EPGEntry.split_string_list("Actor 1,Actor 2,Actor 3")
|
||||
# Returns: ["Actor 1", "Actor 2", "Actor 3"]
|
||||
"""
|
||||
if not text:
|
||||
return []
|
||||
return [item.strip() for item in text.split(EPG_STRING_TOKEN_SEPARATOR) if item.strip()]
|
||||
|
||||
def __post_init__(self):
|
||||
"""Validate required fields after initialization."""
|
||||
if self.broadcast_id <= 0:
|
||||
raise ValueError("broadcast_id must be positive")
|
||||
if self.broadcast_id <= EPG_TAG_INVALID_UID:
|
||||
raise ValueError(
|
||||
f"broadcast_id must be greater than EPG_TAG_INVALID_UID ({EPG_TAG_INVALID_UID})"
|
||||
)
|
||||
|
||||
if not self.title or not self.title.strip():
|
||||
raise ValueError("title is required and cannot be empty")
|
||||
@@ -237,15 +382,116 @@ class EPGEntry:
|
||||
if self.end <= self.start:
|
||||
raise ValueError("end time must be after start time")
|
||||
|
||||
# Validate episode numbers if set
|
||||
if self.season_number is not None and self.season_number < EPG_TAG_INVALID_SERIES_EPISODE:
|
||||
raise ValueError(
|
||||
f"season_number must be >= EPG_TAG_INVALID_SERIES_EPISODE ({EPG_TAG_INVALID_SERIES_EPISODE})"
|
||||
)
|
||||
|
||||
if self.episode_number is not None and self.episode_number < EPG_TAG_INVALID_SERIES_EPISODE:
|
||||
raise ValueError(
|
||||
f"episode_number must be >= EPG_TAG_INVALID_SERIES_EPISODE ({EPG_TAG_INVALID_SERIES_EPISODE})"
|
||||
)
|
||||
|
||||
if self.episode_part_number is not None and self.episode_part_number < EPG_TAG_INVALID_SERIES_EPISODE:
|
||||
raise ValueError(
|
||||
f"episode_part_number must be >= EPG_TAG_INVALID_SERIES_EPISODE ({EPG_TAG_INVALID_SERIES_EPISODE})"
|
||||
)
|
||||
|
||||
|
||||
# Constants matching C++ EPG_TAG_FLAG values
|
||||
class EPGFlags:
|
||||
"""Bit flags for EPG entry properties."""
|
||||
IS_SERIES = 0x01
|
||||
IS_NEW = 0x02
|
||||
IS_PREMIERE = 0x04
|
||||
IS_FINALE = 0x08
|
||||
IS_LIVE = 0x10
|
||||
"""
|
||||
Bit field flags for EPG entry properties.
|
||||
Based on Kodi's EPG_TAG_FLAG enum.
|
||||
|
||||
These can be combined using bitwise OR operator:
|
||||
flags = EPGFlags.IS_SERIES | EPGFlags.IS_NEW
|
||||
|
||||
Example:
|
||||
entry = EPGEntry(
|
||||
...,
|
||||
flags=EPGFlags.IS_SERIES | EPGFlags.IS_PREMIERE
|
||||
)
|
||||
"""
|
||||
UNDEFINED = 0x00 # 0000 0000 : Nothing special to say about this entry
|
||||
IS_SERIES = 0x01 # 0000 0001 : This EPG entry is part of a series
|
||||
IS_NEW = 0x02 # 0000 0010 : This EPG entry will be flagged as new
|
||||
IS_PREMIERE = 0x04 # 0000 0100 : This EPG entry will be flagged as a premiere
|
||||
IS_FINALE = 0x08 # 0000 1000 : This EPG entry will be flagged as a finale
|
||||
IS_LIVE = 0x10 # 0001 0000 : This EPG entry will be flagged as live
|
||||
|
||||
@staticmethod
|
||||
def has_flag(flags: int, flag: int) -> bool:
|
||||
"""
|
||||
Check if a specific flag is set.
|
||||
|
||||
Args:
|
||||
flags: Combined flags value
|
||||
flag: Flag to check for
|
||||
|
||||
Returns:
|
||||
True if flag is set
|
||||
|
||||
Example:
|
||||
if EPGFlags.has_flag(entry.flags, EPGFlags.IS_LIVE):
|
||||
print("This is a live broadcast")
|
||||
"""
|
||||
return (flags & flag) == flag
|
||||
|
||||
@staticmethod
|
||||
def combine(*flags: int) -> int:
|
||||
"""
|
||||
Combine multiple flags using bitwise OR.
|
||||
|
||||
Args:
|
||||
*flags: Variable number of flag values
|
||||
|
||||
Returns:
|
||||
Combined flags value
|
||||
|
||||
Example:
|
||||
combined = EPGFlags.combine(
|
||||
EPGFlags.IS_SERIES,
|
||||
EPGFlags.IS_NEW,
|
||||
EPGFlags.IS_PREMIERE
|
||||
)
|
||||
"""
|
||||
result = 0
|
||||
for flag in flags:
|
||||
result |= flag
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_flag_names(flags: int) -> List[str]:
|
||||
"""
|
||||
Get list of flag names that are set.
|
||||
|
||||
Args:
|
||||
flags: Combined flags value
|
||||
|
||||
Returns:
|
||||
List of flag names
|
||||
|
||||
Example:
|
||||
flags = EPGFlags.IS_SERIES | EPGFlags.IS_NEW
|
||||
names = EPGFlags.get_flag_names(flags)
|
||||
# Returns: ["IS_SERIES", "IS_NEW"]
|
||||
"""
|
||||
flag_map = {
|
||||
EPGFlags.IS_SERIES: "IS_SERIES",
|
||||
EPGFlags.IS_NEW: "IS_NEW",
|
||||
EPGFlags.IS_PREMIERE: "IS_PREMIERE",
|
||||
EPGFlags.IS_FINALE: "IS_FINALE",
|
||||
EPGFlags.IS_LIVE: "IS_LIVE",
|
||||
}
|
||||
|
||||
result = []
|
||||
for flag_value, flag_name in flag_map.items():
|
||||
if flags & flag_value:
|
||||
result.append(flag_name)
|
||||
|
||||
return result if result else ["UNDEFINED"]
|
||||
|
||||
|
||||
# Genre type constants based on ETSI EN 300 468 V1.14.1 (DVB-SI EIT content descriptor)
|
||||
@@ -407,4 +653,23 @@ class EPGGenreSubtype:
|
||||
|
||||
|
||||
# Legacy alias for backwards compatibility with existing code
|
||||
PVREPGTag = EPGEntry
|
||||
PVREPGTag = EPGEntry
|
||||
|
||||
# Export all public symbols
|
||||
__all__ = [
|
||||
# Main classes
|
||||
'EPGEntry',
|
||||
'PVREPGTag', # Legacy alias
|
||||
|
||||
# Constants
|
||||
'EPG_TAG_INVALID_UID',
|
||||
'EPG_TAG_INVALID_SERIES_EPISODE',
|
||||
'EPG_TIMEFRAME_UNLIMITED',
|
||||
'EPG_STRING_TOKEN_SEPARATOR',
|
||||
|
||||
# Enums and flags
|
||||
'EPGEventState',
|
||||
'EPGFlags',
|
||||
'EPGGenre',
|
||||
'EPGGenreSubtype',
|
||||
]
|
||||
Reference in New Issue
Block a user