mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-19 07:32:37 +02:00
828 lines
25 KiB
Python
828 lines
25 KiB
Python
#!/usr/bin/env python3
|
|
# streaming_providers/base/models/epg_models.py
|
|
"""
|
|
EPG Models - Data classes for Electronic Program Guide entries
|
|
Based on Kodi PVR EPG Tag specification (ETSI EN 300 468 DVB-SI standard)
|
|
"""
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
from enum import IntEnum
|
|
from typing import List, Optional
|
|
|
|
# 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
|
|
class EPGEntry:
|
|
"""
|
|
EPG Entry model for Kodi PVR Backend.
|
|
|
|
Represents a single program/event in the Electronic Program Guide.
|
|
Based on Kodi's PVREPGTag C++ class specification.
|
|
|
|
This class matches the dictionary format used by epg_parser.py and
|
|
expected by the C++ PVR frontend.
|
|
|
|
BROADCAST ID ENCODING:
|
|
----------------------
|
|
The broadcast_id field uses a special encoding scheme that embeds provider
|
|
information for catchup functionality:
|
|
|
|
- Bits 0-15 (lower 16 bits): Provider hash
|
|
- Bits 16-31 (upper 16 bits): Event hash (channel + start time)
|
|
|
|
This allows identifying the provider from just the broadcast_id, which is
|
|
critical for catchup operations where only the broadcast_id is available.
|
|
|
|
Usage:
|
|
# Creating entries (done by parser)
|
|
broadcast_id = EPGEntry.encode_broadcast_id("rtlplus", "rtl", start_time)
|
|
|
|
# In catchup handler (only broadcast_id available)
|
|
provider_hash = EPGEntry.get_provider_hash(broadcast_id)
|
|
# Look up provider from hash in registry
|
|
|
|
# Or verify provider
|
|
if EPGEntry.verify_provider(broadcast_id, "rtlplus"):
|
|
# Get catchup stream from rtlplus
|
|
"""
|
|
|
|
# Required fields
|
|
broadcast_id: int
|
|
"""Unique identifier for this broadcast event. Must be unique per channel."""
|
|
|
|
title: str
|
|
"""Program title."""
|
|
|
|
start: int
|
|
"""Start time as Unix timestamp (seconds since epoch)."""
|
|
|
|
end: int
|
|
"""End time as Unix timestamp (seconds since epoch)."""
|
|
|
|
# Optional fields - Program Information
|
|
description: Optional[str] = None
|
|
"""Full program description/plot. C++ expects 'description' key."""
|
|
|
|
plot_outline: Optional[str] = None
|
|
"""Short plot outline (first sentence or ~100 chars of description)."""
|
|
|
|
episode_name: Optional[str] = None
|
|
"""Episode title/name (XMLTV sub-title)."""
|
|
|
|
original_title: Optional[str] = None
|
|
"""Original title if different from main title."""
|
|
|
|
# Optional fields - Media Metadata
|
|
year: Optional[int] = None
|
|
"""Production year."""
|
|
|
|
icon: Optional[str] = None
|
|
"""URL to program icon/poster image. C++ expects 'icon' key."""
|
|
|
|
# Optional fields - People (C++ expects arrays)
|
|
cast: Optional[List[str]] = None
|
|
"""List of actor names. C++ expects array format."""
|
|
|
|
directors: Optional[List[str]] = None
|
|
"""List of director names. C++ expects 'directors' array."""
|
|
|
|
writers: Optional[List[str]] = None
|
|
"""List of writer names. C++ expects 'writers' array."""
|
|
|
|
# Optional fields - Genre/Category
|
|
genre: Optional[int] = None
|
|
"""
|
|
Numeric genre type based on DVB-SI standard (ETSI EN 300 468).
|
|
Use EPGGenre constants (e.g., EPGGenre.MOVIEDRAMA = 0x10).
|
|
Set to EPGGenre.USE_STRING (0xF0) to use genre_description instead.
|
|
"""
|
|
|
|
genre_sub_type: Optional[int] = None
|
|
"""
|
|
Numeric genre subtype based on DVB-SI standard.
|
|
Use EPGGenreSubtype nested classes (e.g., EPGGenreSubtype.Sports.FOOTBALL_SOCCER).
|
|
Must be used in combination with appropriate genre type.
|
|
"""
|
|
|
|
genre_description: Optional[str] = None
|
|
"""
|
|
Text description of genre.
|
|
Used when genre=EPGGenre.USE_STRING or for custom genres not in DVB-SI standard.
|
|
"""
|
|
|
|
# Optional fields - Episode Information
|
|
season_number: Optional[int] = None
|
|
"""Season/series number (1-based). C++ expects 'season_number' key."""
|
|
|
|
episode_number: Optional[int] = None
|
|
"""Episode number within season (1-based)."""
|
|
|
|
episode_part_number: Optional[int] = None
|
|
"""Part number for multi-part episodes (1-based)."""
|
|
|
|
# Optional fields - Ratings
|
|
star_rating: Optional[int] = None
|
|
"""Star rating on 0-10 scale."""
|
|
|
|
parental_rating: Optional[int] = None
|
|
"""Parental rating code/age restriction."""
|
|
|
|
parental_rating_code: Optional[str] = None
|
|
"""Text parental rating code (e.g., 'TV-PG', 'FSK 12')."""
|
|
|
|
# Optional fields - Additional Metadata
|
|
first_aired: Optional[int] = None
|
|
"""Original air date as Unix timestamp."""
|
|
|
|
imdb_number: Optional[str] = None
|
|
"""IMDB identifier."""
|
|
|
|
series_link: Optional[str] = None
|
|
"""Link to series information."""
|
|
|
|
flags: Optional[int] = None
|
|
"""
|
|
Bit field flags for EPG entry properties.
|
|
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:
|
|
"""
|
|
Convert EPGEntry to dictionary format expected by C++ frontend.
|
|
Only includes non-None values to minimize data transfer.
|
|
|
|
Returns:
|
|
Dictionary with EPG data
|
|
"""
|
|
result = {
|
|
"broadcast_id": self.broadcast_id,
|
|
"title": self.title,
|
|
"start": self.start,
|
|
"end": self.end,
|
|
}
|
|
|
|
# Add optional fields only if they have values
|
|
optional_fields = [
|
|
"description",
|
|
"plot_outline",
|
|
"episode_name",
|
|
"original_title",
|
|
"year",
|
|
"icon",
|
|
"cast",
|
|
"directors",
|
|
"writers",
|
|
"genre",
|
|
"genre_description",
|
|
"season_number",
|
|
"episode_number",
|
|
"episode_part_number",
|
|
"star_rating",
|
|
"parental_rating",
|
|
"parental_rating_code",
|
|
"first_aired",
|
|
"imdb_number",
|
|
"series_link",
|
|
"flags",
|
|
]
|
|
|
|
for field_name in optional_fields:
|
|
value = getattr(self, field_name)
|
|
if value is not None:
|
|
result[field_name] = value
|
|
|
|
return result
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: dict) -> "EPGEntry":
|
|
"""
|
|
Create EPGEntry from dictionary (e.g., from epg_parser output).
|
|
|
|
Args:
|
|
data: Dictionary with EPG data
|
|
|
|
Returns:
|
|
EPGEntry instance
|
|
"""
|
|
from dataclasses import fields
|
|
|
|
# Extract only fields that exist in EPGEntry
|
|
valid_fields = {f.name for f in fields(cls)}
|
|
|
|
filtered_data = {k: v for k, v in data.items() if k in valid_fields}
|
|
|
|
return cls(**filtered_data)
|
|
|
|
@property
|
|
def duration_seconds(self) -> int:
|
|
"""Calculate program duration in seconds."""
|
|
return self.end - self.start
|
|
|
|
@property
|
|
def start_datetime(self) -> datetime:
|
|
"""Get start time as datetime object."""
|
|
return datetime.fromtimestamp(self.start)
|
|
|
|
@property
|
|
def end_datetime(self) -> datetime:
|
|
"""Get end time as datetime object."""
|
|
return datetime.fromtimestamp(self.end)
|
|
|
|
def is_currently_airing(self, reference_time: Optional[int] = None) -> bool:
|
|
"""
|
|
Check if program is currently airing.
|
|
|
|
Args:
|
|
reference_time: Unix timestamp to check against (None = now)
|
|
|
|
Returns:
|
|
True if program is airing at reference_time
|
|
"""
|
|
if reference_time is None:
|
|
reference_time = int(datetime.now().timestamp())
|
|
|
|
return self.start <= reference_time < self.end
|
|
|
|
def overlaps_with(self, start_time: int, end_time: int) -> bool:
|
|
"""
|
|
Check if this program overlaps with a given time range.
|
|
|
|
Args:
|
|
start_time: Start of time range (Unix timestamp)
|
|
end_time: End of time range (Unix timestamp)
|
|
|
|
Returns:
|
|
True if there is any overlap
|
|
"""
|
|
# Program overlaps if it doesn't end before range starts
|
|
# 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()
|
|
]
|
|
|
|
@staticmethod
|
|
def encode_broadcast_id(
|
|
provider_name: str, channel_id: str, start_time: int
|
|
) -> int:
|
|
"""
|
|
Generate deterministic broadcast ID with encoded provider information.
|
|
|
|
The ID structure allows extraction of provider hash for catchup operations:
|
|
- Bits 0-15 (lower 16 bits): Provider hash (65536 possible values)
|
|
- Bits 16-31 (upper 16 bits): Event hash (channel + start time)
|
|
|
|
This ensures:
|
|
1. Unique IDs across different providers
|
|
2. Provider can be identified from broadcast_id alone
|
|
3. Same event on same provider always gets same ID
|
|
|
|
Args:
|
|
provider_name: Provider name (e.g., "rtlplus", "joyn_de")
|
|
channel_id: Channel ID (e.g., "rtl", "prosieben")
|
|
start_time: Unix timestamp of programme start
|
|
|
|
Returns:
|
|
Unique 32-bit broadcast ID with encoded provider info
|
|
|
|
Example:
|
|
broadcast_id = EPGEntry.encode_broadcast_id("rtlplus", "rtl", 1234567890)
|
|
provider_hash = EPGEntry.get_provider_hash(broadcast_id)
|
|
"""
|
|
import hashlib
|
|
|
|
# Generate provider hash (16 bits)
|
|
provider_hash_obj = hashlib.sha256(provider_name.encode("utf-8"))
|
|
provider_hash = int(provider_hash_obj.hexdigest()[:4], 16) # 16 bits
|
|
|
|
# Generate event hash from channel + start time (16 bits)
|
|
event_input = f"{channel_id}_{start_time}".encode("utf-8")
|
|
event_hash_obj = hashlib.sha256(event_input)
|
|
event_hash = int(event_hash_obj.hexdigest()[:4], 16) # 16 bits
|
|
|
|
# Combine: upper 16 bits = event hash, lower 16 bits = provider hash
|
|
broadcast_id = (event_hash << 16) | provider_hash
|
|
|
|
# Ensure positive and non-zero
|
|
return broadcast_id if broadcast_id > 0 else 1
|
|
|
|
@staticmethod
|
|
def get_provider_hash(broadcast_id: int) -> int:
|
|
"""
|
|
Extract provider hash from encoded broadcast ID.
|
|
|
|
Args:
|
|
broadcast_id: Encoded broadcast ID from encode_broadcast_id()
|
|
|
|
Returns:
|
|
16-bit provider hash (lower 16 bits of broadcast_id)
|
|
|
|
Example:
|
|
broadcast_id = EPGEntry.encode_broadcast_id("rtlplus", "rtl", 1234567890)
|
|
provider_hash = EPGEntry.get_provider_hash(broadcast_id)
|
|
# Use provider_hash to look up provider from registry
|
|
"""
|
|
return broadcast_id & 0xFFFF
|
|
|
|
@staticmethod
|
|
def get_event_hash(broadcast_id: int) -> int:
|
|
"""
|
|
Extract event hash from encoded broadcast ID.
|
|
|
|
Args:
|
|
broadcast_id: Encoded broadcast ID from encode_broadcast_id()
|
|
|
|
Returns:
|
|
16-bit event hash (upper 16 bits of broadcast_id)
|
|
"""
|
|
return (broadcast_id >> 16) & 0xFFFF
|
|
|
|
@staticmethod
|
|
def verify_provider(broadcast_id: int, provider_name: str) -> bool:
|
|
"""
|
|
Verify if a broadcast ID matches a given provider.
|
|
|
|
Args:
|
|
broadcast_id: Encoded broadcast ID
|
|
provider_name: Provider name to verify against
|
|
|
|
Returns:
|
|
True if broadcast_id was generated for this provider
|
|
|
|
Example:
|
|
if EPGEntry.verify_provider(broadcast_id, "rtlplus"):
|
|
print("This EPG entry is from rtlplus")
|
|
"""
|
|
import hashlib
|
|
|
|
# Get provider hash from broadcast_id
|
|
stored_hash = EPGEntry.get_provider_hash(broadcast_id)
|
|
|
|
# Calculate hash for given provider name
|
|
provider_hash_obj = hashlib.sha256(provider_name.encode("utf-8"))
|
|
calculated_hash = int(provider_hash_obj.hexdigest()[:4], 16)
|
|
|
|
return stored_hash == calculated_hash
|
|
|
|
def __post_init__(self):
|
|
"""Validate required fields after initialization."""
|
|
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")
|
|
|
|
if self.start <= 0:
|
|
raise ValueError("start time must be a valid Unix timestamp")
|
|
|
|
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 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)
|
|
# These match Kodi's EPG_EVENT_CONTENTMASK values
|
|
class EPGGenre:
|
|
"""
|
|
EPG genre type codes based on DVB-SI standard (ETSI EN 300 468).
|
|
These are the main content masks - use with genre_type field.
|
|
"""
|
|
|
|
# Main genre types (content masks)
|
|
UNDEFINED = 0x00
|
|
MOVIEDRAMA = 0x10
|
|
NEWSCURRENTAFFAIRS = 0x20
|
|
SHOW = 0x30
|
|
SPORTS = 0x40
|
|
CHILDRENYOUTH = 0x50
|
|
MUSICBALLETDANCE = 0x60
|
|
ARTSCULTURE = 0x70
|
|
SOCIALPOLITICALECONOMICS = 0x80
|
|
EDUCATIONALSCIENCE = 0x90
|
|
LEISUREHOBBIES = 0xA0
|
|
SPECIAL = 0xB0
|
|
USERDEFINED = 0xF0
|
|
|
|
# Special Kodi value to indicate genre is provided as string
|
|
USE_STRING = 0xF0 # Same as USERDEFINED, signals use of genre_description
|
|
|
|
|
|
class EPGGenreSubtype:
|
|
"""
|
|
EPG genre subtype codes based on DVB-SI standard (ETSI EN 300 468).
|
|
These are used with genre_sub_type field in combination with main genre_type.
|
|
"""
|
|
|
|
# Movie/Drama subtypes (use with EPGGenre.MOVIEDRAMA)
|
|
class MovieDrama:
|
|
GENERAL = 0x00
|
|
DETECTIVE_THRILLER = 0x01
|
|
ADVENTURE_WESTERN_WAR = 0x02
|
|
SCIENCEFICTION_FANTASY_HORROR = 0x03
|
|
COMEDY = 0x04
|
|
SOAP_MELODRAMA_FOLKLORIC = 0x05
|
|
ROMANCE = 0x06
|
|
SERIOUS_CLASSICAL_RELIGIOUS_HISTORICAL = 0x07
|
|
ADULT = 0x08
|
|
USERDEFINED = 0x0F
|
|
|
|
# News/Current Affairs subtypes (use with EPGGenre.NEWSCURRENTAFFAIRS)
|
|
class NewsCurrentAffairs:
|
|
GENERAL = 0x00
|
|
WEATHER = 0x01
|
|
MAGAZINE = 0x02
|
|
DOCUMENTARY = 0x03
|
|
DISCUSSION_INTERVIEW_DEBATE = 0x04
|
|
USERDEFINED = 0x0F
|
|
|
|
# Show/Game Show subtypes (use with EPGGenre.SHOW)
|
|
class Show:
|
|
GENERAL = 0x00
|
|
GAMESHOW_QUIZ_CONTEST = 0x01
|
|
VARIETY_SHOW = 0x02
|
|
TALK_SHOW = 0x03
|
|
USERDEFINED = 0x0F
|
|
|
|
# Sports subtypes (use with EPGGenre.SPORTS)
|
|
class Sports:
|
|
GENERAL = 0x00
|
|
OLYMPICGAMES_WORLDCUP = 0x01
|
|
SPORTS_MAGAZINES = 0x02
|
|
FOOTBALL_SOCCER = 0x03
|
|
TENNIS_SQUASH = 0x04
|
|
TEAMSPORTS = 0x05
|
|
ATHLETICS = 0x06
|
|
MOTORSPORT = 0x07
|
|
WATERSPORT = 0x08
|
|
WINTERSPORTS = 0x09
|
|
EQUESTRIAN = 0x0A
|
|
MARTIALSPORTS = 0x0B
|
|
USERDEFINED = 0x0F
|
|
|
|
# Children/Youth subtypes (use with EPGGenre.CHILDRENYOUTH)
|
|
class ChildrenYouth:
|
|
GENERAL = 0x00
|
|
PRESCHOOL_CHILDREN = 0x01
|
|
ENTERTAIN_6TO14 = 0x02
|
|
ENTERTAIN_10TO16 = 0x03
|
|
INFORMATIONAL_EDUCATIONAL_SCHOOL = 0x04
|
|
CARTOONS_PUPPETS = 0x05
|
|
USERDEFINED = 0x0F
|
|
|
|
# Music/Ballet/Dance subtypes (use with EPGGenre.MUSICBALLETDANCE)
|
|
class MusicBalletDance:
|
|
GENERAL = 0x00
|
|
ROCKPOP = 0x01
|
|
SERIOUSMUSIC_CLASSICALMUSIC = 0x02
|
|
FOLK_TRADITIONAL_MUSIC = 0x03
|
|
JAZZ = 0x04
|
|
MUSICAL_OPERA = 0x05
|
|
BALLET = 0x06
|
|
USERDEFINED = 0x0F
|
|
|
|
# Arts/Culture subtypes (use with EPGGenre.ARTSCULTURE)
|
|
class ArtsCulture:
|
|
GENERAL = 0x00
|
|
PERFORMINGARTS = 0x01
|
|
FINEARTS = 0x02
|
|
RELIGION = 0x03
|
|
POPULARCULTURE_TRADITIONALARTS = 0x04
|
|
LITERATURE = 0x05
|
|
FILM_CINEMA = 0x06
|
|
EXPERIMENTALFILM_VIDEO = 0x07
|
|
BROADCASTING_PRESS = 0x08
|
|
NEWMEDIA = 0x09
|
|
ARTS_CULTUREMAGAZINES = 0x0A
|
|
FASHION = 0x0B
|
|
USERDEFINED = 0x0F
|
|
|
|
# Social/Political/Economics subtypes (use with EPGGenre.SOCIALPOLITICALECONOMICS)
|
|
class SocialPoliticalEconomics:
|
|
GENERAL = 0x00
|
|
MAGAZINES_REPORTS_DOCUMENTARY = 0x01
|
|
ECONOMICS_SOCIALADVISORY = 0x02
|
|
REMARKABLEPEOPLE = 0x03
|
|
USERDEFINED = 0x0F
|
|
|
|
# Educational/Science subtypes (use with EPGGenre.EDUCATIONALSCIENCE)
|
|
class EducationalScience:
|
|
GENERAL = 0x00
|
|
NATURE_ANIMALS_ENVIRONMENT = 0x01
|
|
TECHNOLOGY_NATURALSCIENCES = 0x02
|
|
MEDICINE_PHYSIOLOGY_PSYCHOLOGY = 0x03
|
|
FOREIGNCOUNTRIES_EXPEDITIONS = 0x04
|
|
SOCIAL_SPIRITUALSCIENCES = 0x05
|
|
FURTHEREDUCATION = 0x06
|
|
LANGUAGES = 0x07
|
|
USERDEFINED = 0x0F
|
|
|
|
# Leisure/Hobbies subtypes (use with EPGGenre.LEISUREHOBBIES)
|
|
class LeisureHobbies:
|
|
GENERAL = 0x00
|
|
TOURISM_TRAVEL = 0x01
|
|
HANDICRAFT = 0x02
|
|
MOTORING = 0x03
|
|
FITNESSANDHEALTH = 0x04
|
|
COOKING = 0x05
|
|
ADVERTISEMENT_SHOPPING = 0x06
|
|
GARDENING = 0x07
|
|
USERDEFINED = 0x0F
|
|
|
|
# Special Characteristics subtypes (use with EPGGenre.SPECIAL)
|
|
class Special:
|
|
GENERAL = 0x00
|
|
BLACKANDWHITE = 0x01
|
|
UNPUBLISHED = 0x02
|
|
LIVEBROADCAST = 0x03
|
|
PLANOSTEREOSCOPIC = 0x04
|
|
LOCALORREGIONAL = 0x05
|
|
USERDEFINED = 0x0F
|
|
|
|
|
|
# Legacy alias for backwards compatibility with existing code
|
|
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",
|
|
]
|