Update to newest version

This commit is contained in:
UrloMythus
2025-06-15 19:20:01 +02:00
parent 9431837e6c
commit 7a1e96d8b1
9 changed files with 98 additions and 51 deletions

View File

@@ -30,46 +30,70 @@ class DLHDExtractor(BaseExtractor):
try:
# Channel URL is required and serves as the referer
channel_url = url
player_origin = self._get_origin(channel_url)
channel_origin = self._get_origin(channel_url) # Channel page origin
# Check for direct parameters
player_url = kwargs.get("player_url")
stream_url = kwargs.get("stream_url")
auth_url_base = kwargs.get("auth_url_base")
player_url_from_arg = kwargs.get("player_url")
stream_url_from_arg = kwargs.get("stream_url")
auth_url_base_from_arg = kwargs.get("auth_url_base")
current_player_url_for_processing: str
# If player URL not provided, extract it from channel page
if not player_url:
if not player_url_from_arg:
# Get the channel page to extract the player iframe URL
channel_headers = {
"referer": player_origin + "/",
"origin": player_origin,
"referer": channel_origin + "/",
"origin": channel_origin,
"user-agent": self.base_headers["user-agent"],
}
channel_response = await self._make_request(channel_url, headers=channel_headers)
player_url = self._extract_player_url(channel_response.text)
extracted_iframe_url = self._extract_player_url(channel_response.text)
if not player_url:
if not extracted_iframe_url:
raise ExtractorError("Could not extract player URL from channel page")
current_player_url_for_processing = extracted_iframe_url
else:
current_player_url_for_processing = player_url_from_arg
if not re.search(r"/stream/([a-zA-Z0-9-]+)", player_url):
iframe_player_url = await self._handle_playnow(player_url, player_origin)
player_origin = self._get_origin(player_url)
player_url = iframe_player_url
# Attempt 1: _handle_vecloud with current_player_url_for_processing
# The referer for _handle_vecloud is the origin of the channel page (channel_origin)
# or the origin of the player itself if it is a /stream/ URL.
try:
return await self._handle_vecloud(player_url, player_origin + "/")
except Exception as e:
pass
referer_for_vecloud = channel_origin + "/"
if re.search(r"/stream/([a-zA-Z0-9-]+)", current_player_url_for_processing):
referer_for_vecloud = self._get_origin(current_player_url_for_processing) + "/"
return await self._handle_vecloud(current_player_url_for_processing, referer_for_vecloud)
except Exception:
pass # Fail, Continue
# Attempt 2: If _handle_vecloud fail and the URL is not /stream/, try _handle_playnow
# and then _handle_vecloud again with the URL resulting from playnow.
if not re.search(r"/stream/([a-zA-Z0-9-]+)", current_player_url_for_processing):
try:
playnow_derived_player_url = await self._handle_playnow(current_player_url_for_processing, channel_origin + "/")
if re.search(r"/stream/([a-zA-Z0-9-]+)", playnow_derived_player_url):
try:
referer_for_vecloud_after_playnow = self._get_origin(playnow_derived_player_url) + "/"
return await self._handle_vecloud(playnow_derived_player_url, referer_for_vecloud_after_playnow)
except Exception:
pass
except Exception:
pass
# If all previous attempts have failed, proceed with standard authentication.
player_url_for_auth = current_player_url_for_processing
player_origin_for_auth = self._get_origin(player_url_for_auth)
# Get player page to extract authentication information
player_headers = {
"referer": player_origin + "/",
"origin": player_origin,
"referer": player_origin_for_auth + "/",
"origin": player_origin_for_auth,
"user-agent": self.base_headers["user-agent"],
}
player_response = await self._make_request(player_url, headers=player_headers)
player_response = await self._make_request(player_url_for_auth, headers=player_headers)
player_content = player_response.text
# Extract authentication details from script tag
@@ -78,62 +102,63 @@ class DLHDExtractor(BaseExtractor):
raise ExtractorError("Failed to extract authentication data from player")
# Extract auth URL base if not provided
if not auth_url_base:
auth_url_base = self._extract_auth_url_base(player_content)
final_auth_url_base = auth_url_base_from_arg
if not final_auth_url_base:
final_auth_url_base = self._extract_auth_url_base(player_content)
# If still no auth URL base, try to derive from stream URL or player URL
if not auth_url_base:
if stream_url:
auth_url_base = self._get_origin(stream_url)
if not final_auth_url_base:
if stream_url_from_arg:
final_auth_url_base = self._get_origin(stream_url_from_arg)
else:
# Try to extract from player URL structure
player_domain = self._get_origin(player_url)
player_domain_for_auth_derive = self._get_origin(player_url_for_auth)
# Attempt to construct a standard auth domain
auth_url_base = self._derive_auth_url_base(player_domain)
final_auth_url_base = self._derive_auth_url_base(player_domain_for_auth_derive)
if not auth_url_base:
if not final_auth_url_base:
raise ExtractorError("Could not determine auth URL base")
# Construct auth URL
auth_url = (
f"{auth_url_base}/auth.php?channel_id={auth_data['channel_key']}"
f"{final_auth_url_base}/auth.php?channel_id={auth_data['channel_key']}"
f"&ts={auth_data['auth_ts']}&rnd={auth_data['auth_rnd']}"
f"&sig={quote(auth_data['auth_sig'])}"
)
# Make auth request
player_origin = self._get_origin(player_url)
auth_headers = {
"referer": player_origin + "/",
"origin": player_origin,
auth_req_headers = {
"referer": player_origin_for_auth + "/",
"origin": player_origin_for_auth,
"user-agent": self.base_headers["user-agent"],
}
auth_response = await self._make_request(auth_url, headers=auth_headers)
auth_response = await self._make_request(auth_url, headers=auth_req_headers)
# Check if authentication succeeded
if auth_response.json().get("status") != "ok":
raise ExtractorError("Authentication failed")
# If no stream URL provided, look up the server and generate the stream URL
if not stream_url:
stream_url = await self._lookup_server(
lookup_url_base=player_origin,
auth_url_base=auth_url_base,
final_stream_url = stream_url_from_arg
if not final_stream_url:
final_stream_url = await self._lookup_server(
lookup_url_base=player_origin_for_auth,
auth_url_base=final_auth_url_base,
auth_data=auth_data,
headers=auth_headers,
headers=auth_req_headers,
)
# Set up the final stream headers
stream_headers = {
"referer": player_url,
"origin": player_origin,
"referer": player_url_for_auth,
"origin": player_origin_for_auth,
"user-agent": self.base_headers["user-agent"],
}
# Return the stream URL with headers
return {
"destination_url": stream_url,
"destination_url": final_stream_url,
"request_headers": stream_headers,
"mediaflow_endpoint": self.mediaflow_endpoint,
}

View File

@@ -56,8 +56,6 @@ class VixCloudExtractor(BaseExtractor):
script = soup.find("body").find("script").text
token = re.search(r"'token':\s*'(\w+)'", script).group(1)
expires = re.search(r"'expires':\s*'(\d+)'", script).group(1)
canPlayFHD = re.search(r"window\.canPlayFHD\s*=\s*(\w+)", script).group(1)
print(script,"A")
server_url = re.search(r"url:\s*'([^']+)'", script).group(1)
if "?b=1" in server_url:
final_url = f'{server_url}&token={token}&expires={expires}'

View File

@@ -85,13 +85,19 @@ async def handle_hls_stream_proxy(
proxy_headers.request.update({"range": content_range})
try:
# If force_playlist_proxy is enabled, skip detection and directly process as m3u8
if hls_params.force_playlist_proxy:
return await fetch_and_process_m3u8(
streamer, hls_params.destination, proxy_headers, request, hls_params.key_url, hls_params.force_playlist_proxy
)
parsed_url = urlparse(hls_params.destination)
# Check if the URL is a valid m3u8 playlist or m3u file
if parsed_url.path.endswith((".m3u", ".m3u8", ".m3u_plus")) or parse_qs(parsed_url.query).get("type", [""])[
0
] in ["m3u", "m3u8", "m3u_plus"]:
return await fetch_and_process_m3u8(
streamer, hls_params.destination, proxy_headers, request, hls_params.key_url
streamer, hls_params.destination, proxy_headers, request, hls_params.key_url, hls_params.force_playlist_proxy
)
# Create initial streaming response to check content type
@@ -100,7 +106,7 @@ async def handle_hls_stream_proxy(
if "mpegurl" in response_headers.get("content-type", "").lower():
return await fetch_and_process_m3u8(
streamer, hls_params.destination, proxy_headers, request, hls_params.key_url
streamer, hls_params.destination, proxy_headers, request, hls_params.key_url, hls_params.force_playlist_proxy
)
return EnhancedStreamingResponse(
@@ -190,7 +196,7 @@ async def proxy_stream(method: str, destination: str, proxy_headers: ProxyReques
async def fetch_and_process_m3u8(
streamer: Streamer, url: str, proxy_headers: ProxyRequestHeaders, request: Request, key_url: str = None
streamer: Streamer, url: str, proxy_headers: ProxyRequestHeaders, request: Request, key_url: str = None, force_playlist_proxy: bool = None
):
"""
Fetches and processes the m3u8 playlist on-the-fly, converting it to an HLS playlist.
@@ -201,6 +207,7 @@ async def fetch_and_process_m3u8(
proxy_headers (ProxyRequestHeaders): The headers to include in the request.
request (Request): The incoming HTTP request.
key_url (str, optional): The HLS Key URL to replace the original key URL. Defaults to None.
force_playlist_proxy (bool, optional): Force all playlist URLs to be proxied through MediaFlow. Defaults to None.
Returns:
Response: The HTTP response with the processed m3u8 playlist.
@@ -211,7 +218,7 @@ async def fetch_and_process_m3u8(
await streamer.create_streaming_response(url, proxy_headers.request)
# Initialize processor and response headers
processor = M3U8Processor(request, key_url)
processor = M3U8Processor(request, key_url, force_playlist_proxy)
response_headers = {
"content-disposition": "inline",
"accept-ranges": "none",

View File

@@ -137,8 +137,10 @@ def build_hls(mpd_dict: dict, request: Request, key_id: str = None, key: str = N
# Add video streams
for profile, playlist_url in video_profiles.values():
# Only add AUDIO attribute if there are audio profiles available
audio_attr = ',AUDIO="audio"' if audio_profiles else ""
hls.append(
f'#EXT-X-STREAM-INF:BANDWIDTH={profile["bandwidth"]},RESOLUTION={profile["width"]}x{profile["height"]},CODECS="{profile["codecs"]}",FRAME-RATE={profile["frameRate"]},AUDIO="audio"'
f'#EXT-X-STREAM-INF:BANDWIDTH={profile["bandwidth"]},RESOLUTION={profile["width"]}x{profile["height"]},CODECS="{profile["codecs"]}",FRAME-RATE={profile["frameRate"]}{audio_attr}'
)
hls.append(playlist_url)

View File

@@ -64,6 +64,10 @@ class HLSManifestParams(GenericParams):
None,
description="The HLS Key URL to replace the original key URL. Defaults to None. (Useful for bypassing some sneaky protection)",
)
force_playlist_proxy: Optional[bool] = Field(
None,
description="Force all playlist URLs to be proxied through MediaFlow regardless of m3u8_content_routing setting. Useful for IPTV m3u/m3u_plus formats that don't have clear URL indicators.",
)
class MPDManifestParams(GenericParams):

View File

@@ -9,16 +9,18 @@ from mediaflow_proxy.utils.http_utils import encode_mediaflow_proxy_url, encode_
class M3U8Processor:
def __init__(self, request, key_url: str = None):
def __init__(self, request, key_url: str = None, force_playlist_proxy: bool = None):
"""
Initializes the M3U8Processor with the request and URL prefix.
Args:
request (Request): The incoming HTTP request.
key_url (HttpUrl, optional): The URL of the key server. Defaults to None.
force_playlist_proxy (bool, optional): Force all playlist URLs to be proxied through MediaFlow. Defaults to None.
"""
self.request = request
self.key_url = parse.urlparse(key_url) if key_url else None
self.force_playlist_proxy = force_playlist_proxy
self.mediaflow_proxy_url = str(
request.url_for("hls_manifest_proxy").replace(scheme=get_original_scheme(request))
)
@@ -146,8 +148,15 @@ class M3U8Processor:
# Determine routing strategy based on configuration
routing_strategy = settings.m3u8_content_routing
# Check if we should force MediaFlow proxy for all playlist URLs
if self.force_playlist_proxy:
return await self.proxy_url(full_url, base_url, use_full_url=True)
# For playlist URLs, always use MediaFlow proxy regardless of strategy
if ".m3u" in full_url:
# Check for actual playlist file extensions, not just substring matches
parsed_url = parse.urlparse(full_url)
if (parsed_url.path.endswith((".m3u", ".m3u8", ".m3u_plus")) or
parse.parse_qs(parsed_url.query).get("type", [""])[0] in ["m3u", "m3u8", "m3u_plus"]):
return await self.proxy_url(full_url, base_url, use_full_url=True)
# Route non-playlist content URLs based on strategy
@@ -191,6 +200,8 @@ class M3U8Processor:
has_encrypted = query_params.pop("has_encrypted", False)
# Remove the response headers from the query params to avoid it being added to the consecutive requests
[query_params.pop(key, None) for key in list(query_params.keys()) if key.startswith("r_")]
# Remove force_playlist_proxy to avoid it being added to subsequent requests
query_params.pop("force_playlist_proxy", None)
return encode_mediaflow_proxy_url(
self.mediaflow_proxy_url,