Update to the newest MFP version, including the Vixcloud PR

This commit is contained in:
UrloMythus
2025-05-12 19:40:35 +02:00
parent 323ca2d1b6
commit 2501ff4fd6
41 changed files with 61 additions and 37 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -15,18 +15,18 @@ class VixCloudExtractor(BaseExtractor):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.mediaflow_endpoint = "hls_manifest_proxy" self.mediaflow_endpoint = "hls_manifest_proxy"
async def version(self, domain: str) -> str: async def version(self, site_url: str) -> str:
"""Get version of VixCloud Parent Site.""" """Get version of VixCloud Parent Site."""
base_url = f"https://streamingcommunity.{domain}/richiedi-un-titolo" base_url = f"{site_url}/richiedi-un-titolo"
response = await self._make_request( response = await self._make_request(
base_url, base_url,
headers={ headers={
"Referer": f"https://streamingcommunity.{domain}/", "Referer": f"{site_url}/",
"Origin": f"https://streamingcommunity.{domain}", "Origin": f"{site_url}",
}, },
) )
if response.status_code != 200: if response.status_code != 200:
raise ExtractorError("Outdated Domain") raise ExtractorError("Outdated Url")
# Soup the response # Soup the response
soup = BeautifulSoup(response.text, "lxml", parse_only=SoupStrainer("div", {"id": "app"})) soup = BeautifulSoup(response.text, "lxml", parse_only=SoupStrainer("div", {"id": "app"}))
if soup: if soup:
@@ -39,8 +39,8 @@ class VixCloudExtractor(BaseExtractor):
async def extract(self, url: str, **kwargs) -> Dict[str, Any]: async def extract(self, url: str, **kwargs) -> Dict[str, Any]:
"""Extract Vixcloud URL.""" """Extract Vixcloud URL."""
domain = url.split("://")[1].split("/")[0].split(".")[1] site_url = url.split("/iframe")[0]
version = await self.version(domain) version = await self.version(site_url)
response = await self._make_request(url, headers={"x-inertia": "true", "x-inertia-version": version}) response = await self._make_request(url, headers={"x-inertia": "true", "x-inertia-version": version})
soup = BeautifulSoup(response.text, "lxml", parse_only=SoupStrainer("iframe")) soup = BeautifulSoup(response.text, "lxml", parse_only=SoupStrainer("iframe"))
iframe = soup.find("iframe").get("src") iframe = soup.find("iframe").get("src")

View File

@@ -67,30 +67,24 @@ class EncryptionMiddleware(BaseHTTPMiddleware):
encrypted_token = None encrypted_token = None
# Check for token in path # Check for token in path
if token_marker in path and self.encryption_handler: if path.startswith(token_marker) and self.encryption_handler:
try: try:
# Extract token from path # Extract token from the beginning of the path
token_start = path.find(token_marker) + len(token_marker) token_start = len(token_marker)
token_end = path.find("/", token_start) token_end = path.find("/", token_start)
if token_end == -1: # No trailing slash (no filename after token) if token_end == -1: # No trailing slash
token_end = len(path) encrypted_token = path[token_start:]
filename_part = "" remaining_path = ""
else: else:
# There's something after the token (likely a filename) encrypted_token = path[token_start:token_end]
filename_part = path[token_end:] remaining_path = path[token_end:]
# Get the encrypted token # Modify the path to remove the token part
encrypted_token = path[token_start:token_end] request.scope["path"] = remaining_path
# Modify the path to remove the token part but preserve the filename
original_path = path[: path.find(token_marker)]
original_path += filename_part # Add back the filename part
request.scope["path"] = original_path
# Update the raw path as well # Update the raw path as well
request.scope["raw_path"] = original_path.encode() request.scope["raw_path"] = remaining_path.encode()
except Exception as e: except Exception as e:
logging.error(f"Error processing token in path: {str(e)}") logging.error(f"Error processing token in path: {str(e)}")

View File

@@ -330,15 +330,25 @@ def encode_mediaflow_proxy_url(
# Handle encryption if needed # Handle encryption if needed
if encryption_handler: if encryption_handler:
encrypted_token = encryption_handler.encrypt_data(query_params, expiration, ip) encrypted_token = encryption_handler.encrypt_data(query_params, expiration, ip)
# Build the URL with token in path
path_parts = [base_url, f"_token_{encrypted_token}"] # Parse the base URL to get its components
parsed_url = parse.urlparse(base_url)
# Insert the token at the beginning of the path
new_path = f"/_token_{encrypted_token}{parsed_url.path}"
# Reconstruct the URL with the token at the beginning of the path
url_parts = list(parsed_url)
url_parts[2] = new_path # Update the path component
# Build the URL
url = parse.urlunparse(url_parts)
# Add filename at the end if provided # Add filename at the end if provided
if filename: if filename:
path_parts.append(parse.quote(filename)) url = f"{url}/{parse.quote(filename)}"
return "/".join(path_parts)
return url
else: else:
# No encryption, use regular query parameters # No encryption, use regular query parameters
url = base_url url = base_url

View File

@@ -233,8 +233,21 @@ def parse_representation(
profile["audioSamplingRate"] = representation.get("@audioSamplingRate") or adaptation.get("@audioSamplingRate") profile["audioSamplingRate"] = representation.get("@audioSamplingRate") or adaptation.get("@audioSamplingRate")
profile["channels"] = representation.get("AudioChannelConfiguration", {}).get("@value", "2") profile["channels"] = representation.get("AudioChannelConfiguration", {}).get("@value", "2")
else: else:
profile["width"] = int(representation["@width"]) # Handle video-specific attributes, making them optional with sensible defaults
profile["height"] = int(representation["@height"]) if "@width" in representation:
profile["width"] = int(representation["@width"])
elif "@width" in adaptation:
profile["width"] = int(adaptation["@width"])
else:
profile["width"] = 0 # Default if width is missing
if "@height" in representation:
profile["height"] = int(representation["@height"])
elif "@height" in adaptation:
profile["height"] = int(adaptation["@height"])
else:
profile["height"] = 0 # Default if height is missing
frame_rate = representation.get("@frameRate") or adaptation.get("@maxFrameRate") or "30000/1001" frame_rate = representation.get("@frameRate") or adaptation.get("@maxFrameRate") or "30000/1001"
frame_rate = frame_rate if "/" in frame_rate else f"{frame_rate}/1" frame_rate = frame_rate if "/" in frame_rate else f"{frame_rate}/1"
profile["frameRate"] = round(int(frame_rate.split("/")[0]) / int(frame_rate.split("/")[1]), 3) profile["frameRate"] = round(int(frame_rate.split("/")[0]) / int(frame_rate.split("/")[1]), 3)
@@ -317,7 +330,9 @@ def parse_segment_timeline(parsed_dict: dict, item: dict, profile: dict, source:
""" """
timelines = item["SegmentTimeline"]["S"] timelines = item["SegmentTimeline"]["S"]
timelines = timelines if isinstance(timelines, list) else [timelines] timelines = timelines if isinstance(timelines, list) else [timelines]
period_start = parsed_dict.get("availabilityStartTime", datetime.fromtimestamp(0, tz=timezone.utc)) + timedelta(seconds=parsed_dict.get("PeriodStart", 0)) period_start = parsed_dict.get("availabilityStartTime", datetime.fromtimestamp(0, tz=timezone.utc)) + timedelta(
seconds=parsed_dict.get("PeriodStart", 0)
)
presentation_time_offset = int(item.get("@presentationTimeOffset", 0)) presentation_time_offset = int(item.get("@presentationTimeOffset", 0))
start_number = int(item.get("@startNumber", 1)) start_number = int(item.get("@startNumber", 1))
@@ -354,13 +369,14 @@ def preprocess_timeline(
for _ in range(repeat + 1): for _ in range(repeat + 1):
segment_start_time = period_start + timedelta(seconds=(start_time - presentation_time_offset) / timescale) segment_start_time = period_start + timedelta(seconds=(start_time - presentation_time_offset) / timescale)
segment_end_time = segment_start_time + timedelta(seconds=duration / timescale) segment_end_time = segment_start_time + timedelta(seconds=duration / timescale)
presentation_time = start_time - presentation_time_offset
processed_data.append( processed_data.append(
{ {
"number": start_number, "number": start_number,
"start_time": segment_start_time, "start_time": segment_start_time,
"end_time": segment_end_time, "end_time": segment_end_time,
"duration": duration, "duration": duration,
"time": start_time, "time": presentation_time,
} }
) )
start_time += duration start_time += duration
@@ -475,7 +491,7 @@ def create_segment_data(segment: Dict, item: dict, profile: dict, source: str, t
media = media.replace("$Bandwidth$", str(profile["bandwidth"])) media = media.replace("$Bandwidth$", str(profile["bandwidth"]))
if "time" in segment and timescale is not None: if "time" in segment and timescale is not None:
media = media.replace("$Time$", str(int(segment["time"] * timescale))) media = media.replace("$Time$", str(int(segment["time"])))
if not media.startswith("http"): if not media.startswith("http"):
media = f"{source}/{media}" media = f"{source}/{media}"
@@ -496,16 +512,20 @@ def create_segment_data(segment: Dict, item: dict, profile: dict, source: str, t
} }
) )
elif "start_time" in segment and "duration" in segment: elif "start_time" in segment and "duration" in segment:
duration = segment["duration"] duration_seconds = segment["duration"] / timescale
segment_data.update( segment_data.update(
{ {
"start_time": segment["start_time"], "start_time": segment["start_time"],
"end_time": segment["start_time"] + timedelta(seconds=duration), "end_time": segment["start_time"] + timedelta(seconds=duration_seconds),
"extinf": duration, "extinf": duration_seconds,
"program_date_time": segment["start_time"].isoformat() + "Z", "program_date_time": segment["start_time"].isoformat() + "Z",
} }
) )
elif "duration" in segment and timescale is not None:
# Convert duration from timescale units to seconds
segment_data["extinf"] = segment["duration"] / timescale
elif "duration" in segment: elif "duration" in segment:
# If no timescale is provided, assume duration is already in seconds
segment_data["extinf"] = segment["duration"] segment_data["extinf"] = segment["duration"]
return segment_data return segment_data