diff --git a/backend/main.py b/backend/main.py index 1aa0b1b..00f78da 100644 --- a/backend/main.py +++ b/backend/main.py @@ -126,14 +126,43 @@ def stream_video(): def is_hls(url): return '.m3u8' in urllib.parse.urlparse(url).path + def is_dash(url): + return urllib.parse.urlparse(url).path.lower().endswith('.mpd') + + def guess_content_type(url): + path = urllib.parse.urlparse(url).path.lower() + if path.endswith('.m3u8'): + return 'application/vnd.apple.mpegurl' + if path.endswith('.mpd'): + return 'application/dash+xml' + if path.endswith('.mp4') or path.endswith('.m4v') or path.endswith('.m4s'): + return 'video/mp4' + if path.endswith('.webm'): + return 'video/webm' + if path.endswith('.ts'): + return 'video/mp2t' + if path.endswith('.mov'): + return 'video/quicktime' + if path.endswith('.m4a'): + return 'audio/mp4' + if path.endswith('.mp3'): + return 'audio/mpeg' + if path.endswith('.ogg') or path.endswith('.oga'): + return 'audio/ogg' + return None + def is_direct_media(url): path = urllib.parse.urlparse(url).path.lower() return any(path.endswith(ext) for ext in ('.mp4', '.m4v', '.m4s', '.ts', '.webm', '.mov')) def proxy_response(target_url, content_type_override=None): # Extract the base domain to spoof the referer - parsed_uri = urllib.parse.urlparse(target_url) - referer = f"{parsed_uri.scheme}://{parsed_uri.netloc}/" + referer_override = request.args.get('referer') + if referer_override: + referer = referer_override + else: + parsed_uri = urllib.parse.urlparse(target_url) + referer = f"{parsed_uri.scheme}://{parsed_uri.netloc}/" safe_request_headers = { 'User-Agent': request.headers.get('User-Agent'), @@ -153,16 +182,23 @@ def stream_video(): } forwarded_headers = [] + response_content_type = None for name, value in resp.headers.items(): if name.lower() in hop_by_hop: continue if name.lower() == 'content-length': forwarded_headers.append((name, value)) continue + if name.lower() == 'content-type': + response_content_type = value if name.lower() == 'content-type' and content_type_override: continue forwarded_headers.append((name, value)) + if not content_type_override: + if not response_content_type or 'application/octet-stream' in response_content_type: + content_type_override = guess_content_type(target_url) + if content_type_override: forwarded_headers.append(('Content-Type', content_type_override)) @@ -180,7 +216,7 @@ def stream_video(): return Response(generate(), status=resp.status_code, headers=forwarded_headers) - def proxy_hls_playlist(playlist_url): + def proxy_hls_playlist(playlist_url, referer_hint=None): headers = { 'User-Agent': request.headers.get('User-Agent', 'Mozilla/5.0'), 'Accept': request.headers.get('Accept', '*/*') @@ -188,16 +224,30 @@ def stream_video(): resp = session.get(playlist_url, headers=headers, timeout=30) resp.raise_for_status() base_url = resp.url + if referer_hint: + referer = referer_hint + else: + referer = f"{urllib.parse.urlparse(base_url).scheme}://{urllib.parse.urlparse(base_url).netloc}/" + + def proxied_url(target): + absolute = urljoin(base_url, target) + return f"/api/stream?url={urllib.parse.quote(absolute, safe='')}&referer={urllib.parse.quote(referer, safe='')}" + lines = resp.text.splitlines() rewritten = [] for line in lines: stripped = line.strip() if not stripped or stripped.startswith('#'): + # Rewrite URI attributes inside tags (keys/maps) + if 'URI="' in line: + def repl(match): + uri = match.group(1) + return f'URI="{proxied_url(uri)}"' + import re + line = re.sub(r'URI="([^"]+)"', repl, line) rewritten.append(line) continue - absolute = urljoin(base_url, stripped) - proxied = f"/api/stream?url={urllib.parse.quote(absolute, safe='')}" - rewritten.append(proxied) + rewritten.append(proxied_url(stripped)) if request.method == 'HEAD': return Response("", status=200, content_type='application/vnd.apple.mpegurl') @@ -206,7 +256,11 @@ def stream_video(): if is_hls(video_url): try: - return proxy_hls_playlist(video_url) + referer_hint = request.args.get('referer') + if not referer_hint: + parsed = urllib.parse.urlparse(video_url) + referer_hint = f"{parsed.scheme}://{parsed.netloc}/" + return proxy_hls_playlist(video_url, referer_hint) except Exception as e: return jsonify({"error": str(e)}), 500 @@ -219,7 +273,8 @@ def stream_video(): try: # Configure yt-dlp options ydl_opts = { - 'format': 'best[ext=mp4]/best[vcodec^=avc1]/best[vcodec^=vp]/best', + # 'format': 'best[protocol*=m3u8]/best[ext=mp4]/best', + 'format_sort': ['res', 'fps', 'vcodec:avc1', 'acodec:aac'], 'quiet': True, 'no_warnings': True, 'socket_timeout': 30, @@ -237,6 +292,7 @@ def stream_video(): # Try to get the URL from the info dict (works for progressive downloads) stream_url = info.get('url') + protocol = info.get('protocol') # If no direct URL, try to get it from formats if not stream_url and 'formats' in info: @@ -249,8 +305,21 @@ def stream_video(): if not stream_url: return jsonify({"error": "Could not extract stream URL"}), 500 + referer_hint = None + if isinstance(info.get('http_headers'), dict): + referer_hint = info['http_headers'].get('Referer') or info['http_headers'].get('referer') + if not referer_hint: + parsed = urllib.parse.urlparse(video_url) + referer_hint = f"{parsed.scheme}://{parsed.netloc}/" + + if protocol and 'm3u8' in protocol: + return proxy_hls_playlist(stream_url, referer_hint) + if is_hls(stream_url): - return proxy_hls_playlist(stream_url) + return proxy_hls_playlist(stream_url, referer_hint) + + if is_dash(stream_url): + return proxy_response(stream_url, content_type_override='application/dash+xml') return proxy_response(stream_url) except Exception as e: