mirror of
https://github.com/nirvana-7777/script.service.ultimate.git
synced 2026-09-23 09:32:22 +02:00
334 lines
10 KiB
Python
334 lines
10 KiB
Python
# streaming_providers/base/models.py
|
|
import logging
|
|
from dataclasses import dataclass
|
|
from enum import Enum
|
|
from typing import Dict, List, Optional
|
|
|
|
from .drm import DRMConfig
|
|
|
|
# Setup logging for validation warnings
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class StreamingMode(Enum):
|
|
"""Enum for streaming modes"""
|
|
|
|
LIVE = "live"
|
|
VOD = "vod"
|
|
|
|
|
|
class ContentType(Enum):
|
|
"""Enum for content types"""
|
|
|
|
LIVE = "LIVE"
|
|
VOD = "VOD"
|
|
SERIES = "SERIES"
|
|
MOVIE = "MOVIE"
|
|
RADIO = "RADIO"
|
|
|
|
|
|
class Quality(Enum):
|
|
"""Enum for stream quality"""
|
|
|
|
SD = "SD"
|
|
HD = "HD"
|
|
UHD = "UHD"
|
|
FOUR_K = "4K"
|
|
AUDIO = "AUDIO"
|
|
|
|
|
|
@dataclass
|
|
class StreamingChannel:
|
|
"""
|
|
Universal channel representation for all providers
|
|
|
|
Backward Compatibility Note:
|
|
- All existing fields remain unchanged
|
|
- New fields have default values
|
|
- All existing methods remain functional
|
|
"""
|
|
|
|
# Core identification
|
|
name: str
|
|
channel_id: str
|
|
provider: str # 'joyn', 'zdf', 'ard', etc.
|
|
|
|
# Visual elements
|
|
logo_url: Optional[str] = None
|
|
|
|
# Channel metadata
|
|
channel_number: Optional[int] = None # Display channel number (e.g., 1, 3, 99)
|
|
quality: Optional[str] = None # 'SD', 'HD', 'UHD', '4K'
|
|
|
|
# Streaming configuration
|
|
mode: str = "live" # "live" or "vod" - kept as string for compatibility
|
|
session_manifest: bool = False
|
|
manifest: Optional[str] = None
|
|
manifest_script: Optional[str] = None
|
|
|
|
# DRM/CDM settings
|
|
cdm_type: Optional[str] = None
|
|
use_cdm: bool = True
|
|
cdm: Optional[str] = None
|
|
cdm_mode: str = "external"
|
|
|
|
# DRM Configuration
|
|
drm_config: Optional[DRMConfig] = None
|
|
|
|
# Video settings
|
|
video: str = "best"
|
|
on_demand: bool = True
|
|
speed_up: bool = True
|
|
|
|
# Additional metadata
|
|
content_type: str = "LIVE"
|
|
description: Optional[str] = None
|
|
genre: Optional[str] = None
|
|
language: str = "de"
|
|
country: str = "DE"
|
|
|
|
# Streaming URLs
|
|
license_url: Optional[str] = None
|
|
certificate_url: Optional[str] = None
|
|
streaming_format: Optional[str] = None
|
|
|
|
# NEW: Radio support (backward compatible - defaults to False)
|
|
is_radio: bool = False
|
|
|
|
def __post_init__(self):
|
|
"""
|
|
Post-initialization processing for backward compatibility
|
|
and automatic field synchronization
|
|
"""
|
|
# Run validation checks (warnings only for backward compatibility)
|
|
self._validate_fields()
|
|
|
|
# Auto-update content_type for radio
|
|
if self.is_radio and self.content_type == "LIVE":
|
|
self.content_type = "RADIO"
|
|
self.quality = "AUDIO"
|
|
|
|
def _validate_fields(self):
|
|
"""
|
|
Internal validation method that logs warnings instead of raising errors
|
|
for backward compatibility
|
|
"""
|
|
# Validate content_type consistency
|
|
if self.mode == "vod" and self.content_type == "LIVE":
|
|
logger.warning(
|
|
f"Channel {self.name} ({self.channel_id}): "
|
|
f"VOD mode with LIVE content_type - consider changing to VOD"
|
|
)
|
|
|
|
# Validate manifest consistency
|
|
if self.session_manifest and self.manifest:
|
|
logger.warning(
|
|
f"Channel {self.name} ({self.channel_id}): "
|
|
f"session_manifest=True but manifest URL is set - manifest will be ignored"
|
|
)
|
|
|
|
def to_dict(self) -> Dict:
|
|
"""Convert to dictionary format - backward compatible with new fields added"""
|
|
result = {
|
|
"Name": self.name,
|
|
"Id": self.channel_id,
|
|
"Provider": self.provider,
|
|
"LogoUrl": self.logo_url,
|
|
"ChannelNumber": self.channel_number,
|
|
"Quality": self.quality,
|
|
"Mode": self.mode,
|
|
"SessionManifest": self.session_manifest,
|
|
"Manifest": self.manifest,
|
|
"ManifestScript": self.manifest_script,
|
|
"CdmType": self.cdm_type,
|
|
"UseCdm": self.use_cdm,
|
|
"Cdm": self.cdm,
|
|
"CdmMode": self.cdm_mode,
|
|
"Video": self.video,
|
|
"OnDemand": self.on_demand,
|
|
"SpeedUp": self.speed_up,
|
|
"ContentType": self.content_type,
|
|
"Country": self.country,
|
|
"Language": self.language,
|
|
"StreamingFormat": self.streaming_format,
|
|
# NEW: Radio fields (backward compatible addition)
|
|
"IsRadio": self.is_radio,
|
|
}
|
|
|
|
# Add DRM config if present
|
|
if self.drm_config:
|
|
result["DrmConfig"] = self.drm_config.to_dict()
|
|
|
|
return result
|
|
|
|
def set_static_manifest(self, manifest_url: str) -> None:
|
|
"""
|
|
Set a static manifest URL (scenario 1: provider gives manifest directly)
|
|
"""
|
|
self.manifest = manifest_url
|
|
self.session_manifest = False
|
|
self.manifest_script = None
|
|
|
|
def set_dynamic_manifest(self, manifest_script_params: str) -> None:
|
|
"""
|
|
Set dynamic manifest parameters (scenario 3: manifest needs to be fetched at request time)
|
|
|
|
Args:
|
|
manifest_script_params: Parameters needed to fetch manifest (e.g., channel_id, api_endpoint)
|
|
"""
|
|
self.manifest = None
|
|
self.session_manifest = True
|
|
self.manifest_script = manifest_script_params
|
|
|
|
# NEW: Backward compatible enhancements
|
|
|
|
@classmethod
|
|
def create_live_channel(
|
|
cls, name: str, channel_id: str, provider: str, **kwargs
|
|
) -> "StreamingChannel":
|
|
"""
|
|
Factory method for live channels with proper defaults
|
|
Backward compatible: Existing code can continue using direct instantiation
|
|
"""
|
|
return cls(
|
|
name=name,
|
|
channel_id=channel_id,
|
|
provider=provider,
|
|
mode="live",
|
|
content_type="LIVE",
|
|
**kwargs,
|
|
)
|
|
|
|
@classmethod
|
|
def create_vod_channel(
|
|
cls, name: str, content_id: str, provider: str, **kwargs
|
|
) -> "StreamingChannel":
|
|
"""
|
|
Factory method for VOD content
|
|
Backward compatible: Existing code can continue using direct instantiation
|
|
"""
|
|
return cls(
|
|
name=name,
|
|
channel_id=content_id,
|
|
provider=provider,
|
|
mode="vod",
|
|
content_type="VOD",
|
|
**kwargs,
|
|
)
|
|
|
|
@classmethod
|
|
def create_radio_channel(
|
|
cls, name: str, channel_id: str, provider: str, **kwargs
|
|
) -> "StreamingChannel":
|
|
"""
|
|
Factory method for radio channels
|
|
Backward compatible: Existing code can continue using direct instantiation
|
|
"""
|
|
return cls(
|
|
name=name,
|
|
channel_id=channel_id,
|
|
provider=provider,
|
|
is_radio=True,
|
|
content_type="RADIO",
|
|
quality="AUDIO",
|
|
**kwargs,
|
|
)
|
|
|
|
def get_streaming_urls(self) -> List[str]:
|
|
"""
|
|
Extract all relevant URLs for logging/validation
|
|
New method - doesn't affect backward compatibility
|
|
"""
|
|
urls = []
|
|
if self.manifest:
|
|
urls.append(self.manifest)
|
|
if self.license_url:
|
|
urls.append(self.license_url)
|
|
if self.certificate_url:
|
|
urls.append(self.certificate_url)
|
|
return urls
|
|
|
|
def requires_drm(self) -> bool:
|
|
"""
|
|
Check if this channel needs DRM handling
|
|
New method - doesn't affect backward compatibility
|
|
"""
|
|
return bool(self.drm_config) or bool(self.license_url)
|
|
|
|
def is_audio_content(self) -> bool:
|
|
"""
|
|
Check if this is audio-only content (radio or audio track)
|
|
New method - doesn't affect backward compatibility
|
|
"""
|
|
return self.is_radio
|
|
|
|
def detect_and_set_radio(self) -> None:
|
|
"""
|
|
Auto-detect if this is likely a radio channel
|
|
New method - doesn't affect backward compatibility
|
|
"""
|
|
if self.is_radio: # Already set
|
|
return
|
|
|
|
radio_indicators = [
|
|
self.name.lower().startswith("radio"),
|
|
"radio" in self.name.lower(),
|
|
self.quality in ["audio", "aac", "mp3", "AUDIO"],
|
|
self.description and "radio" in self.description.lower(),
|
|
self.genre and "radio" in self.genre.lower(),
|
|
]
|
|
|
|
if any(radio_indicators):
|
|
self.is_radio = True
|
|
|
|
# Update quality if not set
|
|
if not self.quality or self.quality.upper() not in [
|
|
q.value for q in Quality
|
|
]:
|
|
self.quality = "AUDIO"
|
|
|
|
# Update content_type if it's still LIVE
|
|
if self.content_type == "LIVE":
|
|
self.content_type = "RADIO"
|
|
|
|
# Compatibility properties (optional - for clearer naming)
|
|
@property
|
|
def dynamic_manifest(self) -> bool:
|
|
"""Alias for session_manifest with clearer name"""
|
|
return self.session_manifest
|
|
|
|
@dynamic_manifest.setter
|
|
def dynamic_manifest(self, value: bool):
|
|
"""Setter for dynamic_manifest alias"""
|
|
self.session_manifest = value
|
|
|
|
@property
|
|
def requires_session_manifest(self) -> bool:
|
|
"""Alternative property name for clarity"""
|
|
return self.session_manifest
|
|
|
|
def validate(self) -> List[str]:
|
|
"""
|
|
Run comprehensive validation and return list of warnings/issues
|
|
New method - doesn't affect backward compatibility
|
|
"""
|
|
warnings = []
|
|
|
|
# Check for missing required fields for streaming
|
|
if not self.manifest and not self.manifest_script:
|
|
warnings.append("No manifest URL or manifest script provided")
|
|
|
|
# Check DRM configuration
|
|
if self.license_url and not self.drm_config:
|
|
warnings.append("License URL provided but no DRM configuration")
|
|
|
|
# Check content type consistency
|
|
if self.mode == "vod" and self.content_type == "LIVE":
|
|
warnings.append("VOD mode should not have LIVE content_type")
|
|
|
|
# Check radio consistency
|
|
if self.is_radio and self.quality not in ["AUDIO", "audio", None]:
|
|
warnings.append(f"Radio channel has video quality setting: {self.quality}")
|
|
|
|
return warnings
|