This commit is contained in:
Nirvana
2026-01-23 20:16:58 +01:00
parent 95b3b19213
commit f192504fc6
6 changed files with 54 additions and 34 deletions
+6 -2
View File
@@ -57,7 +57,9 @@ class AuthStatus:
result = {
"provider": (
f"{self.provider_name}_{self.country}" if self.country else self.provider_name
f"{self.provider_name}_{self.country}"
if self.country
else self.provider_name
),
"provider_name": self.provider_name,
"provider_label": self.provider_label,
@@ -99,7 +101,9 @@ class AuthStatus:
result["refresh_token_expires_at"] = self.refresh_token_expires_at
if self.refresh_token_expires_in_seconds is not None:
result["refresh_token_expires_in_seconds"] = self.refresh_token_expires_in_seconds
result["refresh_token_expires_in_seconds"] = (
self.refresh_token_expires_in_seconds
)
return result
@@ -12,6 +12,7 @@ class DRMSystem(str, Enum):
CLEARKEY = "org.w3.clearkey"
FAIRPLAY = "com.apple.fps"
GENERIC = "generic"
NONE = "none"
@property
def system_uuid(self) -> str:
@@ -23,6 +24,7 @@ class DRMSystem(str, Enum):
self.WISEPLAY: "3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c",
self.FAIRPLAY: "94ce86fb-07ff-4f43-adb8-93d2fa968ca2",
self.GENERIC: "", # No UUID for generic plugins
self.NONE: "", # No UUID for unencrypted
}
return uuid_mapping.get(self, "")
@@ -137,7 +139,9 @@ class LicenseConfig:
"""Helper to ensure req_data is base64 encoded"""
import base64
req_data_encoded = base64.b64encode(req_data_template.encode("utf-8")).decode("utf-8")
req_data_encoded = base64.b64encode(req_data_template.encode("utf-8")).decode(
"utf-8"
)
return cls(req_data=req_data_encoded, **kwargs)
@@ -180,7 +184,9 @@ class DRMConfig:
license_dict["unwrapper"] = self.license.unwrapper
if self.license.unwrapper_params:
license_dict["unwrapper_params"] = {
k: v for k, v in vars(self.license.unwrapper_params).items() if v is not None
k: v
for k, v in vars(self.license.unwrapper_params).items()
if v is not None
}
if self.license.keyids:
license_dict["keyids"] = self.license.keyids
@@ -400,10 +400,16 @@ class EPGEntry:
"""
if not text:
return []
return [item.strip() for item in text.split(EPG_STRING_TOKEN_SEPARATOR) if item.strip()]
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:
def encode_broadcast_id(
provider_name: str, channel_id: str, start_time: int
) -> int:
"""
Generate deterministic broadcast ID with encoded provider information.
@@ -520,12 +526,18 @@ class EPGEntry:
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:
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:
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})"
)
@@ -144,7 +144,9 @@ class ProxyConfig:
"""Create ProxyConfig from dictionary"""
auth = None
if "auth" in data and data["auth"]:
auth = ProxyAuth(username=data["auth"]["username"], password=data["auth"]["password"])
auth = ProxyAuth(
username=data["auth"]["username"], password=data["auth"]["password"]
)
scope_data = data.get("scope", {})
scope = ProxyScope(
@@ -167,7 +169,9 @@ class ProxyConfig:
)
@classmethod
def from_url(cls, proxy_url: str, scope: Optional[ProxyScope] = None) -> "ProxyConfig":
def from_url(
cls, proxy_url: str, scope: Optional[ProxyScope] = None
) -> "ProxyConfig":
"""
Create ProxyConfig from proxy URL string
@@ -248,7 +252,9 @@ class RequestConfig:
}
# Add proxy if configured and enabled for this operation
if self.proxy_config and self.proxy_config.scope.should_use_proxy_for(operation):
if self.proxy_config and self.proxy_config.scope.should_use_proxy_for(
operation
):
kwargs["proxies"] = self.proxy_config.to_proxy_dict()
return kwargs
@@ -282,7 +282,9 @@ class StreamingChannel:
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]:
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
+12 -22
View File
@@ -699,25 +699,17 @@ class UltimateService:
provider_name=provider_name, channel_id=channel_id
)
# Check if channel has ClearKey DRM
# Check if channel has ClearKey DRM or is unencrypted
has_clearkey = False
is_unencrypted = False
clearkey_data = None
if (
isinstance(drm_configs, dict)
and "org.w3.clearkey" in drm_configs
):
clearkey_data = drm_configs["org.w3.clearkey"]
has_clearkey = True
elif isinstance(drm_configs, list):
# Legacy format - convert to dict
for config in drm_configs:
if hasattr(config, "to_dict"):
config_dict = config.to_dict()
if "org.w3.clearkey" in config_dict:
clearkey_data = config_dict["org.w3.clearkey"]
has_clearkey = True
break
if isinstance(drm_configs, dict):
if "org.w3.clearkey" in drm_configs:
clearkey_data = drm_configs["org.w3.clearkey"]
has_clearkey = True
elif "none" in drm_configs:
is_unencrypted = True
if has_clearkey and clearkey_data:
# Channel has ClearKey - generate decrypted entry
@@ -731,19 +723,17 @@ class UltimateService:
)
m3u_content += entry_content
channels_included += 1
elif not drm_configs or (
isinstance(drm_configs, dict) and len(drm_configs) == 0
):
# Unencrypted channel - include with direct stream URL
elif is_unencrypted:
# Explicitly unencrypted channel - include with direct stream URL
stream_url = f"{base_url}/api/providers/{provider_name}/channels/{channel_id}/stream"
m3u_content += f'#EXTINF:-1 tvg-id="{channel_id}" tvg-logo="{channel_logo}" group-title="{provider_label}",{channel_name}\n'
m3u_content += f"{stream_url}\n"
channels_included += 1
else:
# Channel has other DRM (not ClearKey) - skip
# Channel has other DRM, is inaccessible, or unknown - skip
channels_skipped += 1
logger.debug(
f"Skipping {provider_name}/{channel_id} - no ClearKey DRM"
f"Skipping {provider_name}/{channel_id} - unsupported DRM or no access"
)
except Exception as drm_err: