StreamingCommunity 2.9.7__py3-none-any.whl → 2.9.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of StreamingCommunity might be problematic. Click here for more details.

Files changed (61) hide show
  1. StreamingCommunity/Api/Player/ddl.py +2 -10
  2. StreamingCommunity/Api/Player/mediapolisvod.py +64 -0
  3. StreamingCommunity/Api/Player/sweetpixel.py +3 -3
  4. StreamingCommunity/Api/Player/vixcloud.py +4 -9
  5. StreamingCommunity/Api/Site/1337xx/__init__.py +1 -1
  6. StreamingCommunity/Api/Site/altadefinizione/__init__.py +23 -7
  7. StreamingCommunity/Api/Site/altadefinizione/film.py +0 -1
  8. StreamingCommunity/Api/Site/altadefinizione/series.py +66 -70
  9. StreamingCommunity/Api/Site/altadefinizione/site.py +2 -1
  10. StreamingCommunity/Api/Site/altadefinizione/util/ScrapeSerie.py +37 -2
  11. StreamingCommunity/Api/Site/animeunity/__init__.py +29 -10
  12. StreamingCommunity/Api/Site/animeunity/film.py +40 -0
  13. StreamingCommunity/Api/Site/animeunity/serie.py +153 -0
  14. StreamingCommunity/Api/Site/animeunity/site.py +1 -2
  15. StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py +15 -0
  16. StreamingCommunity/Api/Site/animeworld/__init__.py +25 -12
  17. StreamingCommunity/Api/Site/animeworld/film.py +63 -0
  18. StreamingCommunity/Api/Site/animeworld/serie.py +25 -22
  19. StreamingCommunity/Api/Site/animeworld/site.py +2 -1
  20. StreamingCommunity/Api/Site/animeworld/util/ScrapeSerie.py +32 -5
  21. StreamingCommunity/Api/Site/cb01new/__init__.py +1 -1
  22. StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +1 -1
  23. StreamingCommunity/Api/Site/ddlstreamitaly/series.py +31 -32
  24. StreamingCommunity/Api/Site/ddlstreamitaly/site.py +2 -2
  25. StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +30 -2
  26. StreamingCommunity/Api/Site/guardaserie/__init__.py +21 -7
  27. StreamingCommunity/Api/Site/guardaserie/series.py +55 -53
  28. StreamingCommunity/Api/Site/guardaserie/site.py +3 -2
  29. StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +27 -1
  30. StreamingCommunity/Api/Site/raiplay/__init__.py +93 -0
  31. StreamingCommunity/Api/Site/raiplay/film.py +65 -0
  32. StreamingCommunity/Api/Site/raiplay/series.py +162 -0
  33. StreamingCommunity/Api/Site/raiplay/site.py +166 -0
  34. StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py +127 -0
  35. StreamingCommunity/Api/Site/streamingcommunity/__init__.py +29 -22
  36. StreamingCommunity/Api/Site/streamingcommunity/film.py +1 -2
  37. StreamingCommunity/Api/Site/streamingcommunity/series.py +76 -90
  38. StreamingCommunity/Api/Site/streamingcommunity/site.py +1 -3
  39. StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +41 -15
  40. StreamingCommunity/Api/Template/site.py +2 -2
  41. StreamingCommunity/Lib/Downloader/HLS/downloader.py +1 -1
  42. StreamingCommunity/Lib/Downloader/HLS/segments.py +2 -3
  43. StreamingCommunity/Lib/Downloader/MP4/downloader.py +2 -1
  44. StreamingCommunity/Lib/FFmpeg/util.py +47 -17
  45. StreamingCommunity/Lib/M3U8/estimator.py +50 -21
  46. StreamingCommunity/Lib/M3U8/parser.py +26 -6
  47. StreamingCommunity/Upload/update.py +22 -3
  48. StreamingCommunity/Upload/version.py +1 -1
  49. StreamingCommunity/Util/config_json.py +425 -274
  50. StreamingCommunity/Util/table.py +4 -2
  51. StreamingCommunity/run.py +1 -1
  52. {streamingcommunity-2.9.7.dist-info → streamingcommunity-2.9.9.dist-info}/METADATA +1 -1
  53. streamingcommunity-2.9.9.dist-info/RECORD +91 -0
  54. {streamingcommunity-2.9.7.dist-info → streamingcommunity-2.9.9.dist-info}/WHEEL +1 -1
  55. StreamingCommunity/Api/Site/animeunity/film_serie.py +0 -181
  56. StreamingCommunity/Api/Site/mostraguarda/__init__.py +0 -73
  57. StreamingCommunity/Api/Site/mostraguarda/film.py +0 -93
  58. streamingcommunity-2.9.7.dist-info/RECORD +0 -85
  59. {streamingcommunity-2.9.7.dist-info → streamingcommunity-2.9.9.dist-info}/entry_points.txt +0 -0
  60. {streamingcommunity-2.9.7.dist-info → streamingcommunity-2.9.9.dist-info}/licenses/LICENSE +0 -0
  61. {streamingcommunity-2.9.7.dist-info → streamingcommunity-2.9.9.dist-info}/top_level.txt +0 -0
@@ -47,7 +47,6 @@ class GetSerieInfo:
47
47
  Returns:
48
48
  List[Dict[str, str]]: List of dictionaries containing episode information.
49
49
  """
50
-
51
50
  try:
52
51
  response = httpx.get(f"{self.url}?area=online", cookies=self.cookies, headers=self.headers, timeout=max_timeout)
53
52
  response.raise_for_status()
@@ -81,4 +80,33 @@ class GetSerieInfo:
81
80
 
82
81
  self.list_episodes = list_dict_episode
83
82
  return list_dict_episode
84
-
83
+
84
+
85
+ # ------------- FOR GUI -------------
86
+ def getNumberSeason(self) -> int:
87
+ """
88
+ Get the total number of seasons available for the series.
89
+ Note: DDLStreamItaly typically provides content organized as threads, not seasons.
90
+ """
91
+ return 1
92
+
93
+ def getEpisodeSeasons(self, season_number: int = 1) -> list:
94
+ """
95
+ Get all episodes for a specific season.
96
+ Note: For DDLStreamItaly, this returns all episodes as they're typically in one list.
97
+ """
98
+ if not self.list_episodes:
99
+ self.list_episodes = self.get_episode_number()
100
+
101
+ return self.list_episodes
102
+
103
+ def selectEpisode(self, season_number: int = 1, episode_index: int = 0) -> dict:
104
+ """
105
+ Get information for a specific episode.
106
+ """
107
+ episodes = self.getEpisodeSeasons()
108
+ if not episodes or episode_index < 0 or episode_index >= len(episodes):
109
+ logging.error(f"Episode index {episode_index} is out of range")
110
+ return None
111
+
112
+ return episodes[episode_index]
@@ -30,24 +30,38 @@ msg = Prompt()
30
30
  console = Console()
31
31
 
32
32
 
33
- def process_search_result(select_title):
33
+ def process_search_result(select_title, selections=None):
34
34
  """
35
35
  Handles the search result and initiates the download for either a film or series.
36
+
37
+ Parameters:
38
+ select_title (MediaItem): The selected media item
39
+ selections (dict, optional): Dictionary containing selection inputs that bypass manual input
40
+ {'season': season_selection, 'episode': episode_selection}
36
41
  """
37
- download_series(select_title)
42
+ season_selection = None
43
+ episode_selection = None
44
+
45
+ if selections:
46
+ season_selection = selections.get('season')
47
+ episode_selection = selections.get('episode')
48
+
49
+ download_series(select_title, season_selection, episode_selection)
38
50
 
39
- def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
51
+ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
40
52
  """
41
- Main function of the application for search film, series and anime.
53
+ Main function of the application for search.
42
54
 
43
55
  Parameters:
44
56
  string_to_search (str, optional): String to search for
45
- get_onylDatabase (bool, optional): If True, return only the database object
57
+ get_onlyDatabase (bool, optional): If True, return only the database object
46
58
  direct_item (dict, optional): Direct item to process (bypass search)
59
+ selections (dict, optional): Dictionary containing selection inputs that bypass manual input
60
+ {'season': season_selection, 'episode': episode_selection}
47
61
  """
48
62
  if direct_item:
49
63
  select_title = MediaItem(**direct_item)
50
- process_search_result(select_title)
64
+ process_search_result(select_title, selections)
51
65
  return
52
66
 
53
67
  if string_to_search is None:
@@ -62,7 +76,7 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
62
76
 
63
77
  if len_database > 0:
64
78
  select_title = get_select_title(table_show_manager, media_search_manager)
65
- process_search_result(select_title)
79
+ process_search_result(select_title, selections)
66
80
 
67
81
  else:
68
82
 
@@ -1,6 +1,7 @@
1
1
  # 13.06.24
2
2
 
3
3
  import os
4
+ import logging
4
5
  from typing import Tuple
5
6
 
6
7
 
@@ -39,22 +40,22 @@ console = Console()
39
40
 
40
41
  def download_video(index_season_selected: int, index_episode_selected: int, scape_info_serie: GetSerieInfo) -> Tuple[str,bool]:
41
42
  """
42
- Download a single episode video.
43
+ Downloads a specific episode from a specified season.
43
44
 
44
45
  Parameters:
45
- - tv_name (str): Name of the TV series.
46
- - index_season_selected (int): Index of the selected season.
47
- - index_episode_selected (int): Index of the selected episode.
46
+ - index_season_selected (int): Season number
47
+ - index_episode_selected (int): Episode index
48
+ - scape_info_serie (GetSerieInfo): Scraper object with series information
48
49
 
49
- Return:
50
- - str: output path
51
- - bool: kill handler status
50
+ Returns:
51
+ - str: Path to downloaded file
52
+ - bool: Whether download was stopped
52
53
  """
53
54
  start_message()
54
- index_season_selected = dynamic_format_number(str(index_season_selected))
55
55
 
56
- # Get info about episode
57
- obj_episode = scape_info_serie.list_episodes[index_episode_selected - 1]
56
+ # Get episode information
57
+ obj_episode = scape_info_serie.selectEpisode(index_season_selected, index_episode_selected-1)
58
+ index_season_selected = dynamic_format_number(str(index_season_selected))
58
59
  console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.get('name')}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
59
60
 
60
61
  # Define filename and path for the downloaded video
@@ -80,24 +81,23 @@ def download_video(index_season_selected: int, index_episode_selected: int, scap
80
81
  return r_proc['path'], r_proc['stopped']
81
82
 
82
83
 
83
- def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int, download_all: bool = False) -> None:
84
+ def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int, download_all: bool = False, episode_selection: str = None) -> None:
84
85
  """
85
- Download all episodes of a season.
86
+ Handle downloading episodes for a specific season.
86
87
 
87
88
  Parameters:
88
- - tv_name (str): Name of the TV series.
89
- - index_season_selected (int): Index of the selected season.
90
- - download_all (bool): Download all seasons episodes
89
+ - scape_info_serie (GetSerieInfo): Scraper object with series information
90
+ - index_season_selected (int): Season number
91
+ - download_all (bool): Whether to download all episodes
92
+ - episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
91
93
  """
92
-
93
- # Start message and collect information about episodes
94
- start_message()
95
- list_dict_episode = scape_info_serie.get_episode_number(index_season_selected)
96
- episodes_count = len(list_dict_episode)
94
+ # Get episodes for the selected season
95
+ episodes = scape_info_serie.get_episode_number(index_season_selected)
96
+ episodes_count = len(episodes)
97
97
 
98
98
  if download_all:
99
-
100
- # Download all episodes without asking
99
+
100
+ # Download all episodes in the season
101
101
  for i_episode in range(1, episodes_count + 1):
102
102
  path, stopped = download_video(index_season_selected, i_episode, scape_info_serie)
103
103
 
@@ -109,14 +109,15 @@ def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int,
109
109
  else:
110
110
 
111
111
  # Display episodes list and manage user selection
112
- last_command = display_episodes_list(scape_info_serie.list_episodes)
112
+ if episode_selection is None:
113
+ last_command = display_episodes_list(scape_info_serie.list_episodes)
114
+ else:
115
+ last_command = episode_selection
116
+ console.print(f"\n[cyan]Using provided episode selection: [yellow]{episode_selection}")
117
+
118
+ # Validate the selection
113
119
  list_episode_select = manage_selection(last_command, episodes_count)
114
-
115
- try:
116
- list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
117
- except ValueError as e:
118
- console.print(f"[red]{str(e)}")
119
- return
120
+ list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
120
121
 
121
122
  # Download selected episodes
122
123
  for i_episode in list_episode_select:
@@ -126,46 +127,47 @@ def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int,
126
127
  break
127
128
 
128
129
 
129
- def download_series(dict_serie: MediaItem) -> None:
130
+ def download_series(dict_serie: MediaItem, season_selection: str = None, episode_selection: str = None) -> None:
130
131
  """
131
- Download all episodes of a TV series.
132
+ Handle downloading a complete series.
132
133
 
133
134
  Parameters:
134
- - dict_serie (MediaItem): obj with url name type and score
135
+ - dict_serie (MediaItem): Series metadata from search
136
+ - season_selection (str, optional): Pre-defined season selection that bypasses manual input
137
+ - episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
135
138
  """
136
-
137
- # Start message and set up video source
138
139
  start_message()
139
140
 
140
- # Init class
141
- scape_info_serie = GetSerieInfo(dict_serie)
142
-
143
- # Collect information about seasons
144
- seasons_count = scape_info_serie.get_seasons_number()
141
+ # Create class
142
+ scrape_serie = GetSerieInfo(dict_serie)
145
143
 
144
+ # Get season count
145
+ seasons_count = scrape_serie.get_seasons_number()
146
+
146
147
  # Prompt user for season selection and download episodes
147
148
  console.print(f"\n[green]Seasons found: [red]{seasons_count}")
148
- index_season_selected = msg.ask(
149
- "\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
150
- "[yellow](e.g., 1-2) [cyan]for a range of seasons, or [yellow](e.g., 3-*) [cyan]to download from a specific season to the end"
151
- )
152
-
153
- # Manage and validate the selection
154
- list_season_select = manage_selection(index_season_selected, seasons_count)
155
149
 
156
- try:
157
- list_season_select = validate_selection(list_season_select, seasons_count)
158
- except ValueError as e:
159
- console.print(f"[red]{str(e)}")
160
- return
150
+ # If season_selection is provided, use it instead of asking for input
151
+ if season_selection is None:
152
+ index_season_selected = msg.ask(
153
+ "\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
154
+ "[yellow](e.g., 1-2) [cyan]for a range of seasons, or [yellow](e.g., 3-*) [cyan]to download from a specific season to the end"
155
+ )
156
+ else:
157
+ index_season_selected = season_selection
158
+ console.print(f"\n[cyan]Using provided season selection: [yellow]{season_selection}")
159
+
160
+ # Validate the selection
161
+ list_season_select = manage_selection(index_season_selected, seasons_count)
162
+ list_season_select = validate_selection(list_season_select, seasons_count)
161
163
 
162
164
  # Loop through the selected seasons and download episodes
163
165
  for i_season in list_season_select:
164
166
  if len(list_season_select) > 1 or index_season_selected == "*":
165
167
 
166
168
  # Download all episodes if multiple seasons are selected or if '*' is used
167
- download_episode(scape_info_serie, i_season, download_all=True)
169
+ download_episode(scrape_serie, i_season, download_all=True)
168
170
  else:
169
171
 
170
172
  # Otherwise, let the user select specific episodes for the single season
171
- download_episode(scape_info_serie, i_season, download_all=False)
173
+ download_episode(scrape_serie, i_season, download_all=False, episode_selection=episode_selection)
@@ -62,9 +62,10 @@ def title_search(query: str) -> int:
62
62
  link = serie_div.find('a').get("href")
63
63
 
64
64
  serie_info = {
65
- 'name': title,
65
+ 'name': title.replace("streaming guardaserie", ""),
66
66
  'url': link,
67
- 'type': 'tv'
67
+ 'type': 'tv',
68
+ 'image': f"{site_constant.FULL_URL}/{serie_div.find('img').get('src')}",
68
69
  }
69
70
 
70
71
  media_search_manager.add_media(serie_info)
@@ -104,4 +104,30 @@ class GetSerieInfo:
104
104
  except Exception as e:
105
105
  logging.error(f"Error parsing HTML page: {e}")
106
106
 
107
- return []
107
+ return []
108
+
109
+
110
+ # ------------- FOR GUI -------------
111
+ def getNumberSeason(self) -> int:
112
+ """
113
+ Get the total number of seasons available for the series.
114
+ """
115
+ return self.get_seasons_number()
116
+
117
+ def getEpisodeSeasons(self, season_number: int) -> list:
118
+ """
119
+ Get all episodes for a specific season.
120
+ """
121
+ episodes = self.get_episode_number(season_number)
122
+ return episodes
123
+
124
+ def selectEpisode(self, season_number: int, episode_index: int) -> dict:
125
+ """
126
+ Get information for a specific episode in a specific season.
127
+ """
128
+ episodes = self.getEpisodeSeasons(season_number)
129
+ if not episodes or episode_index < 0 or episode_index >= len(episodes):
130
+ logging.error(f"Episode index {episode_index} is out of range for season {season_number}")
131
+ return None
132
+
133
+ return episodes[episode_index]
@@ -0,0 +1,93 @@
1
+ # 21.05.24
2
+
3
+
4
+ # External library
5
+ from rich.console import Console
6
+ from rich.prompt import Prompt
7
+
8
+
9
+ # Internal utilities
10
+ from StreamingCommunity.Api.Template import get_select_title
11
+ from StreamingCommunity.Api.Template.config_loader import site_constant
12
+ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
13
+
14
+
15
+ # Logic class
16
+ from .site import title_search, table_show_manager, media_search_manager
17
+ from .series import download_series
18
+ from .film import download_film
19
+
20
+
21
+ # Variable
22
+ indice = 8
23
+ _useFor = "film_serie"
24
+ _deprecate = False
25
+ _priority = 3
26
+ _engineDownload = "hls"
27
+
28
+ msg = Prompt()
29
+ console = Console()
30
+
31
+
32
+ def get_user_input(string_to_search: str = None):
33
+ """
34
+ Asks the user to input a search term.
35
+ """
36
+ return msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
37
+
38
+ def process_search_result(select_title, selections=None):
39
+ """
40
+ Handles the search result and initiates the download for either a film or series.
41
+
42
+ Parameters:
43
+ select_title (MediaItem): The selected media item
44
+ selections (dict, optional): Dictionary containing selection inputs that bypass manual input
45
+ {'season': season_selection, 'episode': episode_selection}
46
+ """
47
+ if select_title.type == 'tv':
48
+ season_selection = None
49
+ episode_selection = None
50
+
51
+ if selections:
52
+ season_selection = selections.get('season')
53
+ episode_selection = selections.get('episode')
54
+
55
+ download_series(select_title, season_selection, episode_selection)
56
+
57
+ else:
58
+ download_film(select_title)
59
+
60
+ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
61
+ """
62
+ Main function of the application for search.
63
+
64
+ Parameters:
65
+ string_to_search (str, optional): String to search for
66
+ get_onlyDatabase (bool, optional): If True, return only the database object
67
+ direct_item (dict, optional): Direct item to process (bypass search)
68
+ selections (dict, optional): Dictionary containing selection inputs that bypass manual input
69
+ {'season': season_selection, 'episode': episode_selection}
70
+ """
71
+ if direct_item:
72
+ select_title = MediaItem(**direct_item)
73
+ process_search_result(select_title, selections)
74
+ return
75
+
76
+ if string_to_search is None:
77
+ string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
78
+
79
+ # Search on database
80
+ len_database = title_search(string_to_search)
81
+
82
+ # If only the database is needed, return the manager
83
+ if get_onlyDatabase:
84
+ return media_search_manager
85
+
86
+ if len_database > 0:
87
+ select_title = get_select_title(table_show_manager, media_search_manager)
88
+ process_search_result(select_title, selections)
89
+
90
+ else:
91
+ # If no results are found, ask again
92
+ console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
93
+ search()
@@ -0,0 +1,65 @@
1
+ # 3.12.23
2
+
3
+ import os
4
+ from typing import Tuple
5
+
6
+
7
+ # External library
8
+ import httpx
9
+ from rich.console import Console
10
+
11
+
12
+ # Internal utilities
13
+ from StreamingCommunity.Util.os import os_manager
14
+ from StreamingCommunity.Util.message import start_message
15
+ from StreamingCommunity.Lib.Downloader import HLS_Downloader
16
+ from StreamingCommunity.Util.headers import get_headers
17
+
18
+
19
+ # Logic class
20
+ from StreamingCommunity.Api.Template.config_loader import site_constant
21
+ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
22
+
23
+
24
+ # Player
25
+ from StreamingCommunity.Api.Player.mediapolisvod import VideoSource
26
+
27
+
28
+ # Variable
29
+ console = Console()
30
+
31
+
32
+ def download_film(select_title: MediaItem) -> Tuple[str, bool]:
33
+ """
34
+ Downloads a film using the provided MediaItem information.
35
+
36
+ Parameters:
37
+ - select_title (MediaItem): The media item containing film information
38
+
39
+ Return:
40
+ - str: Path to downloaded file
41
+ - bool: Whether download was stopped
42
+ """
43
+ start_message()
44
+ console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
45
+
46
+ # Extract m3u8 URL from the film's URL
47
+ response = httpx.get(select_title.url + ".json", headers=get_headers(), timeout=10)
48
+ first_item_path = "https://www.raiplay.it" + response.json().get("first_item_path")
49
+ master_playlist = VideoSource.extract_m3u8_url(first_item_path)
50
+
51
+ # Define the filename and path for the downloaded film
52
+ title_name = os_manager.get_sanitize_file(select_title.name) + ".mp4"
53
+ mp4_path = os.path.join(site_constant.MOVIE_FOLDER, title_name.replace(".mp4", ""))
54
+
55
+ # Download the film using the m3u8 playlist, and output filename
56
+ r_proc = HLS_Downloader(
57
+ m3u8_url=master_playlist,
58
+ output_path=os.path.join(mp4_path, title_name)
59
+ ).start()
60
+
61
+ if r_proc['error'] is not None:
62
+ try: os.remove(r_proc['path'])
63
+ except: pass
64
+
65
+ return r_proc['path'], r_proc['stopped']
@@ -0,0 +1,162 @@
1
+ # 3.12.23
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.Lib.Downloader import HLS_Downloader
15
+
16
+ # Logic class
17
+ from .util.ScrapeSerie import GetSerieInfo
18
+ from StreamingCommunity.Api.Template.Util import (
19
+ manage_selection,
20
+ map_episode_title,
21
+ validate_selection,
22
+ validate_episode_selection,
23
+ display_episodes_list
24
+ )
25
+ from StreamingCommunity.Api.Template.config_loader import site_constant
26
+ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
27
+
28
+
29
+ # Player
30
+ from StreamingCommunity.Api.Player.mediapolisvod import VideoSource
31
+
32
+
33
+ # Variable
34
+ msg = Prompt()
35
+ console = Console()
36
+
37
+
38
+ def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: GetSerieInfo) -> Tuple[str,bool]:
39
+ """
40
+ Downloads a specific episode from the specified season.
41
+
42
+ Parameters:
43
+ - index_season_selected (int): Season number
44
+ - index_episode_selected (int): Episode index
45
+ - scrape_serie (GetSerieInfo): Scraper object with series information
46
+
47
+ Returns:
48
+ - str: Path to downloaded file
49
+ - bool: Whether download was stopped
50
+ """
51
+ start_message()
52
+
53
+ # Get episode information
54
+ obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
55
+ console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.name}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
56
+
57
+ # Get streaming URL
58
+ master_playlist = VideoSource.extract_m3u8_url(obj_episode.url)
59
+
60
+ # Define filename and path
61
+ mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4"
62
+ mp4_path = os.path.join(site_constant.SERIES_FOLDER, scrape_serie.series_name, f"S{index_season_selected}")
63
+
64
+ # Download the episode
65
+ r_proc = HLS_Downloader(
66
+ m3u8_url=master_playlist,
67
+ output_path=os.path.join(mp4_path, mp4_name)
68
+ ).start()
69
+
70
+ if r_proc['error'] is not None:
71
+ try: os.remove(r_proc['path'])
72
+ except: pass
73
+
74
+ return r_proc['path'], r_proc['stopped']
75
+
76
+
77
+ def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, download_all: bool = False, episode_selection: str = None) -> None:
78
+ """
79
+ Handle downloading episodes for a specific season.
80
+
81
+ Parameters:
82
+ - index_season_selected (int): Season number
83
+ - scrape_serie (GetSerieInfo): Scraper object with series information
84
+ - download_all (bool): Whether to download all episodes
85
+ - episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
86
+ """
87
+ # Get episodes for the selected season
88
+ episodes = scrape_serie.getEpisodeSeasons(index_season_selected)
89
+ episodes_count = len(episodes)
90
+
91
+ if download_all:
92
+ for i_episode in range(1, episodes_count + 1):
93
+ path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
94
+ if stopped:
95
+ break
96
+ console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.")
97
+
98
+ else:
99
+ # Display episodes list and manage user selection
100
+ if episode_selection is None:
101
+ last_command = display_episodes_list(episodes)
102
+ else:
103
+ last_command = episode_selection
104
+ console.print(f"\n[cyan]Using provided episode selection: [yellow]{episode_selection}")
105
+
106
+ # Validate the selection
107
+ list_episode_select = manage_selection(last_command, episodes_count)
108
+ list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
109
+
110
+ # Download selected episodes if not stopped
111
+ for i_episode in list_episode_select:
112
+ path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
113
+ if stopped:
114
+ break
115
+
116
+ def download_series(select_season: MediaItem, season_selection: str = None, episode_selection: str = None) -> None:
117
+ """
118
+ Handle downloading a complete series.
119
+
120
+ Parameters:
121
+ - select_season (MediaItem): Series metadata from search
122
+ - season_selection (str, optional): Pre-defined season selection that bypasses manual input
123
+ - episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
124
+ """
125
+ start_message()
126
+
127
+ # Extract program name from path_id
128
+ program_name = None
129
+ if select_season.path_id:
130
+ parts = select_season.path_id.strip('/').split('/')
131
+ if len(parts) >= 2:
132
+ program_name = parts[-1].split('.')[0]
133
+
134
+ # Init scraper
135
+ scrape_serie = GetSerieInfo(program_name)
136
+
137
+ # Get seasons info
138
+ scrape_serie.collect_info_title()
139
+ seasons_count = len(scrape_serie.seasons_manager)
140
+ console.print(f"\n[green]Seasons found: [red]{seasons_count}")
141
+
142
+ # If season_selection is provided, use it instead of asking for input
143
+ if season_selection is None:
144
+ index_season_selected = msg.ask(
145
+ "\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
146
+ "[yellow](e.g., 1-2) [cyan]for a range of seasons, or [yellow](e.g., 3-*) [cyan]to download from a specific season to the end"
147
+ )
148
+
149
+ else:
150
+ index_season_selected = season_selection
151
+ console.print(f"\n[cyan]Using provided season selection: [yellow]{season_selection}")
152
+
153
+ # Validate the selection
154
+ list_season_select = manage_selection(index_season_selected, seasons_count)
155
+ list_season_select = validate_selection(list_season_select, seasons_count)
156
+
157
+ # Loop through the selected seasons and download episodes
158
+ for season_number in list_season_select:
159
+ if len(list_season_select) > 1 or index_season_selected == "*":
160
+ download_episode(season_number, scrape_serie, download_all=True)
161
+ else:
162
+ download_episode(season_number, scrape_serie, download_all=False, episode_selection=episode_selection)