updated to lastest version

This commit is contained in:
UrloMythus
2025-09-01 18:41:27 +02:00
parent bc41be6194
commit 8f8c3b195e
21 changed files with 2389 additions and 390 deletions

View File

@@ -1,10 +1,14 @@
from typing import Annotated
from urllib.parse import quote
from urllib.parse import quote, unquote
import re
import logging
from fastapi import Request, Depends, APIRouter, Query, HTTPException
from fastapi.responses import Response, RedirectResponse
from mediaflow_proxy.handlers import (
handle_hls_stream_proxy,
handle_stream_request,
proxy_stream,
get_manifest,
get_playlist,
@@ -18,10 +22,144 @@ from mediaflow_proxy.schemas import (
MPDManifestParams,
)
from mediaflow_proxy.utils.http_utils import get_proxy_headers, ProxyRequestHeaders
from mediaflow_proxy.utils.base64_utils import process_potential_base64_url
proxy_router = APIRouter()
def sanitize_url(url: str) -> str:
"""
Sanitize URL to fix common encoding issues and handle base64 encoded URLs.
Args:
url (str): The URL to sanitize.
Returns:
str: The sanitized URL.
"""
logger = logging.getLogger(__name__)
original_url = url
# First, try to process potential base64 encoded URLs
url = process_potential_base64_url(url)
# Fix malformed URLs where https%22// should be https://
url = re.sub(r'https%22//', 'https://', url)
url = re.sub(r'http%22//', 'http://', url)
# Fix malformed URLs where https%3A%22// should be https://
url = re.sub(r'https%3A%22//', 'https://', url)
url = re.sub(r'http%3A%22//', 'http://', url)
# Fix malformed URLs where https:"// should be https:// (after partial decoding)
url = re.sub(r'https:"//', 'https://', url)
url = re.sub(r'http:"//', 'http://', url)
# Fix URLs where key_id and key parameters are incorrectly appended to the base URL
# This happens when the URL contains &key_id= and &key= which should be handled as proxy parameters
if '&key_id=' in url and '&key=' in url:
# Split the URL at the first occurrence of &key_id= to separate the base URL from the incorrectly appended parameters
base_url = url.split('&key_id=')[0]
logger.info(f"Removed incorrectly appended key parameters from URL: '{url}' -> '{base_url}'")
url = base_url
# Log if URL was changed
if url != original_url:
logger.info(f"URL sanitized: '{original_url}' -> '{url}'")
# Also try URL decoding to see what we get
try:
decoded_url = unquote(url)
if decoded_url != url:
logger.info(f"URL after decoding: '{decoded_url}'")
# If after decoding we still have malformed protocol, fix it
if ':"/' in decoded_url:
# Fix https:"// or http:"// patterns
fixed_decoded = re.sub(r'([a-z]+):"//', r'\1://', decoded_url)
logger.info(f"Fixed decoded URL: '{fixed_decoded}'")
return fixed_decoded
except Exception as e:
logger.warning(f"Error decoding URL '{url}': {e}")
return url
def extract_drm_params_from_url(url: str) -> tuple[str, str, str]:
"""
Extract DRM parameters (key_id and key) from a URL if they are incorrectly appended.
Args:
url (str): The URL that may contain appended DRM parameters.
Returns:
tuple: (clean_url, key_id, key) where clean_url has the parameters removed,
and key_id/key are the extracted values (or None if not found).
"""
logger = logging.getLogger(__name__)
key_id = None
key = None
clean_url = url
# Check if URL contains incorrectly appended key_id and key parameters
if '&key_id=' in url and '&key=' in url:
# Extract key_id
key_id_match = re.search(r'&key_id=([^&]+)', url)
if key_id_match:
key_id = key_id_match.group(1)
# Extract key
key_match = re.search(r'&key=([^&]+)', url)
if key_match:
key = key_match.group(1)
# Remove the parameters from the URL
clean_url = re.sub(r'&key_id=[^&]*', '', url)
clean_url = re.sub(r'&key=[^&]*', '', clean_url)
logger.info(f"Extracted DRM parameters from URL: key_id={key_id}, key={key}")
logger.info(f"Cleaned URL: '{url}' -> '{clean_url}'")
return clean_url, key_id, key
def _check_and_redirect_dlhd_stream(request: Request, destination: str) -> RedirectResponse | None:
"""
Check if destination contains stream-{numero} pattern and redirect to extractor if needed.
Args:
request (Request): The incoming HTTP request.
destination (str): The destination URL to check.
Returns:
RedirectResponse | None: RedirectResponse if redirect is needed, None otherwise.
"""
import re
# Check for stream-{numero} pattern (e.g., stream-1, stream-123, etc.)
if re.search(r'stream-\d+', destination):
from urllib.parse import urlencode
# Build redirect URL to extractor
redirect_params = {
"host": "DLHD",
"redirect_stream": "true",
"d": destination
}
# Preserve api_password if present
if "api_password" in request.query_params:
redirect_params["api_password"] = request.query_params["api_password"]
# Build the redirect URL
base_url = str(request.url_for("extract_url"))
redirect_url = f"{base_url}?{urlencode(redirect_params)}"
return RedirectResponse(url=redirect_url, status_code=302)
return None
@proxy_router.head("/hls/manifest.m3u8")
@proxy_router.head("/hls/manifest.m3u8")
@proxy_router.get("/hls/manifest.m3u8")
async def hls_manifest_proxy(
@@ -40,9 +178,111 @@ async def hls_manifest_proxy(
Returns:
Response: The HTTP response with the processed m3u8 playlist or streamed content.
"""
# Sanitize destination URL to fix common encoding issues
hls_params.destination = sanitize_url(hls_params.destination)
# Check if destination contains stream-{numero} pattern and redirect to extractor
redirect_response = _check_and_redirect_dlhd_stream(request, hls_params.destination)
if redirect_response:
return redirect_response
return await handle_hls_stream_proxy(request, hls_params, proxy_headers)
@proxy_router.get("/hls/segment")
async def hls_segment_proxy(
request: Request,
proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
segment_url: str = Query(..., description="URL of the HLS segment"),
):
"""
Proxy HLS segments with optional pre-buffering support.
Args:
request (Request): The incoming HTTP request.
segment_url (str): URL of the HLS segment to proxy.
proxy_headers (ProxyRequestHeaders): The headers to include in the request.
Returns:
Response: The HTTP response with the segment content.
"""
from mediaflow_proxy.utils.hls_prebuffer import hls_prebuffer
from mediaflow_proxy.configs import settings
# Sanitize segment URL to fix common encoding issues
segment_url = sanitize_url(segment_url)
# Extract headers for pre-buffering
headers = {}
for key, value in request.query_params.items():
if key.startswith("h_"):
headers[key[2:]] = value
# Try to get segment from pre-buffer cache first
if settings.enable_hls_prebuffer:
cached_segment = await hls_prebuffer.get_segment(segment_url, headers)
if cached_segment:
return Response(
content=cached_segment,
media_type="video/mp2t",
headers={
"Content-Type": "video/mp2t",
"Cache-Control": "public, max-age=3600",
"Access-Control-Allow-Origin": "*"
}
)
# Fallback to direct streaming if not in cache
return await handle_stream_request("GET", segment_url, proxy_headers)
@proxy_router.get("/dash/segment")
async def dash_segment_proxy(
request: Request,
proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
segment_url: str = Query(..., description="URL of the DASH segment"),
):
"""
Proxy DASH segments with optional pre-buffering support.
Args:
request (Request): The incoming HTTP request.
segment_url (str): URL of the DASH segment to proxy.
proxy_headers (ProxyRequestHeaders): The headers to include in the request.
Returns:
Response: The HTTP response with the segment content.
"""
from mediaflow_proxy.utils.dash_prebuffer import dash_prebuffer
from mediaflow_proxy.configs import settings
# Sanitize segment URL to fix common encoding issues
segment_url = sanitize_url(segment_url)
# Extract headers for pre-buffering
headers = {}
for key, value in request.query_params.items():
if key.startswith("h_"):
headers[key[2:]] = value
# Try to get segment from pre-buffer cache first
if settings.enable_dash_prebuffer:
cached_segment = await dash_prebuffer.get_segment(segment_url, headers)
if cached_segment:
return Response(
content=cached_segment,
media_type="video/mp4",
headers={
"Content-Type": "video/mp4",
"Cache-Control": "public, max-age=3600",
"Access-Control-Allow-Origin": "*"
}
)
# Fallback to direct streaming if not in cache
return await handle_stream_request("GET", segment_url, proxy_headers)
@proxy_router.head("/stream")
@proxy_router.get("/stream")
@proxy_router.head("/stream/{filename:path}")
@@ -65,6 +305,14 @@ async def proxy_stream_endpoint(
Returns:
Response: The HTTP response with the streamed content.
"""
# Sanitize destination URL to fix common encoding issues
destination = sanitize_url(destination)
# Check if destination contains stream-{numero} pattern and redirect to extractor
redirect_response = _check_and_redirect_dlhd_stream(request, destination)
if redirect_response:
return redirect_response
content_range = proxy_headers.request.get("range", "bytes=0-")
if "nan" in content_range.casefold():
# Handle invalid range requests "bytes=NaN-NaN"
@@ -103,6 +351,21 @@ async def mpd_manifest_proxy(
Returns:
Response: The HTTP response with the HLS manifest.
"""
# Extract DRM parameters from destination URL if they are incorrectly appended
clean_url, extracted_key_id, extracted_key = extract_drm_params_from_url(manifest_params.destination)
# Update the destination with the cleaned URL
manifest_params.destination = clean_url
# Use extracted parameters if they exist and the manifest params don't already have them
if extracted_key_id and not manifest_params.key_id:
manifest_params.key_id = extracted_key_id
if extracted_key and not manifest_params.key:
manifest_params.key = extracted_key
# Sanitize destination URL to fix common encoding issues
manifest_params.destination = sanitize_url(manifest_params.destination)
return await get_manifest(request, manifest_params, proxy_headers)
@@ -123,6 +386,21 @@ async def playlist_endpoint(
Returns:
Response: The HTTP response with the HLS playlist.
"""
# Extract DRM parameters from destination URL if they are incorrectly appended
clean_url, extracted_key_id, extracted_key = extract_drm_params_from_url(playlist_params.destination)
# Update the destination with the cleaned URL
playlist_params.destination = clean_url
# Use extracted parameters if they exist and the playlist params don't already have them
if extracted_key_id and not playlist_params.key_id:
playlist_params.key_id = extracted_key_id
if extracted_key and not playlist_params.key:
playlist_params.key = extracted_key
# Sanitize destination URL to fix common encoding issues
playlist_params.destination = sanitize_url(playlist_params.destination)
return await get_playlist(request, playlist_params, proxy_headers)
@@ -152,4 +430,4 @@ async def get_mediaflow_proxy_public_ip():
Returns:
Response: The HTTP response with the public IP address in the form of a JSON object. {"ip": "xxx.xxx.xxx.xxx"}
"""
return await get_public_ip()
return await get_public_ip()