|
| 1 | +# 22.12.25 |
| 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 import os_manager, config_manager, start_message |
| 14 | +from StreamingCommunity.Api.Template import site_constants, MediaItem |
| 15 | +from StreamingCommunity.Api.Template.episode_manager import ( |
| 16 | + manage_selection, |
| 17 | + map_episode_title, |
| 18 | + validate_selection, |
| 19 | + validate_episode_selection, |
| 20 | + display_episodes_list, |
| 21 | + display_seasons_list |
| 22 | +) |
| 23 | +from StreamingCommunity.Lib.DASH.downloader import DASH_Downloader |
| 24 | +from StreamingCommunity.Lib.HLS import HLS_Downloader |
| 25 | + |
| 26 | + |
| 27 | + |
| 28 | +# Logic |
| 29 | +from .util.ScrapeSerie import GetSerieInfo |
| 30 | +from .util.get_license import get_playback_info, generate_license_headers,DiscoveryEUAPI |
| 31 | + |
| 32 | + |
| 33 | +# Variables |
| 34 | +msg = Prompt() |
| 35 | +console = Console() |
| 36 | +extension_output = config_manager.config.get("M3U8_CONVERSION", "extension") |
| 37 | + |
| 38 | + |
| 39 | +def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: GetSerieInfo) -> Tuple[str, bool]: |
| 40 | + """ |
| 41 | + Download a specific episode |
| 42 | + |
| 43 | + Parameters: |
| 44 | + index_season_selected (int): Season number |
| 45 | + index_episode_selected (int): Episode index |
| 46 | + scrape_serie (GetSerieInfo): Series scraper instance |
| 47 | + |
| 48 | + Returns: |
| 49 | + Tuple[str, bool]: (output_path, stopped_status) |
| 50 | + """ |
| 51 | + start_message() |
| 52 | + |
| 53 | + # Get episode information |
| 54 | + obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected - 1) |
| 55 | + |
| 56 | + # Get the real season number. Due to some seasons not having free episodes there's a mismatch between seasons and their index number. |
| 57 | + index_season_selected = scrape_serie.getRealNumberSeason(index_season_selected) |
| 58 | + |
| 59 | + console.print(f"\n[yellow]Download: [red]{site_constants.SITE_NAME} → [cyan]{scrape_serie.series_name} \\ [magenta]{obj_episode.name} ([cyan]S{index_season_selected}E{index_episode_selected}) \n") |
| 60 | + |
| 61 | + # Define output path |
| 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_manager.get_sanitize_path( |
| 64 | + os.path.join(site_constants.SERIES_FOLDER, scrape_serie.series_name, f"S{index_season_selected}") |
| 65 | + ) |
| 66 | + |
| 67 | + # Get playback information using video_id |
| 68 | + playback_info = get_playback_info(obj_episode.video_id) |
| 69 | + |
| 70 | + if (str(playback_info['type']).strip().lower() == 'dash' and playback_info['license_url'] is None) or (str(playback_info['type']).strip().lower() != 'hls' and str(playback_info['type']).strip().lower() != 'dash' ): |
| 71 | + console.print(f"[red]Unsupported streaming type. Playbackk info: {playback_info}") |
| 72 | + return None, False |
| 73 | + |
| 74 | + # Check the type of stream |
| 75 | + status = None |
| 76 | + if playback_info['type'] == 'dash': |
| 77 | + license_headers = generate_license_headers(playback_info['license_token']) |
| 78 | + |
| 79 | + # Download the episode |
| 80 | + dash_process = DASH_Downloader( |
| 81 | + license_url=playback_info['license_url'], |
| 82 | + mpd_url=playback_info['mpd_url'], |
| 83 | + output_path=os.path.join(mp4_path, mp4_name), |
| 84 | + ) |
| 85 | + |
| 86 | + dash_process.parse_manifest(custom_headers=license_headers) |
| 87 | + |
| 88 | + if dash_process.download_and_decrypt(custom_headers=license_headers): |
| 89 | + dash_process.finalize_output() |
| 90 | + |
| 91 | + # Get final status |
| 92 | + status = dash_process.get_status() |
| 93 | + |
| 94 | + elif playback_info['type'] == 'hls': |
| 95 | + |
| 96 | + api = DiscoveryEUAPI() |
| 97 | + headers = api.get_request_headers() |
| 98 | + |
| 99 | + # Download the episode |
| 100 | + status = HLS_Downloader( |
| 101 | + m3u8_url=playback_info['mpd_url'], #mpd_url is just a typo: it is a hls |
| 102 | + headers=headers, |
| 103 | + output_path=os.path.join(mp4_path, mp4_name), |
| 104 | + ).start() |
| 105 | + |
| 106 | + if status['error'] is not None and status['path']: |
| 107 | + try: |
| 108 | + os.remove(status['path']) |
| 109 | + except Exception: |
| 110 | + pass |
| 111 | + |
| 112 | + return status['path'], status['stopped'] |
| 113 | + |
| 114 | + |
| 115 | +def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, download_all: bool = False, episode_selection: str = None) -> None: |
| 116 | + """ |
| 117 | + Handle downloading episodes for a specific season |
| 118 | + |
| 119 | + Parameters: |
| 120 | + index_season_selected (int): Season number |
| 121 | + scrape_serie (GetSerieInfo): Series scraper instance |
| 122 | + download_all (bool): Whether to download all episodes |
| 123 | + episode_selection (str, optional): Pre-defined episode selection |
| 124 | + """ |
| 125 | + # Get episodes for the selected season |
| 126 | + episodes = scrape_serie.getEpisodeSeasons(index_season_selected) |
| 127 | + episodes_count = len(episodes) |
| 128 | + |
| 129 | + if episodes_count == 0: |
| 130 | + console.print(f"[red]No episodes found for season {index_season_selected}") |
| 131 | + return |
| 132 | + |
| 133 | + if download_all: |
| 134 | + for i_episode in range(1, episodes_count + 1): |
| 135 | + path, stopped = download_video(index_season_selected, i_episode, scrape_serie) |
| 136 | + if stopped: |
| 137 | + break |
| 138 | + else: |
| 139 | + if episode_selection is not None: |
| 140 | + last_command = episode_selection |
| 141 | + console.print(f"\n[cyan]Using provided episode selection: [yellow]{episode_selection}") |
| 142 | + else: |
| 143 | + last_command = display_episodes_list(episodes) |
| 144 | + |
| 145 | + # Prompt user for episode selection |
| 146 | + list_episode_select = manage_selection(last_command, episodes_count) |
| 147 | + list_episode_select = validate_episode_selection(list_episode_select, episodes_count) |
| 148 | + |
| 149 | + # Download selected episodes |
| 150 | + for i_episode in list_episode_select: |
| 151 | + path, stopped = download_video(index_season_selected, i_episode, scrape_serie) |
| 152 | + if stopped: |
| 153 | + break |
| 154 | + |
| 155 | + |
| 156 | +def download_series(select_season: MediaItem, season_selection: str = None, episode_selection: str = None) -> None: |
| 157 | + """ |
| 158 | + Handle downloading a complete series |
| 159 | + |
| 160 | + Parameters: |
| 161 | + select_season (MediaItem): Series metadata from search |
| 162 | + season_selection (str, optional): Pre-defined season selection |
| 163 | + episode_selection (str, optional): Pre-defined episode selection |
| 164 | + """ |
| 165 | + id_parts = select_season.id.split('|') |
| 166 | + |
| 167 | + # Initialize series scraper |
| 168 | + scrape_serie = GetSerieInfo(id_parts[1], id_parts[0]) |
| 169 | + seasons_count = scrape_serie.getNumberSeason() |
| 170 | + |
| 171 | + if seasons_count == 0: |
| 172 | + console.print("[red]No seasons found for this series") |
| 173 | + return |
| 174 | + |
| 175 | + # Handle season selection |
| 176 | + if season_selection is None: |
| 177 | + index_season_selected = display_seasons_list(scrape_serie.seasons_manager) |
| 178 | + else: |
| 179 | + index_season_selected = season_selection |
| 180 | + console.print(f"\n[cyan]Using provided season selection: [yellow]{season_selection}") |
| 181 | + |
| 182 | + # Validate the selection |
| 183 | + list_season_select = manage_selection(index_season_selected, seasons_count) |
| 184 | + list_season_select = validate_selection(list_season_select, seasons_count) |
| 185 | + |
| 186 | + # Loop through selected seasons and download episodes |
| 187 | + for i_season in list_season_select: |
| 188 | + if len(list_season_select) > 1 or index_season_selected == "*": |
| 189 | + download_episode(i_season, scrape_serie, download_all=True) |
| 190 | + else: |
| 191 | + download_episode(i_season, scrape_serie, download_all=False, episode_selection=episode_selection) |
0 commit comments