1+ # 26.11.2025
2+
3+ import os
4+ from typing import Tuple
5+
6+
7+ # External library
8+ from rich .console import Console
9+ from rich .prompt import Prompt
10+
11+
12+ # Internal utilities
13+ from StreamingCommunity .Util .message import start_message
14+ from StreamingCommunity .Util .config_json import config_manager
15+
16+
17+ # Logic class
18+ from ..realtime .util .ScrapeSerie import GetSerieInfo
19+ from StreamingCommunity .Api .Template .Util import (
20+ manage_selection ,
21+ map_episode_title ,
22+ validate_selection ,
23+ validate_episode_selection ,
24+ display_episodes_list ,
25+ display_seasons_list
26+ )
27+ from StreamingCommunity .Api .Template .config_loader import site_constant
28+ from StreamingCommunity .Api .Template .Class .SearchType import MediaItem
29+
30+
31+ # Player
32+ from StreamingCommunity import HLS_Downloader
33+ from ..realtime .util .get_license import get_bearer_token , get_playback_url
34+
35+
36+ # Variable
37+ msg = Prompt ()
38+ console = Console ()
39+ extension_output = config_manager .get ("M3U8_CONVERSION" , "extension" )
40+
41+
42+ def download_video (index_season_selected : int , index_episode_selected : int , scrape_serie : GetSerieInfo ) -> Tuple [str ,bool ]:
43+ """
44+ Downloads a specific episode from the specified season.
45+
46+ Parameters:
47+ - index_season_selected (int): Season number
48+ - index_episode_selected (int): Episode index
49+ - scrape_serie (GetSerieInfo): Scraper object with series information
50+
51+ Returns:
52+ - str: Path to downloaded file
53+ - bool: Whether download was stopped
54+ """
55+ start_message ()
56+
57+ # Get episode information
58+ obj_episode = scrape_serie .selectEpisode (index_season_selected , index_episode_selected - 1 )
59+ console .print (f"\n [bold yellow]Download:[/bold yellow] [red]{ site_constant .SITE_NAME } [/red] → [cyan]{ scrape_serie .series_name } [/cyan] \\ [bold magenta]{ obj_episode .name } [/bold magenta] ([cyan]S{ index_season_selected } E{ index_episode_selected } [/cyan]) \n " )
60+
61+ # Define filename and path for the downloaded video
62+ mp4_name = f"{ map_episode_title (scrape_serie .series_name , index_season_selected , index_episode_selected , obj_episode .name )} .{ extension_output } "
63+ mp4_path = os .path .join (site_constant .SERIES_FOLDER , scrape_serie .series_name , f"S{ index_season_selected } " )
64+
65+ # Get m3u8 playlist
66+ bearer_token = get_bearer_token ()
67+ master_playlist = get_playback_url (obj_episode .id , bearer_token )
68+
69+ # Download the episode
70+ hls_process = HLS_Downloader (
71+ m3u8_url = master_playlist ,
72+ output_path = os .path .join (mp4_path , mp4_name )
73+ ).start ()
74+
75+ if hls_process ['error' ] is not None :
76+ try :
77+ os .remove (hls_process ['path' ])
78+ except Exception :
79+ pass
80+
81+ return hls_process ['path' ], hls_process ['stopped' ]
82+
83+
84+ def download_episode (index_season_selected : int , scrape_serie : GetSerieInfo , download_all : bool = False , episode_selection : str = None ) -> None :
85+ """
86+ Handle downloading episodes for a specific season.
87+
88+ Parameters:
89+ - index_season_selected (int): Season number
90+ - scrape_serie (GetSerieInfo): Scraper object with series information
91+ - download_all (bool): Whether to download all episodes
92+ - episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
93+ """
94+ # Get episodes for the selected season
95+ episodes = scrape_serie .getEpisodeSeasons (index_season_selected )
96+ episodes_count = len (episodes )
97+
98+ if episodes_count == 0 :
99+ console .print (f"[red]No episodes found for season { index_season_selected } " )
100+ return
101+
102+ if download_all :
103+ # Download all episodes in the season
104+ for i_episode in range (1 , episodes_count + 1 ):
105+ path , stopped = download_video (index_season_selected , i_episode , scrape_serie )
106+
107+ if stopped :
108+ break
109+
110+ console .print (f"\n [red]End downloaded [yellow]season: [red]{ index_season_selected } ." )
111+
112+ else :
113+ # Display episodes list and manage user selection
114+ if episode_selection is None :
115+ last_command = display_episodes_list (episodes )
116+ else :
117+ last_command = episode_selection
118+ console .print (f"\n [cyan]Using provided episode selection: [yellow]{ episode_selection } " )
119+
120+ # Validate the selection
121+ list_episode_select = manage_selection (last_command , episodes_count )
122+ list_episode_select = validate_episode_selection (list_episode_select , episodes_count )
123+
124+ # Download selected episodes if not stopped
125+ for i_episode in list_episode_select :
126+ path , stopped = download_video (index_season_selected , i_episode , scrape_serie )
127+
128+ if stopped :
129+ break
130+
131+
132+ def download_series (select_season : MediaItem , season_selection : str = None , episode_selection : str = None ) -> None :
133+ """
134+ Handle downloading a complete series.
135+
136+ Parameters:
137+ - select_season (MediaItem): Series metadata from search
138+ - season_selection (str, optional): Pre-defined season selection that bypasses manual input
139+ - episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
140+ """
141+ start_message ()
142+
143+ # Init class
144+ scrape_serie = GetSerieInfo (select_season .url )
145+
146+ # Collect information about season
147+ scrape_serie .getNumberSeason ()
148+ seasons_count = len (scrape_serie .seasons_manager )
149+
150+ # If season_selection is provided, use it instead of asking for input
151+ if season_selection is None :
152+ index_season_selected = display_seasons_list (scrape_serie .seasons_manager )
153+ else :
154+ index_season_selected = season_selection
155+ console .print (f"\n [cyan]Using provided season selection: [yellow]{ season_selection } " )
156+
157+ # Validate the selection
158+ list_season_select = manage_selection (index_season_selected , seasons_count )
159+ list_season_select = validate_selection (list_season_select , seasons_count )
160+
161+ # Loop through the selected seasons and download episodes
162+ for i_season in list_season_select :
163+ try :
164+ season = scrape_serie .seasons_manager .seasons [i_season - 1 ]
165+ except IndexError :
166+ console .print (f"[red]Season index { i_season } not found! Available seasons: { [s .number for s in scrape_serie .seasons_manager .seasons ]} " )
167+ continue
168+
169+ season_number = season .number
170+
171+ if len (list_season_select ) > 1 or index_season_selected == "*" :
172+ download_episode (season_number , scrape_serie , download_all = True )
173+ else :
174+ download_episode (season_number , scrape_serie , download_all = False , episode_selection = episode_selection )
0 commit comments