mirror of
https://github.com/Viren070/mediaflow-proxy.git
synced 2025-12-01 23:22:12 +01:00
Add Support for play expired or self-signed SSL certificates server streams
This commit is contained in:
@@ -30,7 +30,7 @@ MediaFlow Proxy is a powerful and flexible solution for proxifying various types
|
||||
- Retrieve public IP address of the MediaFlow Proxy server for use with Debrid services
|
||||
- Support for HTTP/HTTPS/SOCKS5 proxy forwarding
|
||||
- Protect against unauthorized access and network bandwidth abuses
|
||||
|
||||
- Support for play expired or self-signed SSL certificates server streams
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -157,6 +157,13 @@ Once the server is running, for more details on the available endpoints and thei
|
||||
mpv "http://localhost:8888/proxy/stream?d=https://jsoncompare.org/LearningContainer/SampleFiles/Video/MP4/sample-mp4-file.mp4&api_password=your_password"
|
||||
```
|
||||
|
||||
#### Proxy HTTPS self-signed certificate Stream
|
||||
|
||||
```bash
|
||||
mpv "http://localhost:8888/proxy/stream?d=https://self-signed.badssl.com/&api_password=your_password&verify_ssl=false"
|
||||
```
|
||||
|
||||
|
||||
#### Proxy HLS Stream with Headers
|
||||
|
||||
```bash
|
||||
|
||||
+41
-11
@@ -24,7 +24,9 @@ from .utils.mpd_utils import pad_base64
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def handle_hls_stream_proxy(request: Request, destination: str, headers: dict, key_url: HttpUrl = None):
|
||||
async def handle_hls_stream_proxy(
|
||||
request: Request, destination: str, headers: dict, key_url: HttpUrl = None, verify_ssl: bool = True
|
||||
):
|
||||
"""
|
||||
Handles the HLS stream proxy request, fetching and processing the m3u8 playlist or streaming the content.
|
||||
|
||||
@@ -33,6 +35,7 @@ async def handle_hls_stream_proxy(request: Request, destination: str, headers: d
|
||||
destination (str): The destination URL to fetch the content from.
|
||||
headers (dict): The headers to include in the request.
|
||||
key_url (str, optional): The HLS Key URL to replace the original key URL. Defaults to None.
|
||||
verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
|
||||
|
||||
Returns:
|
||||
Response: The HTTP response with the processed m3u8 playlist or streamed content.
|
||||
@@ -42,6 +45,7 @@ async def handle_hls_stream_proxy(request: Request, destination: str, headers: d
|
||||
timeout=httpx.Timeout(30.0),
|
||||
limits=httpx.Limits(max_keepalive_connections=10, max_connections=20),
|
||||
proxy=settings.proxy_url,
|
||||
verify=verify_ssl,
|
||||
)
|
||||
streamer = Streamer(client)
|
||||
try:
|
||||
@@ -83,7 +87,7 @@ async def handle_hls_stream_proxy(request: Request, destination: str, headers: d
|
||||
return Response(status_code=502, content=f"Internal server error: {e}")
|
||||
|
||||
|
||||
async def proxy_stream(method: str, video_url: str, headers: dict):
|
||||
async def proxy_stream(method: str, video_url: str, headers: dict, verify_ssl: bool = True):
|
||||
"""
|
||||
Proxies the stream request to the given video URL.
|
||||
|
||||
@@ -91,14 +95,15 @@ async def proxy_stream(method: str, video_url: str, headers: dict):
|
||||
method (str): The HTTP method (e.g., GET, HEAD).
|
||||
video_url (str): The URL of the video to stream.
|
||||
headers (dict): The headers to include in the request.
|
||||
verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
|
||||
|
||||
Returns:
|
||||
Response: The HTTP response with the streamed content.
|
||||
"""
|
||||
return await handle_stream_request(method, video_url, headers)
|
||||
return await handle_stream_request(method, video_url, headers, verify_ssl)
|
||||
|
||||
|
||||
async def handle_stream_request(method: str, video_url: str, headers: dict):
|
||||
async def handle_stream_request(method: str, video_url: str, headers: dict, verify_ssl: bool = True):
|
||||
"""
|
||||
Handles the stream request, fetching the content from the video URL and streaming it.
|
||||
|
||||
@@ -106,6 +111,7 @@ async def handle_stream_request(method: str, video_url: str, headers: dict):
|
||||
method (str): The HTTP method (e.g., GET, HEAD).
|
||||
video_url (str): The URL of the video to stream.
|
||||
headers (dict): The headers to include in the request.
|
||||
verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
|
||||
|
||||
Returns:
|
||||
Response: The HTTP response with the streamed content.
|
||||
@@ -115,6 +121,7 @@ async def handle_stream_request(method: str, video_url: str, headers: dict):
|
||||
timeout=httpx.Timeout(30.0),
|
||||
limits=httpx.Limits(max_keepalive_connections=10, max_connections=20),
|
||||
proxy=settings.proxy_url,
|
||||
verify=verify_ssl,
|
||||
)
|
||||
streamer = Streamer(client)
|
||||
try:
|
||||
@@ -223,7 +230,9 @@ async def handle_drm_key_data(key_id, key, drm_info):
|
||||
return key_id, key
|
||||
|
||||
|
||||
async def get_manifest(request: Request, mpd_url: str, headers: dict, key_id: str = None, key: str = None):
|
||||
async def get_manifest(
|
||||
request: Request, mpd_url: str, headers: dict, key_id: str = None, key: str = None, verify_ssl: bool = True
|
||||
):
|
||||
"""
|
||||
Retrieves and processes the MPD manifest, converting it to an HLS manifest.
|
||||
|
||||
@@ -233,12 +242,15 @@ async def get_manifest(request: Request, mpd_url: str, headers: dict, key_id: st
|
||||
headers (dict): The headers to include in the request.
|
||||
key_id (str, optional): The DRM key ID. Defaults to None.
|
||||
key (str, optional): The DRM key. Defaults to None.
|
||||
verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
|
||||
|
||||
Returns:
|
||||
Response: The HTTP response with the HLS manifest.
|
||||
"""
|
||||
try:
|
||||
mpd_dict = await get_cached_mpd(mpd_url, headers=headers, parse_drm=not key_id and not key)
|
||||
mpd_dict = await get_cached_mpd(
|
||||
mpd_url, headers=headers, parse_drm=not key_id and not key, verify_ssl=verify_ssl
|
||||
)
|
||||
except DownloadError as e:
|
||||
raise HTTPException(status_code=e.status_code, detail=f"Failed to download MPD: {e.message}")
|
||||
drm_info = mpd_dict.get("drmInfo", {})
|
||||
@@ -259,7 +271,13 @@ async def get_manifest(request: Request, mpd_url: str, headers: dict, key_id: st
|
||||
|
||||
|
||||
async def get_playlist(
|
||||
request: Request, mpd_url: str, profile_id: str, headers: dict, key_id: str = None, key: str = None
|
||||
request: Request,
|
||||
mpd_url: str,
|
||||
profile_id: str,
|
||||
headers: dict,
|
||||
key_id: str = None,
|
||||
key: str = None,
|
||||
verify_ssl: bool = True,
|
||||
):
|
||||
"""
|
||||
Retrieves and processes the MPD manifest, converting it to an HLS playlist for a specific profile.
|
||||
@@ -271,18 +289,29 @@ async def get_playlist(
|
||||
headers (dict): The headers to include in the request.
|
||||
key_id (str, optional): The DRM key ID. Defaults to None.
|
||||
key (str, optional): The DRM key. Defaults to None.
|
||||
verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
|
||||
|
||||
Returns:
|
||||
Response: The HTTP response with the HLS playlist.
|
||||
"""
|
||||
mpd_dict = await get_cached_mpd(
|
||||
mpd_url, headers=headers, parse_drm=not key_id and not key, parse_segment_profile_id=profile_id
|
||||
mpd_url,
|
||||
headers=headers,
|
||||
parse_drm=not key_id and not key,
|
||||
parse_segment_profile_id=profile_id,
|
||||
verify_ssl=verify_ssl,
|
||||
)
|
||||
return await process_playlist(request, mpd_dict, profile_id)
|
||||
|
||||
|
||||
async def get_segment(
|
||||
init_url: str, segment_url: str, mimetype: str, headers: dict, key_id: str = None, key: str = None
|
||||
init_url: str,
|
||||
segment_url: str,
|
||||
mimetype: str,
|
||||
headers: dict,
|
||||
key_id: str = None,
|
||||
key: str = None,
|
||||
verify_ssl: bool = True,
|
||||
):
|
||||
"""
|
||||
Retrieves and processes a media segment, decrypting it if necessary.
|
||||
@@ -294,13 +323,14 @@ async def get_segment(
|
||||
headers (dict): The headers to include in the request.
|
||||
key_id (str, optional): The DRM key ID. Defaults to None.
|
||||
key (str, optional): The DRM key. Defaults to None.
|
||||
verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
|
||||
|
||||
Returns:
|
||||
Response: The HTTP response with the processed segment.
|
||||
"""
|
||||
try:
|
||||
init_content = await get_cached_init_segment(init_url, headers)
|
||||
segment_content = await download_file_with_retry(segment_url, headers)
|
||||
init_content = await get_cached_init_segment(init_url, headers, verify_ssl)
|
||||
segment_content = await download_file_with_retry(segment_url, headers, verify_ssl=verify_ssl)
|
||||
except DownloadError as e:
|
||||
raise HTTPException(status_code=e.status_code, detail=f"Failed to download segment: {e.message}")
|
||||
return await process_segment(init_content, segment_content, mimetype, key_id, key)
|
||||
|
||||
@@ -14,6 +14,7 @@ async def hls_stream_proxy(
|
||||
d: HttpUrl,
|
||||
headers: dict = Depends(get_proxy_headers),
|
||||
key_url: HttpUrl | None = None,
|
||||
verify_ssl: bool = True,
|
||||
):
|
||||
"""
|
||||
Proxify HLS stream requests, fetching and processing the m3u8 playlist or streaming the content.
|
||||
@@ -23,17 +24,20 @@ async def hls_stream_proxy(
|
||||
d (HttpUrl): The destination URL to fetch the content from.
|
||||
key_url (HttpUrl, optional): The HLS Key URL to replace the original key URL. Defaults to None. (Useful for bypassing some sneaky protection)
|
||||
headers (dict): The headers to include in the request.
|
||||
verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
|
||||
|
||||
Returns:
|
||||
Response: The HTTP response with the processed m3u8 playlist or streamed content.
|
||||
"""
|
||||
destination = str(d)
|
||||
return await handle_hls_stream_proxy(request, destination, headers, key_url)
|
||||
return await handle_hls_stream_proxy(request, destination, headers, key_url, verify_ssl)
|
||||
|
||||
|
||||
@proxy_router.head("/stream")
|
||||
@proxy_router.get("/stream")
|
||||
async def proxy_stream_endpoint(request: Request, d: HttpUrl, headers: dict = Depends(get_proxy_headers)):
|
||||
async def proxy_stream_endpoint(
|
||||
request: Request, d: HttpUrl, headers: dict = Depends(get_proxy_headers), verify_ssl: bool = True
|
||||
):
|
||||
"""
|
||||
Proxies stream requests to the given video URL.
|
||||
|
||||
@@ -41,12 +45,13 @@ async def proxy_stream_endpoint(request: Request, d: HttpUrl, headers: dict = De
|
||||
request (Request): The incoming HTTP request.
|
||||
d (HttpUrl): The URL of the video to stream.
|
||||
headers (dict): The headers to include in the request.
|
||||
verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
|
||||
|
||||
Returns:
|
||||
Response: The HTTP response with the streamed content.
|
||||
"""
|
||||
headers.update({"range": headers.get("range", "bytes=0-")})
|
||||
return await proxy_stream(request.method, str(d), headers)
|
||||
return await proxy_stream(request.method, str(d), headers, verify_ssl)
|
||||
|
||||
|
||||
@proxy_router.get("/mpd/manifest")
|
||||
@@ -56,6 +61,7 @@ async def manifest_endpoint(
|
||||
headers: dict = Depends(get_proxy_headers),
|
||||
key_id: str = None,
|
||||
key: str = None,
|
||||
verify_ssl: bool = True,
|
||||
):
|
||||
"""
|
||||
Retrieves and processes the MPD manifest, converting it to an HLS manifest.
|
||||
@@ -66,11 +72,12 @@ async def manifest_endpoint(
|
||||
headers (dict): The headers to include in the request.
|
||||
key_id (str, optional): The DRM key ID. Defaults to None.
|
||||
key (str, optional): The DRM key. Defaults to None.
|
||||
verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
|
||||
|
||||
Returns:
|
||||
Response: The HTTP response with the HLS manifest.
|
||||
"""
|
||||
return await get_manifest(request, str(d), headers, key_id, key)
|
||||
return await get_manifest(request, str(d), headers, key_id, key, verify_ssl)
|
||||
|
||||
|
||||
@proxy_router.get("/mpd/playlist")
|
||||
@@ -81,6 +88,7 @@ async def playlist_endpoint(
|
||||
headers: dict = Depends(get_proxy_headers),
|
||||
key_id: str = None,
|
||||
key: str = None,
|
||||
verify_ssl: bool = True,
|
||||
):
|
||||
"""
|
||||
Retrieves and processes the MPD manifest, converting it to an HLS playlist for a specific profile.
|
||||
@@ -92,11 +100,12 @@ async def playlist_endpoint(
|
||||
headers (dict): The headers to include in the request.
|
||||
key_id (str, optional): The DRM key ID. Defaults to None.
|
||||
key (str, optional): The DRM key. Defaults to None.
|
||||
verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
|
||||
|
||||
Returns:
|
||||
Response: The HTTP response with the HLS playlist.
|
||||
"""
|
||||
return await get_playlist(request, str(d), profile_id, headers, key_id, key)
|
||||
return await get_playlist(request, str(d), profile_id, headers, key_id, key, verify_ssl)
|
||||
|
||||
|
||||
@proxy_router.get("/mpd/segment")
|
||||
@@ -107,6 +116,7 @@ async def segment_endpoint(
|
||||
headers: dict = Depends(get_proxy_headers),
|
||||
key_id: str = None,
|
||||
key: str = None,
|
||||
verify_ssl: bool = True,
|
||||
):
|
||||
"""
|
||||
Retrieves and processes a media segment, decrypting it if necessary.
|
||||
@@ -118,11 +128,12 @@ async def segment_endpoint(
|
||||
headers (dict): The headers to include in the request.
|
||||
key_id (str, optional): The DRM key ID. Defaults to None.
|
||||
key (str, optional): The DRM key. Defaults to None.
|
||||
verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
|
||||
|
||||
Returns:
|
||||
Response: The HTTP response with the processed segment.
|
||||
"""
|
||||
return await get_segment(str(init_url), str(segment_url), mime_type, headers, key_id, key)
|
||||
return await get_segment(str(init_url), str(segment_url), mime_type, headers, key_id, key, verify_ssl)
|
||||
|
||||
|
||||
@proxy_router.get("/ip")
|
||||
|
||||
@@ -14,7 +14,7 @@ init_segment_cache = TTLCache(maxsize=100, ttl=3600) # 1 hour default TTL
|
||||
|
||||
|
||||
async def get_cached_mpd(
|
||||
mpd_url: str, headers: dict, parse_drm: bool, parse_segment_profile_id: str | None = None
|
||||
mpd_url: str, headers: dict, parse_drm: bool, parse_segment_profile_id: str | None = None, verify_ssl: bool = True
|
||||
) -> dict:
|
||||
"""
|
||||
Retrieves and caches the MPD manifest, parsing it if not already cached.
|
||||
@@ -24,6 +24,7 @@ async def get_cached_mpd(
|
||||
headers (dict): The headers to include in the request.
|
||||
parse_drm (bool): Whether to parse DRM information.
|
||||
parse_segment_profile_id (str, optional): The profile ID to parse segments for. Defaults to None.
|
||||
verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
|
||||
|
||||
Returns:
|
||||
dict: The parsed MPD manifest data.
|
||||
@@ -33,7 +34,7 @@ async def get_cached_mpd(
|
||||
logger.info(f"Using cached MPD for {mpd_url}")
|
||||
return parse_mpd_dict(mpd_cache[mpd_url]["mpd"], mpd_url, parse_drm, parse_segment_profile_id)
|
||||
|
||||
mpd_dict = parse_mpd(await download_file_with_retry(mpd_url, headers))
|
||||
mpd_dict = parse_mpd(await download_file_with_retry(mpd_url, headers, verify_ssl=verify_ssl))
|
||||
parsed_mpd_dict = parse_mpd_dict(mpd_dict, mpd_url, parse_drm, parse_segment_profile_id)
|
||||
current_time = datetime.datetime.now(datetime.UTC)
|
||||
expiration_time = current_time + datetime.timedelta(seconds=parsed_mpd_dict.get("minimumUpdatePeriod", 300))
|
||||
@@ -41,18 +42,19 @@ async def get_cached_mpd(
|
||||
return parsed_mpd_dict
|
||||
|
||||
|
||||
async def get_cached_init_segment(init_url: str, headers: dict) -> bytes:
|
||||
async def get_cached_init_segment(init_url: str, headers: dict, verify_ssl: bool = True) -> bytes:
|
||||
"""
|
||||
Retrieves and caches the initialization segment.
|
||||
|
||||
Args:
|
||||
init_url (str): The URL of the initialization segment.
|
||||
headers (dict): The headers to include in the request.
|
||||
verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
|
||||
|
||||
Returns:
|
||||
bytes: The initialization segment content.
|
||||
"""
|
||||
if init_url not in init_segment_cache:
|
||||
init_content = await download_file_with_retry(init_url, headers)
|
||||
init_content = await download_file_with_retry(init_url, headers, verify_ssl=verify_ssl)
|
||||
init_segment_cache[init_url] = init_content
|
||||
return init_segment_cache[init_url]
|
||||
|
||||
@@ -137,7 +137,7 @@ class Streamer:
|
||||
await self.client.aclose()
|
||||
|
||||
|
||||
async def download_file_with_retry(url: str, headers: dict, timeout: float = 10.0):
|
||||
async def download_file_with_retry(url: str, headers: dict, timeout: float = 10.0, verify_ssl: bool = True):
|
||||
"""
|
||||
Downloads a file with retry logic.
|
||||
|
||||
@@ -145,6 +145,7 @@ async def download_file_with_retry(url: str, headers: dict, timeout: float = 10.
|
||||
url (str): The URL of the file to download.
|
||||
headers (dict): The headers to include in the request.
|
||||
timeout (float, optional): The request timeout. Defaults to 10.0.
|
||||
verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
|
||||
|
||||
Returns:
|
||||
bytes: The downloaded file content.
|
||||
@@ -152,7 +153,9 @@ async def download_file_with_retry(url: str, headers: dict, timeout: float = 10.
|
||||
Raises:
|
||||
DownloadError: If the download fails after retries.
|
||||
"""
|
||||
async with httpx.AsyncClient(follow_redirects=True, timeout=timeout, proxy=settings.proxy_url) as client:
|
||||
async with httpx.AsyncClient(
|
||||
follow_redirects=True, timeout=timeout, proxy=settings.proxy_url, verify=verify_ssl
|
||||
) as client:
|
||||
try:
|
||||
response = await fetch_with_retry(client, "GET", url, headers)
|
||||
return response.content
|
||||
|
||||
@@ -10,12 +10,12 @@ import xmltodict
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def parse_mpd(mpd_content: str) -> dict:
|
||||
def parse_mpd(mpd_content: str | bytes) -> dict:
|
||||
"""
|
||||
Parses the MPD content into a dictionary.
|
||||
|
||||
Args:
|
||||
mpd_content (str): The MPD content as a string.
|
||||
mpd_content (str | bytes): The MPD content to parse.
|
||||
|
||||
Returns:
|
||||
dict: The parsed MPD content as a dictionary.
|
||||
|
||||
Reference in New Issue
Block a user