@@ -226,17 +226,20 @@ def _ydl_download(
226226 dest_dir / (_sanitize_filename (title_hint or "%(title)s" ) + ".%(ext)s" )
227227 )
228228 logger .debug (f"yt-dlp output template: { outtmpl } " )
229+ # Keep retries conservative to avoid endless retry loops on dead links
229230 ydl_opts : Dict [str , Any ] = {
230231 "outtmpl" : outtmpl ,
231- "retries" : 5 ,
232+ "retries" : 3 , # whole-request retries
233+ "fragment_retries" : 3 , # per-fragment retries
232234 "continuedl" : True ,
233235 "concurrent_fragment_downloads" : 4 ,
234236 "quiet" : True ,
235237 "noprogress" : True ,
236238 "merge_output_format" : "mkv" ,
237- "fragment_retries" : 9999 ,
238239 "downloader" : "ffmpeg" ,
239240 "hls_use_mpegts" : True ,
241+ # Fail faster on broken CDNs
242+ "socket_timeout" : 20 ,
240243 }
241244
242245 # Apply proxy for yt-dlp if configured
@@ -339,15 +342,85 @@ def download_episode(
339342 base_hint = f"{ slug } -S{ season :02d} E{ episode :02d} -{ language } -{ chosen } "
340343 logger .debug (f"Generated base_hint for filename: { base_hint } " )
341344
342- temp_path , info = _ydl_download (
343- direct ,
344- dest_dir ,
345- title_hint = base_hint ,
346- cookiefile = cookiefile ,
347- progress_cb = progress_cb ,
348- stop_event = stop_event ,
349- force_no_proxy = force_no_proxy ,
350- )
345+ try :
346+ temp_path , info = _ydl_download (
347+ direct ,
348+ dest_dir ,
349+ title_hint = base_hint ,
350+ cookiefile = cookiefile ,
351+ progress_cb = progress_cb ,
352+ stop_event = stop_event ,
353+ force_no_proxy = force_no_proxy ,
354+ )
355+ except Exception as e :
356+ # If the actual download fails (timeouts/CDN issues), try one controlled
357+ # fallback path before giving up:
358+ # 1) If we used proxy, re-resolve and download without proxy consistently
359+ # 2) Otherwise, try to re-resolve with a different provider if available
360+ msg = str (e )
361+ logger .warning (f"Primary download failed: { msg } " )
362+
363+ # Attempt no-proxy re-resolution+download if proxy was in play
364+ tried_alt = False
365+ if PROXY_ENABLED and not force_no_proxy and PROXY_SCOPE in ("all" , "ytdlp" ):
366+ try :
367+ with disabled_proxy_env ():
368+ ep2 = build_episode (
369+ link = link , slug = slug , season = season , episode = episode
370+ )
371+ direct2 , chosen2 = get_direct_url_with_fallback (
372+ ep2 , preferred = provider , language = language
373+ )
374+ logger .info (
375+ f"Retrying download without proxy using provider { chosen2 } "
376+ )
377+ temp_path , info = _ydl_download (
378+ direct2 ,
379+ dest_dir ,
380+ title_hint = base_hint ,
381+ cookiefile = cookiefile ,
382+ progress_cb = progress_cb ,
383+ stop_event = stop_event ,
384+ force_no_proxy = True ,
385+ )
386+ tried_alt = True
387+ except Exception as e2 :
388+ logger .error (f"No-proxy fallback download failed: { e2 } " )
389+ # fall through to final error or other alt attempts
390+
391+ if not tried_alt :
392+ # As a last resort, try a different provider once (if any available)
393+ try :
394+ providers_left = [p for p in PROVIDER_ORDER if p != (provider or "" )]
395+ for p in providers_left :
396+ try :
397+ direct3 , chosen3 = get_direct_url_with_fallback (
398+ ep , preferred = p , language = language
399+ )
400+ logger .info (
401+ f"Retrying download via alternate provider { chosen3 } "
402+ )
403+ temp_path , info = _ydl_download (
404+ direct3 ,
405+ dest_dir ,
406+ title_hint = base_hint ,
407+ cookiefile = cookiefile ,
408+ progress_cb = progress_cb ,
409+ stop_event = stop_event ,
410+ force_no_proxy = force_no_proxy ,
411+ )
412+ tried_alt = True
413+ break
414+ except Exception as e3 :
415+ logger .warning (
416+ f"Alternate provider { p } failed to download: { e3 } "
417+ )
418+ except Exception as e4 :
419+ logger .debug (f"Alternate-provider resolution failed: { e4 } " )
420+
421+ if not tried_alt :
422+ # Give up with the original failure
423+ raise
351424
352425 logger .info (f"Download complete, renaming to release schema." )
353426 final_path = rename_to_release (
0 commit comments