mhdzumair commited on
Commit
0e2d63a
·
1 Parent(s): 9c4a1a8

Add Support for play expired or self-signed SSL certificates server streams

Browse files
README.md CHANGED
@@ -30,7 +30,7 @@ MediaFlow Proxy is a powerful and flexible solution for proxifying various types
30
  - Retrieve public IP address of the MediaFlow Proxy server for use with Debrid services
31
  - Support for HTTP/HTTPS/SOCKS5 proxy forwarding
32
  - Protect against unauthorized access and network bandwidth abuses
33
-
34
 
35
  ## Configuration
36
 
@@ -157,6 +157,13 @@ Once the server is running, for more details on the available endpoints and thei
157
  mpv "http://localhost:8888/proxy/stream?d=https://jsoncompare.org/LearningContainer/SampleFiles/Video/MP4/sample-mp4-file.mp4&api_password=your_password"
158
  ```
159
 
 
 
 
 
 
 
 
160
  #### Proxy HLS Stream with Headers
161
 
162
  ```bash
 
30
  - Retrieve public IP address of the MediaFlow Proxy server for use with Debrid services
31
  - Support for HTTP/HTTPS/SOCKS5 proxy forwarding
32
  - Protect against unauthorized access and network bandwidth abuses
33
+ - Support for play expired or self-signed SSL certificates server streams
34
 
35
  ## Configuration
36
 
 
157
  mpv "http://localhost:8888/proxy/stream?d=https://jsoncompare.org/LearningContainer/SampleFiles/Video/MP4/sample-mp4-file.mp4&api_password=your_password"
158
  ```
159
 
160
+ #### Proxy HTTPS self-signed certificate Stream
161
+
162
+ ```bash
163
+ mpv "http://localhost:8888/proxy/stream?d=https://self-signed.badssl.com/&api_password=your_password&verify_ssl=false"
164
+ ```
165
+
166
+
167
  #### Proxy HLS Stream with Headers
168
 
169
  ```bash
mediaflow_proxy/handlers.py CHANGED
@@ -24,7 +24,9 @@ from .utils.mpd_utils import pad_base64
24
  logger = logging.getLogger(__name__)
25
 
26
 
27
- async def handle_hls_stream_proxy(request: Request, destination: str, headers: dict, key_url: HttpUrl = None):
 
 
28
  """
29
  Handles the HLS stream proxy request, fetching and processing the m3u8 playlist or streaming the content.
30
 
@@ -33,6 +35,7 @@ async def handle_hls_stream_proxy(request: Request, destination: str, headers: d
33
  destination (str): The destination URL to fetch the content from.
34
  headers (dict): The headers to include in the request.
35
  key_url (str, optional): The HLS Key URL to replace the original key URL. Defaults to None.
 
36
 
37
  Returns:
38
  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
42
  timeout=httpx.Timeout(30.0),
43
  limits=httpx.Limits(max_keepalive_connections=10, max_connections=20),
44
  proxy=settings.proxy_url,
 
45
  )
46
  streamer = Streamer(client)
47
  try:
@@ -83,7 +87,7 @@ async def handle_hls_stream_proxy(request: Request, destination: str, headers: d
83
  return Response(status_code=502, content=f"Internal server error: {e}")
84
 
85
 
86
- async def proxy_stream(method: str, video_url: str, headers: dict):
87
  """
88
  Proxies the stream request to the given video URL.
89
 
@@ -91,14 +95,15 @@ async def proxy_stream(method: str, video_url: str, headers: dict):
91
  method (str): The HTTP method (e.g., GET, HEAD).
92
  video_url (str): The URL of the video to stream.
93
  headers (dict): The headers to include in the request.
 
94
 
95
  Returns:
96
  Response: The HTTP response with the streamed content.
97
  """
98
- return await handle_stream_request(method, video_url, headers)
99
 
100
 
101
- async def handle_stream_request(method: str, video_url: str, headers: dict):
102
  """
103
  Handles the stream request, fetching the content from the video URL and streaming it.
104
 
@@ -106,6 +111,7 @@ async def handle_stream_request(method: str, video_url: str, headers: dict):
106
  method (str): The HTTP method (e.g., GET, HEAD).
107
  video_url (str): The URL of the video to stream.
108
  headers (dict): The headers to include in the request.
 
109
 
110
  Returns:
111
  Response: The HTTP response with the streamed content.
@@ -115,6 +121,7 @@ async def handle_stream_request(method: str, video_url: str, headers: dict):
115
  timeout=httpx.Timeout(30.0),
116
  limits=httpx.Limits(max_keepalive_connections=10, max_connections=20),
117
  proxy=settings.proxy_url,
 
118
  )
119
  streamer = Streamer(client)
120
  try:
@@ -223,7 +230,9 @@ async def handle_drm_key_data(key_id, key, drm_info):
223
  return key_id, key
224
 
225
 
226
- async def get_manifest(request: Request, mpd_url: str, headers: dict, key_id: str = None, key: str = None):
 
 
227
  """
228
  Retrieves and processes the MPD manifest, converting it to an HLS manifest.
229
 
@@ -233,12 +242,15 @@ async def get_manifest(request: Request, mpd_url: str, headers: dict, key_id: st
233
  headers (dict): The headers to include in the request.
234
  key_id (str, optional): The DRM key ID. Defaults to None.
235
  key (str, optional): The DRM key. Defaults to None.
 
236
 
237
  Returns:
238
  Response: The HTTP response with the HLS manifest.
239
  """
240
  try:
241
- mpd_dict = await get_cached_mpd(mpd_url, headers=headers, parse_drm=not key_id and not key)
 
 
242
  except DownloadError as e:
243
  raise HTTPException(status_code=e.status_code, detail=f"Failed to download MPD: {e.message}")
244
  drm_info = mpd_dict.get("drmInfo", {})
@@ -259,7 +271,13 @@ async def get_manifest(request: Request, mpd_url: str, headers: dict, key_id: st
259
 
260
 
261
  async def get_playlist(
262
- request: Request, mpd_url: str, profile_id: str, headers: dict, key_id: str = None, key: str = None
 
 
 
 
 
 
263
  ):
264
  """
265
  Retrieves and processes the MPD manifest, converting it to an HLS playlist for a specific profile.
@@ -271,18 +289,29 @@ async def get_playlist(
271
  headers (dict): The headers to include in the request.
272
  key_id (str, optional): The DRM key ID. Defaults to None.
273
  key (str, optional): The DRM key. Defaults to None.
 
274
 
275
  Returns:
276
  Response: The HTTP response with the HLS playlist.
277
  """
278
  mpd_dict = await get_cached_mpd(
279
- mpd_url, headers=headers, parse_drm=not key_id and not key, parse_segment_profile_id=profile_id
 
 
 
 
280
  )
281
  return await process_playlist(request, mpd_dict, profile_id)
282
 
283
 
284
  async def get_segment(
285
- init_url: str, segment_url: str, mimetype: str, headers: dict, key_id: str = None, key: str = None
 
 
 
 
 
 
286
  ):
287
  """
288
  Retrieves and processes a media segment, decrypting it if necessary.
@@ -294,13 +323,14 @@ async def get_segment(
294
  headers (dict): The headers to include in the request.
295
  key_id (str, optional): The DRM key ID. Defaults to None.
296
  key (str, optional): The DRM key. Defaults to None.
 
297
 
298
  Returns:
299
  Response: The HTTP response with the processed segment.
300
  """
301
  try:
302
- init_content = await get_cached_init_segment(init_url, headers)
303
- segment_content = await download_file_with_retry(segment_url, headers)
304
  except DownloadError as e:
305
  raise HTTPException(status_code=e.status_code, detail=f"Failed to download segment: {e.message}")
306
  return await process_segment(init_content, segment_content, mimetype, key_id, key)
 
24
  logger = logging.getLogger(__name__)
25
 
26
 
27
+ async def handle_hls_stream_proxy(
28
+ request: Request, destination: str, headers: dict, key_url: HttpUrl = None, verify_ssl: bool = True
29
+ ):
30
  """
31
  Handles the HLS stream proxy request, fetching and processing the m3u8 playlist or streaming the content.
32
 
 
35
  destination (str): The destination URL to fetch the content from.
36
  headers (dict): The headers to include in the request.
37
  key_url (str, optional): The HLS Key URL to replace the original key URL. Defaults to None.
38
+ verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
39
 
40
  Returns:
41
  Response: The HTTP response with the processed m3u8 playlist or streamed content.
 
45
  timeout=httpx.Timeout(30.0),
46
  limits=httpx.Limits(max_keepalive_connections=10, max_connections=20),
47
  proxy=settings.proxy_url,
48
+ verify=verify_ssl,
49
  )
50
  streamer = Streamer(client)
51
  try:
 
87
  return Response(status_code=502, content=f"Internal server error: {e}")
88
 
89
 
90
+ async def proxy_stream(method: str, video_url: str, headers: dict, verify_ssl: bool = True):
91
  """
92
  Proxies the stream request to the given video URL.
93
 
 
95
  method (str): The HTTP method (e.g., GET, HEAD).
96
  video_url (str): The URL of the video to stream.
97
  headers (dict): The headers to include in the request.
98
+ verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
99
 
100
  Returns:
101
  Response: The HTTP response with the streamed content.
102
  """
103
+ return await handle_stream_request(method, video_url, headers, verify_ssl)
104
 
105
 
106
+ async def handle_stream_request(method: str, video_url: str, headers: dict, verify_ssl: bool = True):
107
  """
108
  Handles the stream request, fetching the content from the video URL and streaming it.
109
 
 
111
  method (str): The HTTP method (e.g., GET, HEAD).
112
  video_url (str): The URL of the video to stream.
113
  headers (dict): The headers to include in the request.
114
+ verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
115
 
116
  Returns:
117
  Response: The HTTP response with the streamed content.
 
121
  timeout=httpx.Timeout(30.0),
122
  limits=httpx.Limits(max_keepalive_connections=10, max_connections=20),
123
  proxy=settings.proxy_url,
124
+ verify=verify_ssl,
125
  )
126
  streamer = Streamer(client)
127
  try:
 
230
  return key_id, key
231
 
232
 
233
+ async def get_manifest(
234
+ request: Request, mpd_url: str, headers: dict, key_id: str = None, key: str = None, verify_ssl: bool = True
235
+ ):
236
  """
237
  Retrieves and processes the MPD manifest, converting it to an HLS manifest.
238
 
 
242
  headers (dict): The headers to include in the request.
243
  key_id (str, optional): The DRM key ID. Defaults to None.
244
  key (str, optional): The DRM key. Defaults to None.
245
+ verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
246
 
247
  Returns:
248
  Response: The HTTP response with the HLS manifest.
249
  """
250
  try:
251
+ mpd_dict = await get_cached_mpd(
252
+ mpd_url, headers=headers, parse_drm=not key_id and not key, verify_ssl=verify_ssl
253
+ )
254
  except DownloadError as e:
255
  raise HTTPException(status_code=e.status_code, detail=f"Failed to download MPD: {e.message}")
256
  drm_info = mpd_dict.get("drmInfo", {})
 
271
 
272
 
273
  async def get_playlist(
274
+ request: Request,
275
+ mpd_url: str,
276
+ profile_id: str,
277
+ headers: dict,
278
+ key_id: str = None,
279
+ key: str = None,
280
+ verify_ssl: bool = True,
281
  ):
282
  """
283
  Retrieves and processes the MPD manifest, converting it to an HLS playlist for a specific profile.
 
289
  headers (dict): The headers to include in the request.
290
  key_id (str, optional): The DRM key ID. Defaults to None.
291
  key (str, optional): The DRM key. Defaults to None.
292
+ verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
293
 
294
  Returns:
295
  Response: The HTTP response with the HLS playlist.
296
  """
297
  mpd_dict = await get_cached_mpd(
298
+ mpd_url,
299
+ headers=headers,
300
+ parse_drm=not key_id and not key,
301
+ parse_segment_profile_id=profile_id,
302
+ verify_ssl=verify_ssl,
303
  )
304
  return await process_playlist(request, mpd_dict, profile_id)
305
 
306
 
307
  async def get_segment(
308
+ init_url: str,
309
+ segment_url: str,
310
+ mimetype: str,
311
+ headers: dict,
312
+ key_id: str = None,
313
+ key: str = None,
314
+ verify_ssl: bool = True,
315
  ):
316
  """
317
  Retrieves and processes a media segment, decrypting it if necessary.
 
323
  headers (dict): The headers to include in the request.
324
  key_id (str, optional): The DRM key ID. Defaults to None.
325
  key (str, optional): The DRM key. Defaults to None.
326
+ verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
327
 
328
  Returns:
329
  Response: The HTTP response with the processed segment.
330
  """
331
  try:
332
+ init_content = await get_cached_init_segment(init_url, headers, verify_ssl)
333
+ segment_content = await download_file_with_retry(segment_url, headers, verify_ssl=verify_ssl)
334
  except DownloadError as e:
335
  raise HTTPException(status_code=e.status_code, detail=f"Failed to download segment: {e.message}")
336
  return await process_segment(init_content, segment_content, mimetype, key_id, key)
mediaflow_proxy/routes.py CHANGED
@@ -14,6 +14,7 @@ async def hls_stream_proxy(
14
  d: HttpUrl,
15
  headers: dict = Depends(get_proxy_headers),
16
  key_url: HttpUrl | None = None,
 
17
  ):
18
  """
19
  Proxify HLS stream requests, fetching and processing the m3u8 playlist or streaming the content.
@@ -23,17 +24,20 @@ async def hls_stream_proxy(
23
  d (HttpUrl): The destination URL to fetch the content from.
24
  key_url (HttpUrl, optional): The HLS Key URL to replace the original key URL. Defaults to None. (Useful for bypassing some sneaky protection)
25
  headers (dict): The headers to include in the request.
 
26
 
27
  Returns:
28
  Response: The HTTP response with the processed m3u8 playlist or streamed content.
29
  """
30
  destination = str(d)
31
- return await handle_hls_stream_proxy(request, destination, headers, key_url)
32
 
33
 
34
  @proxy_router.head("/stream")
35
  @proxy_router.get("/stream")
36
- async def proxy_stream_endpoint(request: Request, d: HttpUrl, headers: dict = Depends(get_proxy_headers)):
 
 
37
  """
38
  Proxies stream requests to the given video URL.
39
 
@@ -41,12 +45,13 @@ async def proxy_stream_endpoint(request: Request, d: HttpUrl, headers: dict = De
41
  request (Request): The incoming HTTP request.
42
  d (HttpUrl): The URL of the video to stream.
43
  headers (dict): The headers to include in the request.
 
44
 
45
  Returns:
46
  Response: The HTTP response with the streamed content.
47
  """
48
  headers.update({"range": headers.get("range", "bytes=0-")})
49
- return await proxy_stream(request.method, str(d), headers)
50
 
51
 
52
  @proxy_router.get("/mpd/manifest")
@@ -56,6 +61,7 @@ async def manifest_endpoint(
56
  headers: dict = Depends(get_proxy_headers),
57
  key_id: str = None,
58
  key: str = None,
 
59
  ):
60
  """
61
  Retrieves and processes the MPD manifest, converting it to an HLS manifest.
@@ -66,11 +72,12 @@ async def manifest_endpoint(
66
  headers (dict): The headers to include in the request.
67
  key_id (str, optional): The DRM key ID. Defaults to None.
68
  key (str, optional): The DRM key. Defaults to None.
 
69
 
70
  Returns:
71
  Response: The HTTP response with the HLS manifest.
72
  """
73
- return await get_manifest(request, str(d), headers, key_id, key)
74
 
75
 
76
  @proxy_router.get("/mpd/playlist")
@@ -81,6 +88,7 @@ async def playlist_endpoint(
81
  headers: dict = Depends(get_proxy_headers),
82
  key_id: str = None,
83
  key: str = None,
 
84
  ):
85
  """
86
  Retrieves and processes the MPD manifest, converting it to an HLS playlist for a specific profile.
@@ -92,11 +100,12 @@ async def playlist_endpoint(
92
  headers (dict): The headers to include in the request.
93
  key_id (str, optional): The DRM key ID. Defaults to None.
94
  key (str, optional): The DRM key. Defaults to None.
 
95
 
96
  Returns:
97
  Response: The HTTP response with the HLS playlist.
98
  """
99
- return await get_playlist(request, str(d), profile_id, headers, key_id, key)
100
 
101
 
102
  @proxy_router.get("/mpd/segment")
@@ -107,6 +116,7 @@ async def segment_endpoint(
107
  headers: dict = Depends(get_proxy_headers),
108
  key_id: str = None,
109
  key: str = None,
 
110
  ):
111
  """
112
  Retrieves and processes a media segment, decrypting it if necessary.
@@ -118,11 +128,12 @@ async def segment_endpoint(
118
  headers (dict): The headers to include in the request.
119
  key_id (str, optional): The DRM key ID. Defaults to None.
120
  key (str, optional): The DRM key. Defaults to None.
 
121
 
122
  Returns:
123
  Response: The HTTP response with the processed segment.
124
  """
125
- return await get_segment(str(init_url), str(segment_url), mime_type, headers, key_id, key)
126
 
127
 
128
  @proxy_router.get("/ip")
 
14
  d: HttpUrl,
15
  headers: dict = Depends(get_proxy_headers),
16
  key_url: HttpUrl | None = None,
17
+ verify_ssl: bool = True,
18
  ):
19
  """
20
  Proxify HLS stream requests, fetching and processing the m3u8 playlist or streaming the content.
 
24
  d (HttpUrl): The destination URL to fetch the content from.
25
  key_url (HttpUrl, optional): The HLS Key URL to replace the original key URL. Defaults to None. (Useful for bypassing some sneaky protection)
26
  headers (dict): The headers to include in the request.
27
+ verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
28
 
29
  Returns:
30
  Response: The HTTP response with the processed m3u8 playlist or streamed content.
31
  """
32
  destination = str(d)
33
+ return await handle_hls_stream_proxy(request, destination, headers, key_url, verify_ssl)
34
 
35
 
36
  @proxy_router.head("/stream")
37
  @proxy_router.get("/stream")
38
+ async def proxy_stream_endpoint(
39
+ request: Request, d: HttpUrl, headers: dict = Depends(get_proxy_headers), verify_ssl: bool = True
40
+ ):
41
  """
42
  Proxies stream requests to the given video URL.
43
 
 
45
  request (Request): The incoming HTTP request.
46
  d (HttpUrl): The URL of the video to stream.
47
  headers (dict): The headers to include in the request.
48
+ verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
49
 
50
  Returns:
51
  Response: The HTTP response with the streamed content.
52
  """
53
  headers.update({"range": headers.get("range", "bytes=0-")})
54
+ return await proxy_stream(request.method, str(d), headers, verify_ssl)
55
 
56
 
57
  @proxy_router.get("/mpd/manifest")
 
61
  headers: dict = Depends(get_proxy_headers),
62
  key_id: str = None,
63
  key: str = None,
64
+ verify_ssl: bool = True,
65
  ):
66
  """
67
  Retrieves and processes the MPD manifest, converting it to an HLS manifest.
 
72
  headers (dict): The headers to include in the request.
73
  key_id (str, optional): The DRM key ID. Defaults to None.
74
  key (str, optional): The DRM key. Defaults to None.
75
+ verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
76
 
77
  Returns:
78
  Response: The HTTP response with the HLS manifest.
79
  """
80
+ return await get_manifest(request, str(d), headers, key_id, key, verify_ssl)
81
 
82
 
83
  @proxy_router.get("/mpd/playlist")
 
88
  headers: dict = Depends(get_proxy_headers),
89
  key_id: str = None,
90
  key: str = None,
91
+ verify_ssl: bool = True,
92
  ):
93
  """
94
  Retrieves and processes the MPD manifest, converting it to an HLS playlist for a specific profile.
 
100
  headers (dict): The headers to include in the request.
101
  key_id (str, optional): The DRM key ID. Defaults to None.
102
  key (str, optional): The DRM key. Defaults to None.
103
+ verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
104
 
105
  Returns:
106
  Response: The HTTP response with the HLS playlist.
107
  """
108
+ return await get_playlist(request, str(d), profile_id, headers, key_id, key, verify_ssl)
109
 
110
 
111
  @proxy_router.get("/mpd/segment")
 
116
  headers: dict = Depends(get_proxy_headers),
117
  key_id: str = None,
118
  key: str = None,
119
+ verify_ssl: bool = True,
120
  ):
121
  """
122
  Retrieves and processes a media segment, decrypting it if necessary.
 
128
  headers (dict): The headers to include in the request.
129
  key_id (str, optional): The DRM key ID. Defaults to None.
130
  key (str, optional): The DRM key. Defaults to None.
131
+ verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
132
 
133
  Returns:
134
  Response: The HTTP response with the processed segment.
135
  """
136
+ return await get_segment(str(init_url), str(segment_url), mime_type, headers, key_id, key, verify_ssl)
137
 
138
 
139
  @proxy_router.get("/ip")
mediaflow_proxy/utils/cache_utils.py CHANGED
@@ -14,7 +14,7 @@ init_segment_cache = TTLCache(maxsize=100, ttl=3600) # 1 hour default TTL
14
 
15
 
16
  async def get_cached_mpd(
17
- mpd_url: str, headers: dict, parse_drm: bool, parse_segment_profile_id: str | None = None
18
  ) -> dict:
19
  """
20
  Retrieves and caches the MPD manifest, parsing it if not already cached.
@@ -24,6 +24,7 @@ async def get_cached_mpd(
24
  headers (dict): The headers to include in the request.
25
  parse_drm (bool): Whether to parse DRM information.
26
  parse_segment_profile_id (str, optional): The profile ID to parse segments for. Defaults to None.
 
27
 
28
  Returns:
29
  dict: The parsed MPD manifest data.
@@ -33,7 +34,7 @@ async def get_cached_mpd(
33
  logger.info(f"Using cached MPD for {mpd_url}")
34
  return parse_mpd_dict(mpd_cache[mpd_url]["mpd"], mpd_url, parse_drm, parse_segment_profile_id)
35
 
36
- mpd_dict = parse_mpd(await download_file_with_retry(mpd_url, headers))
37
  parsed_mpd_dict = parse_mpd_dict(mpd_dict, mpd_url, parse_drm, parse_segment_profile_id)
38
  current_time = datetime.datetime.now(datetime.UTC)
39
  expiration_time = current_time + datetime.timedelta(seconds=parsed_mpd_dict.get("minimumUpdatePeriod", 300))
@@ -41,18 +42,19 @@ async def get_cached_mpd(
41
  return parsed_mpd_dict
42
 
43
 
44
- async def get_cached_init_segment(init_url: str, headers: dict) -> bytes:
45
  """
46
  Retrieves and caches the initialization segment.
47
 
48
  Args:
49
  init_url (str): The URL of the initialization segment.
50
  headers (dict): The headers to include in the request.
 
51
 
52
  Returns:
53
  bytes: The initialization segment content.
54
  """
55
  if init_url not in init_segment_cache:
56
- init_content = await download_file_with_retry(init_url, headers)
57
  init_segment_cache[init_url] = init_content
58
  return init_segment_cache[init_url]
 
14
 
15
 
16
  async def get_cached_mpd(
17
+ mpd_url: str, headers: dict, parse_drm: bool, parse_segment_profile_id: str | None = None, verify_ssl: bool = True
18
  ) -> dict:
19
  """
20
  Retrieves and caches the MPD manifest, parsing it if not already cached.
 
24
  headers (dict): The headers to include in the request.
25
  parse_drm (bool): Whether to parse DRM information.
26
  parse_segment_profile_id (str, optional): The profile ID to parse segments for. Defaults to None.
27
+ verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
28
 
29
  Returns:
30
  dict: The parsed MPD manifest data.
 
34
  logger.info(f"Using cached MPD for {mpd_url}")
35
  return parse_mpd_dict(mpd_cache[mpd_url]["mpd"], mpd_url, parse_drm, parse_segment_profile_id)
36
 
37
+ mpd_dict = parse_mpd(await download_file_with_retry(mpd_url, headers, verify_ssl=verify_ssl))
38
  parsed_mpd_dict = parse_mpd_dict(mpd_dict, mpd_url, parse_drm, parse_segment_profile_id)
39
  current_time = datetime.datetime.now(datetime.UTC)
40
  expiration_time = current_time + datetime.timedelta(seconds=parsed_mpd_dict.get("minimumUpdatePeriod", 300))
 
42
  return parsed_mpd_dict
43
 
44
 
45
+ async def get_cached_init_segment(init_url: str, headers: dict, verify_ssl: bool = True) -> bytes:
46
  """
47
  Retrieves and caches the initialization segment.
48
 
49
  Args:
50
  init_url (str): The URL of the initialization segment.
51
  headers (dict): The headers to include in the request.
52
+ verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
53
 
54
  Returns:
55
  bytes: The initialization segment content.
56
  """
57
  if init_url not in init_segment_cache:
58
+ init_content = await download_file_with_retry(init_url, headers, verify_ssl=verify_ssl)
59
  init_segment_cache[init_url] = init_content
60
  return init_segment_cache[init_url]
mediaflow_proxy/utils/http_utils.py CHANGED
@@ -137,7 +137,7 @@ class Streamer:
137
  await self.client.aclose()
138
 
139
 
140
- async def download_file_with_retry(url: str, headers: dict, timeout: float = 10.0):
141
  """
142
  Downloads a file with retry logic.
143
 
@@ -145,6 +145,7 @@ async def download_file_with_retry(url: str, headers: dict, timeout: float = 10.
145
  url (str): The URL of the file to download.
146
  headers (dict): The headers to include in the request.
147
  timeout (float, optional): The request timeout. Defaults to 10.0.
 
148
 
149
  Returns:
150
  bytes: The downloaded file content.
@@ -152,7 +153,9 @@ async def download_file_with_retry(url: str, headers: dict, timeout: float = 10.
152
  Raises:
153
  DownloadError: If the download fails after retries.
154
  """
155
- async with httpx.AsyncClient(follow_redirects=True, timeout=timeout, proxy=settings.proxy_url) as client:
 
 
156
  try:
157
  response = await fetch_with_retry(client, "GET", url, headers)
158
  return response.content
 
137
  await self.client.aclose()
138
 
139
 
140
+ async def download_file_with_retry(url: str, headers: dict, timeout: float = 10.0, verify_ssl: bool = True):
141
  """
142
  Downloads a file with retry logic.
143
 
 
145
  url (str): The URL of the file to download.
146
  headers (dict): The headers to include in the request.
147
  timeout (float, optional): The request timeout. Defaults to 10.0.
148
+ verify_ssl (bool, optional): Whether to verify the SSL certificate of the destination. Defaults to True.
149
 
150
  Returns:
151
  bytes: The downloaded file content.
 
153
  Raises:
154
  DownloadError: If the download fails after retries.
155
  """
156
+ async with httpx.AsyncClient(
157
+ follow_redirects=True, timeout=timeout, proxy=settings.proxy_url, verify=verify_ssl
158
+ ) as client:
159
  try:
160
  response = await fetch_with_retry(client, "GET", url, headers)
161
  return response.content
mediaflow_proxy/utils/mpd_utils.py CHANGED
@@ -10,12 +10,12 @@ import xmltodict
10
  logger = logging.getLogger(__name__)
11
 
12
 
13
- def parse_mpd(mpd_content: str) -> dict:
14
  """
15
  Parses the MPD content into a dictionary.
16
 
17
  Args:
18
- mpd_content (str): The MPD content as a string.
19
 
20
  Returns:
21
  dict: The parsed MPD content as a dictionary.
 
10
  logger = logging.getLogger(__name__)
11
 
12
 
13
+ def parse_mpd(mpd_content: str | bytes) -> dict:
14
  """
15
  Parses the MPD content into a dictionary.
16
 
17
  Args:
18
+ mpd_content (str | bytes): The MPD content to parse.
19
 
20
  Returns:
21
  dict: The parsed MPD content as a dictionary.