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

View File

@@ -67,30 +67,24 @@ class EncryptionMiddleware(BaseHTTPMiddleware):
encrypted_token = None
# Check for token in path
if token_marker in path and self.encryption_handler:
if path.startswith(token_marker) and self.encryption_handler:
try:
# Extract token from path
token_start = path.find(token_marker) + len(token_marker)
# Extract token from the beginning of the path
token_start = len(token_marker)
token_end = path.find("/", token_start)
if token_end == -1: # No trailing slash (no filename after token)
token_end = len(path)
filename_part = ""
if token_end == -1: # No trailing slash
encrypted_token = path[token_start:]
remaining_path = ""
else:
# There's something after the token (likely a filename)
filename_part = path[token_end:]
encrypted_token = path[token_start:token_end]
remaining_path = path[token_end:]
# Get the encrypted token
encrypted_token = path[token_start:token_end]
# 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
# Modify the path to remove the token part
request.scope["path"] = remaining_path
# Update the raw path as well
request.scope["raw_path"] = original_path.encode()
request.scope["raw_path"] = remaining_path.encode()
except Exception as 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
if encryption_handler:
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
if filename:
path_parts.append(parse.quote(filename))
return "/".join(path_parts)
url = f"{url}/{parse.quote(filename)}"
return url
else:
# No encryption, use regular query parameters
url = base_url

View File

@@ -233,8 +233,21 @@ def parse_representation(
profile["audioSamplingRate"] = representation.get("@audioSamplingRate") or adaptation.get("@audioSamplingRate")
profile["channels"] = representation.get("AudioChannelConfiguration", {}).get("@value", "2")
else:
profile["width"] = int(representation["@width"])
profile["height"] = int(representation["@height"])
# Handle video-specific attributes, making them optional with sensible defaults
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 = 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)
@@ -317,7 +330,9 @@ def parse_segment_timeline(parsed_dict: dict, item: dict, profile: dict, source:
"""
timelines = item["SegmentTimeline"]["S"]
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))
start_number = int(item.get("@startNumber", 1))
@@ -354,13 +369,14 @@ def preprocess_timeline(
for _ in range(repeat + 1):
segment_start_time = period_start + timedelta(seconds=(start_time - presentation_time_offset) / timescale)
segment_end_time = segment_start_time + timedelta(seconds=duration / timescale)
presentation_time = start_time - presentation_time_offset
processed_data.append(
{
"number": start_number,
"start_time": segment_start_time,
"end_time": segment_end_time,
"duration": duration,
"time": start_time,
"time": presentation_time,
}
)
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"]))
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"):
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:
duration = segment["duration"]
duration_seconds = segment["duration"] / timescale
segment_data.update(
{
"start_time": segment["start_time"],
"end_time": segment["start_time"] + timedelta(seconds=duration),
"extinf": duration,
"end_time": segment["start_time"] + timedelta(seconds=duration_seconds),
"extinf": duration_seconds,
"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:
# If no timescale is provided, assume duration is already in seconds
segment_data["extinf"] = segment["duration"]
return segment_data