Files
script.service.ultimate/lib/streaming_providers/base/manager.py
T

309 lines
12 KiB
Python
Raw Normal View History

2025-10-29 20:23:50 +01:00
# streaming_providers/base/manager.py
2025-12-23 16:51:49 +01:00
"""
Main ProviderManager as a facade coordinating all operations.
Maintains backward compatibility while delegating to specialized classes.
"""
2026-01-06 16:41:03 +01:00
from typing import Any, Dict, List, Optional
from .catchup_operations import CatchupOperations
2025-12-23 16:51:49 +01:00
from .channel_operations import ChannelOperations
from .drm_operations import DRMOperations
2026-01-06 16:41:03 +01:00
from .epg_operations import EPGOperations
2025-12-23 16:51:49 +01:00
from .models import StreamingChannel
2026-01-06 16:41:03 +01:00
from .provider_registry import ProviderRegistry
from .subscription_operations import SubscriptionOperations
2025-10-29 20:23:50 +01:00
from .utils.logger import logger
2025-12-23 16:51:49 +01:00
2025-10-29 20:23:50 +01:00
class ProviderManager:
"""
2025-12-23 16:51:49 +01:00
Facade coordinating all provider-related operations.
Maintains backward compatibility while delegating to specialized classes.
2025-10-29 20:23:50 +01:00
"""
def __init__(self):
2025-12-23 16:51:49 +01:00
# Core components
self.registry = ProviderRegistry()
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
# Specialized operations
self.channel_ops = ChannelOperations(self.registry)
self.epg_ops = EPGOperations(self.registry)
self.drm_ops = DRMOperations(self.registry)
self.catchup_ops = CatchupOperations(self.registry, self.drm_ops)
self.subscription_ops = SubscriptionOperations(self.registry)
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
# Backward compatibility - expose managers directly
self.drm_plugin_manager = self.drm_ops.drm_plugin_manager
self.epg_manager = self.epg_ops.epg_manager
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
# Legacy compatibility - expose providers dict
self.providers = self.registry.providers
self.provider_metadata = self.registry.provider_metadata
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
logger.info("ProviderManager: Initialized with modular architecture")
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
# ==========================================================================
# REGISTRY OPERATIONS (delegate to ProviderRegistry)
# ==========================================================================
2025-10-29 20:23:50 +01:00
2026-01-06 16:41:03 +01:00
def discover_all_providers(self, default_country: str = "DE") -> List[str]:
2025-12-23 16:51:49 +01:00
return self.registry.discover_all_providers(default_country)
2025-10-29 20:23:50 +01:00
2026-01-16 12:24:41 +01:00
def discover_providers(self, country: str = "DE", detected_providers: Dict = None) -> List[str]:
2025-12-23 16:51:49 +01:00
"""Legacy method for backward compatibility."""
if not self.registry.provider_metadata:
self.registry.discover_all_providers(country)
return self.registry.get_enabled_providers()
2025-10-29 20:23:50 +01:00
2026-02-15 18:16:41 +01:00
def rediscover_providers(self, country: str = "DE") -> List[str]:
"""
Re-scan for new providers, particularly useful for dynamically added M3U playlists.
Args:
country: Country code for provider discovery
Returns:
List of all discovered provider names
"""
logger.info(f"ProviderManager: Re-discovering providers for country '{country}'...")
discovered = self.registry.discover_all_providers(country)
logger.info(f"ProviderManager: Re-discovery complete. Found {len(discovered)} providers")
return discovered
2025-12-23 16:51:49 +01:00
def get_provider(self, provider_name: str):
return self.registry.get_provider(provider_name)
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
def set_provider_enabled(self, provider_name: str, enabled: bool) -> bool:
return self.registry.set_provider_enabled(provider_name, enabled)
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
def get_all_providers_metadata(self) -> List[Dict[str, Any]]:
return self.registry.get_all_providers_metadata()
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
def reinitialize_provider(self, provider_name: str) -> bool:
return self.registry.reinitialize_provider(provider_name)
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
def reinitialize_providers(self, provider_names: List[str]) -> Dict[str, bool]:
2026-01-16 12:24:41 +01:00
return {name: self.registry.reinitialize_provider(name) for name in provider_names}
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
def reinitialize_all_providers(self) -> Dict[str, bool]:
enabled = self.registry.get_enabled_providers()
return self.reinitialize_providers(enabled)
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
def list_providers(self) -> List[str]:
return self.registry.list_providers()
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
def list_all_providers(self) -> List[str]:
return self.registry.list_all_providers()
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
def clear_providers(self):
self.registry.clear_providers()
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
@staticmethod
def get_provider_class(provider_name: str):
"""Get provider class from AVAILABLE_PROVIDERS registry."""
from streaming_providers import AVAILABLE_PROVIDERS
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
# Remove country suffix if present
base_name = provider_name
2026-01-06 16:41:03 +01:00
if "_" in provider_name:
name_parts = provider_name.rsplit("_", 1)
2025-12-23 16:51:49 +01:00
if len(name_parts[1]) in (2, 3) and name_parts[1].isalpha():
base_name = name_parts[0]
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
return AVAILABLE_PROVIDERS.get(base_name)
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
# ==========================================================================
# CHANNEL OPERATIONS (delegate to ChannelOperations)
# ==========================================================================
2025-10-29 20:23:50 +01:00
2026-01-06 16:41:03 +01:00
def get_channels(
2026-02-15 18:16:41 +01:00
self, provider_name: str, fetch_manifests: bool = False, **kwargs
2026-01-06 16:41:03 +01:00
) -> List[StreamingChannel]:
2025-12-23 16:51:49 +01:00
return self.channel_ops.get_channels(provider_name, fetch_manifests, **kwargs)
2025-10-29 20:23:50 +01:00
2026-01-16 12:24:41 +01:00
def get_channel_manifest(self, provider_name: str, channel_id: str, **kwargs) -> Optional[str]:
return self.channel_ops.get_channel_manifest(provider_name, channel_id, **kwargs)
2025-10-29 20:23:50 +01:00
2026-01-06 16:41:03 +01:00
def get_all_channels(
2026-02-15 18:16:41 +01:00
self, fetch_manifests: bool = True, **kwargs
2026-01-06 16:41:03 +01:00
) -> Dict[str, List[StreamingChannel]]:
2025-12-23 16:51:49 +01:00
return self.channel_ops.get_all_channels(fetch_manifests, **kwargs)
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
# ==========================================================================
# EPG OPERATIONS (delegate to EPGOperations)
# ==========================================================================
2025-12-04 10:22:54 +01:00
2026-01-16 12:24:41 +01:00
def get_channel_epg(self, provider_name: str, channel_id: str, **kwargs) -> List[Dict]:
2025-12-23 16:51:49 +01:00
return self.epg_ops.get_channel_epg(provider_name, channel_id, **kwargs)
2025-10-29 20:23:50 +01:00
def get_provider_epg_xmltv(self, provider_name: str, **kwargs) -> Optional[str]:
2025-12-23 16:51:49 +01:00
return self.epg_ops.get_provider_epg_xmltv(provider_name, **kwargs)
2025-10-29 20:23:50 +01:00
2025-12-04 10:22:54 +01:00
def clear_epg_cache(self) -> bool:
2025-12-23 16:51:49 +01:00
return self.epg_ops.clear_epg_cache()
2025-12-04 10:22:54 +01:00
def reload_epg_mapping(self) -> bool:
2025-12-23 16:51:49 +01:00
return self.epg_ops.reload_epg_mapping()
2025-12-04 10:22:54 +01:00
def get_epg_cache_info(self) -> Optional[Dict]:
2025-12-23 16:51:49 +01:00
return self.epg_ops.get_epg_cache_info()
2025-12-04 10:22:54 +01:00
def get_epg_mapping_stats(self) -> Dict:
2025-12-23 16:51:49 +01:00
return self.epg_ops.get_epg_mapping_stats()
2025-12-04 10:22:54 +01:00
def has_epg_mapping(self, provider_name: str, channel_id: str) -> bool:
2025-12-23 16:51:49 +01:00
return self.epg_ops.has_epg_mapping(provider_name, channel_id)
2025-12-07 20:43:28 +01:00
2025-12-23 16:51:49 +01:00
# ==========================================================================
# DRM OPERATIONS (delegate to DRMOperations)
# ==========================================================================
2025-12-07 20:43:28 +01:00
2026-01-16 12:24:41 +01:00
def get_channel_drm_configs(self, provider_name: str, channel_id: str, **kwargs) -> List:
2025-12-23 16:51:49 +01:00
return self.drm_ops.get_channel_drm_configs(provider_name, channel_id, **kwargs)
2025-12-07 20:43:28 +01:00
2025-12-23 16:51:49 +01:00
def list_drm_plugins(self) -> Dict:
return self.drm_ops.list_drm_plugins()
2025-12-07 20:43:28 +01:00
2025-12-23 16:51:49 +01:00
def clear_drm_plugins(self):
self.drm_ops.clear_drm_plugins()
2025-12-07 20:43:28 +01:00
2025-12-23 16:51:49 +01:00
# ==========================================================================
# CATCHUP OPERATIONS (delegate to CatchupOperations)
# ==========================================================================
2025-12-07 20:43:28 +01:00
2026-01-06 16:41:03 +01:00
def get_catchup_manifest(
2026-02-15 18:16:41 +01:00
self,
provider_name: str,
channel_id: str,
start_time: int,
end_time: int,
epg_id: Optional[str] = None,
country: Optional[str] = None,
2026-01-06 16:41:03 +01:00
) -> Optional[str]:
2025-12-23 16:51:49 +01:00
return self.catchup_ops.get_catchup_manifest(
provider_name, channel_id, start_time, end_time, epg_id, country
)
2025-12-07 20:43:28 +01:00
2026-01-06 16:41:03 +01:00
def get_catchup_drm_configs(
2026-02-15 18:16:41 +01:00
self,
provider_name: str,
channel_id: str,
start_time: int,
end_time: int,
epg_id: Optional[str] = None,
country: Optional[str] = None,
2026-01-06 16:41:03 +01:00
) -> List:
2025-12-23 16:51:49 +01:00
return self.catchup_ops.get_catchup_drm_configs(
provider_name, channel_id, start_time, end_time, epg_id, country
)
2025-12-07 20:43:28 +01:00
2026-01-16 12:24:41 +01:00
def get_catchup_window(self, provider_name: str, channel_id: Optional[str] = None) -> int:
2025-12-23 16:51:49 +01:00
return self.catchup_ops.get_catchup_window(provider_name, channel_id)
2025-12-07 20:43:28 +01:00
def supports_catchup(self, provider_name: str) -> bool:
2025-12-23 16:51:49 +01:00
return self.catchup_ops.supports_catchup(provider_name)
2025-12-07 20:43:28 +01:00
2025-12-23 16:51:49 +01:00
def get_all_catchup_capabilities(self) -> Dict[str, Dict]:
return self.catchup_ops.get_all_catchup_capabilities()
2025-12-07 20:43:28 +01:00
2025-12-23 16:51:49 +01:00
# ==========================================================================
# SUBSCRIPTION OPERATIONS (delegate to SubscriptionOperations)
# ==========================================================================
2025-12-07 20:43:28 +01:00
2025-12-23 16:51:49 +01:00
def get_subscription_status(self, provider_name: str, **kwargs):
return self.subscription_ops.get_subscription_status(provider_name, **kwargs)
2025-12-07 20:43:28 +01:00
2025-12-23 16:51:49 +01:00
def get_subscribed_channels(self, provider_name: str, **kwargs):
return self.subscription_ops.get_subscribed_channels(provider_name, **kwargs)
2025-12-07 20:43:28 +01:00
2025-12-23 16:51:49 +01:00
def get_available_packages(self, provider_name: str, **kwargs):
return self.subscription_ops.get_available_packages(provider_name, **kwargs)
2025-12-22 14:32:36 +01:00
2025-12-23 16:51:49 +01:00
def is_channel_accessible(self, provider_name: str, channel_id: str, **kwargs):
2026-01-16 12:24:41 +01:00
return self.subscription_ops.is_channel_accessible(provider_name, channel_id, **kwargs)
2025-12-22 14:32:36 +01:00
2025-12-23 16:51:49 +01:00
# ==========================================================================
# UTILITY METHODS (remain in ProviderManager as helpers)
# ==========================================================================
2025-12-22 14:32:36 +01:00
2025-12-23 16:51:49 +01:00
def get_provider_http_manager(self, provider_name: str):
"""Get HTTP manager for a specific provider."""
2025-12-22 14:32:36 +01:00
provider = self.get_provider(provider_name)
if not provider:
2025-12-23 16:51:49 +01:00
logger.warning(f"ProviderManager: Provider '{provider_name}' not found")
2025-12-22 14:32:36 +01:00
return None
2025-12-23 16:51:49 +01:00
http_manager = provider.http_manager
if not http_manager:
2026-01-16 12:24:41 +01:00
logger.warning(f"ProviderManager: Provider '{provider_name}' has no HTTP manager")
2025-12-23 16:51:49 +01:00
return None
2025-12-22 14:32:36 +01:00
2025-12-23 16:51:49 +01:00
logger.debug(f"ProviderManager: Retrieved HTTP manager for '{provider_name}'")
return http_manager
2025-12-22 14:32:36 +01:00
2025-12-23 16:51:49 +01:00
def needs_proxy(self, provider_name: str) -> bool:
"""Check if a provider needs proxy support."""
http_manager = self.get_provider_http_manager(provider_name)
if not http_manager:
return False
2025-12-22 14:32:36 +01:00
2025-12-23 16:51:49 +01:00
has_proxy = http_manager.config.proxy_config is not None
logger.debug(
2026-01-06 16:41:03 +01:00
f"ProviderManager: Provider '{provider_name}' {'requires' if has_proxy else 'does not require'} proxy"
)
2025-12-23 16:51:49 +01:00
return has_proxy
2025-12-22 14:32:36 +01:00
2025-12-23 16:51:49 +01:00
def get_provider_choices(self) -> Dict[int, str]:
2025-12-22 14:32:36 +01:00
"""
2025-12-23 16:51:49 +01:00
Get numbered provider choices for user selection.
Includes an 'all' option as the last choice.
"""
enabled_providers = self.registry.get_enabled_providers()
choices = {i + 1: name for i, name in enumerate(enabled_providers)}
2026-01-06 16:41:03 +01:00
choices[len(choices) + 1] = "all"
2025-12-23 16:51:49 +01:00
return choices
2025-12-22 14:32:36 +01:00
2025-12-23 16:51:49 +01:00
def get_selected_providers(self, choices_input: str) -> List[str]:
2025-12-22 14:32:36 +01:00
"""
2025-12-23 16:51:49 +01:00
Convert user input string to list of provider names.
2025-12-22 14:32:36 +01:00
Args:
2025-12-23 16:51:49 +01:00
choices_input: Comma-separated string of choice numbers
2025-12-22 14:32:36 +01:00
Returns:
2025-12-23 16:51:49 +01:00
List of selected provider names
2025-12-22 14:32:36 +01:00
"""
2025-12-23 16:51:49 +01:00
available = self.registry.get_enabled_providers()
2025-12-22 14:32:36 +01:00
2025-12-23 16:51:49 +01:00
if not available:
2026-01-16 12:24:41 +01:00
logger.warning("ProviderManager: No enabled providers available for selection")
2025-10-29 20:23:50 +01:00
return []
2025-12-23 16:51:49 +01:00
selected = []
2026-01-06 16:41:03 +01:00
for choice in choices_input.split(","):
2025-12-23 16:51:49 +01:00
choice = choice.strip()
if choice.isdigit():
idx = int(choice) - 1
if 0 <= idx < len(available):
selected.append(available[idx])
elif idx == len(available): # 'all' option
selected = available.copy()
break
2025-10-29 20:23:50 +01:00
2025-12-23 16:51:49 +01:00
result = selected if selected else available
2026-01-06 16:41:03 +01:00
logger.debug(
f"ProviderManager: Selected {len(result)} providers from input '{choices_input}'"
)
2025-10-29 20:23:50 +01:00
return result
2025-12-23 16:51:49 +01:00
# ==========================================================================
# LEGACY COMPATIBILITY METHODS
# ==========================================================================
2025-12-11 21:08:03 +01:00
2025-12-23 16:51:49 +01:00
def _extract_pssh_from_manifest(self, manifest_url: str) -> List:
2025-12-11 21:08:03 +01:00
"""
2025-12-23 16:51:49 +01:00
Extract PSSH data from a manifest URL.
Kept for backward compatibility but delegated to DRMOperations.
2025-12-11 21:08:03 +01:00
"""
2026-02-15 18:16:41 +01:00
return self.drm_ops._extract_pssh_from_manifest(manifest_url)