StreamingCommunity 3.3.5__py3-none-any.whl → 3.3.6__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 (44) hide show
  1. StreamingCommunity/Api/Site/altadefinizione/__init__.py +17 -18
  2. StreamingCommunity/Api/Site/altadefinizione/series.py +4 -0
  3. StreamingCommunity/Api/Site/animeunity/__init__.py +14 -15
  4. StreamingCommunity/Api/Site/animeunity/serie.py +1 -1
  5. StreamingCommunity/Api/Site/animeworld/__init__.py +15 -13
  6. StreamingCommunity/Api/Site/animeworld/serie.py +1 -1
  7. StreamingCommunity/Api/Site/crunchyroll/__init__.py +16 -17
  8. StreamingCommunity/Api/Site/crunchyroll/series.py +6 -1
  9. StreamingCommunity/Api/Site/guardaserie/__init__.py +17 -19
  10. StreamingCommunity/Api/Site/guardaserie/series.py +4 -0
  11. StreamingCommunity/Api/Site/guardaserie/site.py +2 -7
  12. StreamingCommunity/Api/Site/mediasetinfinity/__init__.py +15 -15
  13. StreamingCommunity/Api/Site/mediasetinfinity/series.py +4 -0
  14. StreamingCommunity/Api/Site/mediasetinfinity/site.py +12 -2
  15. StreamingCommunity/Api/Site/mediasetinfinity/util/ScrapeSerie.py +67 -98
  16. StreamingCommunity/Api/Site/raiplay/__init__.py +15 -15
  17. StreamingCommunity/Api/Site/raiplay/series.py +5 -1
  18. StreamingCommunity/Api/Site/streamingcommunity/__init__.py +16 -14
  19. StreamingCommunity/Api/Site/streamingwatch/__init__.py +12 -12
  20. StreamingCommunity/Api/Site/streamingwatch/series.py +4 -0
  21. StreamingCommunity/Api/Template/Class/SearchType.py +0 -1
  22. StreamingCommunity/Api/Template/Util/manage_ep.py +1 -11
  23. StreamingCommunity/Api/Template/site.py +2 -3
  24. StreamingCommunity/Lib/Downloader/DASH/downloader.py +55 -17
  25. StreamingCommunity/Lib/Downloader/DASH/segments.py +73 -17
  26. StreamingCommunity/Lib/Downloader/HLS/downloader.py +282 -152
  27. StreamingCommunity/Lib/Downloader/HLS/segments.py +1 -5
  28. StreamingCommunity/Lib/FFmpeg/capture.py +1 -1
  29. StreamingCommunity/Lib/FFmpeg/command.py +6 -6
  30. StreamingCommunity/Lib/FFmpeg/util.py +11 -30
  31. StreamingCommunity/Lib/M3U8/estimator.py +27 -13
  32. StreamingCommunity/Upload/update.py +2 -2
  33. StreamingCommunity/Upload/version.py +1 -1
  34. StreamingCommunity/Util/installer/__init__.py +11 -0
  35. StreamingCommunity/Util/installer/device_install.py +1 -1
  36. StreamingCommunity/Util/os.py +2 -6
  37. StreamingCommunity/Util/table.py +40 -8
  38. StreamingCommunity/run.py +15 -8
  39. {streamingcommunity-3.3.5.dist-info → streamingcommunity-3.3.6.dist-info}/METADATA +38 -51
  40. {streamingcommunity-3.3.5.dist-info → streamingcommunity-3.3.6.dist-info}/RECORD +44 -43
  41. {streamingcommunity-3.3.5.dist-info → streamingcommunity-3.3.6.dist-info}/WHEEL +0 -0
  42. {streamingcommunity-3.3.5.dist-info → streamingcommunity-3.3.6.dist-info}/entry_points.txt +0 -0
  43. {streamingcommunity-3.3.5.dist-info → streamingcommunity-3.3.6.dist-info}/licenses/LICENSE +0 -0
  44. {streamingcommunity-3.3.5.dist-info → streamingcommunity-3.3.6.dist-info}/top_level.txt +0 -0
@@ -5,16 +5,15 @@ from urllib.parse import urlparse
5
5
 
6
6
 
7
7
  # External libraries
8
- from curl_cffi import requests
8
+ import httpx
9
9
  from bs4 import BeautifulSoup
10
10
 
11
11
 
12
12
  # Internal utilities
13
- from StreamingCommunity.Util.headers import get_headers, get_userAgent
14
13
  from StreamingCommunity.Util.config_json import config_manager
14
+ from StreamingCommunity.Util.headers import get_headers, get_userAgent
15
15
  from StreamingCommunity.Api.Player.Helper.Vixcloud.util import SeasonManager
16
16
 
17
-
18
17
  # Variable
19
18
  max_timeout = config_manager.get_int("REQUESTS", "timeout")
20
19
 
@@ -36,58 +35,26 @@ class GetSerieInfo:
36
35
  self.stagioni_disponibili = []
37
36
 
38
37
  def _extract_serie_id(self):
39
- """Estrae l'ID della serie dall'URL di partenza"""
38
+ """Extract the series ID from the starting URL"""
40
39
  self.serie_id = f"SE{self.url.split('SE')[1]}"
41
- print(f"Serie ID: {self.serie_id}")
42
40
  return self.serie_id
43
41
 
44
42
  def _get_public_id(self):
43
+ """Get the public ID for API calls"""
45
44
  self.public_id = "PR1GhC"
46
45
  return self.public_id
47
-
48
- """
49
- bearer_token = get_bearer_token()
50
- headers = {
51
- 'authorization': f'Bearer {bearer_token}',
52
- 'user-agent': get_userAgent(),
53
- }
54
-
55
- response = requests.get(
56
- 'https://api-ott-prod-fe.mediaset.net/PROD/play/userlist/watchlist/v2.0',
57
- headers=headers,
58
- impersonate="chrome",
59
- allow_redirects=True
60
- )
61
-
62
- if response.status_code == 401:
63
- print("Token scaduto, rinnovare il token")
64
-
65
- if response.status_code == 200:
66
- data = response.json()
67
- self.public_id = data['response']['entries'][0]['media'][0]['publicUrl'].split("/")[4]
68
- print(f"Public id: {self.public_id}")
69
- return self.public_id
70
-
71
- else:
72
- logging.error(f"Failed to get public ID: {response.status_code}")
73
- return None
74
- """
75
46
 
76
47
  def _get_series_data(self):
77
- """Ottiene i dati della serie tramite l'API"""
78
- headers = {
79
- 'User-Agent': get_userAgent(),
80
- }
48
+ """Get series data through the API"""
49
+ headers = {'User-Agent': get_userAgent()}
81
50
  params = {'byGuid': self.serie_id}
82
51
 
83
- response = requests.get(
84
- f'https://feed.entertainment.tv.theplatform.eu/f/{self.public_id}/mediaset-prod-all-series-v2',
85
- params=params,
86
- headers=headers,
87
- impersonate="chrome",
88
- allow_redirects=True
89
- )
90
- print("Risposta per _get_series_data:", response.status_code)
52
+ with httpx.Client(timeout=max_timeout, follow_redirects=True) as client:
53
+ response = client.get(
54
+ f'https://feed.entertainment.tv.theplatform.eu/f/{self.public_id}/mediaset-prod-all-series-v2',
55
+ params=params,
56
+ headers=headers
57
+ )
91
58
 
92
59
  if response.status_code == 200:
93
60
  return response.json()
@@ -96,7 +63,7 @@ class GetSerieInfo:
96
63
  return None
97
64
 
98
65
  def _process_available_seasons(self, data):
99
- """Processa le stagioni disponibili dai dati della serie"""
66
+ """Process available seasons from series data"""
100
67
  if not data or not data.get('entries'):
101
68
  logging.error("No series data found")
102
69
  return []
@@ -118,16 +85,17 @@ class GetSerieInfo:
118
85
  'id': str(url).split("/")[-1],
119
86
  'guid': season['guid']
120
87
  })
88
+
121
89
  else:
122
90
  logging.warning(f"Season URL not found: {url}")
123
91
 
124
- # Ordina le stagioni dalla più vecchia alla più nuova
92
+ # Sort seasons from oldest to newest
125
93
  stagioni_disponibili.sort(key=lambda s: s['tvSeasonNumber'])
126
94
 
127
95
  return stagioni_disponibili
128
96
 
129
97
  def _build_season_page_urls(self, stagioni_disponibili):
130
- """Costruisce gli URL delle pagine delle stagioni"""
98
+ """Build season page URLs"""
131
99
  parsed_url = urlparse(self.url)
132
100
  base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
133
101
  series_slug = parsed_url.path.strip('/').split('/')[-1].split('_')[0]
@@ -137,39 +105,36 @@ class GetSerieInfo:
137
105
  season['page_url'] = page_url
138
106
 
139
107
  def _extract_season_sb_ids(self, stagioni_disponibili):
140
- """Estrae gli ID sb dalle pagine delle stagioni"""
141
- for season in stagioni_disponibili:
142
- response_page = requests.get(
143
- season['page_url'],
144
- headers={'User-Agent': get_userAgent()},
145
- impersonate="chrome",
146
- allow_redirects=True
147
- )
148
- print("Risposta per _extract_season_sb_ids:", response_page.status_code)
149
-
150
- soup = BeautifulSoup(response_page.text, 'html.parser')
151
-
152
- # Prova prima con 'Episodi', poi con 'Puntate intere'
153
- link = soup.find('a', string='Episodi')
154
- if not link:
155
- print("Using word: Puntate intere")
156
- link = soup.find('a', string='Puntate intere')
157
-
158
- if link and link.has_attr('href'):
159
- if not link.string == 'Puntate intere':
160
- print("Using word: Episodi")
161
- season['sb'] = link['href'].split(',')[-1]
162
- else:
163
- logging.warning(f"Link 'Episodi' o 'Puntate intere' non trovato per stagione {season['tvSeasonNumber']}")
108
+ """Extract sb IDs from season pages"""
109
+ with httpx.Client(timeout=max_timeout, follow_redirects=True) as client:
110
+ for season in stagioni_disponibili:
111
+ response_page = client.get(
112
+ season['page_url'],
113
+ headers={'User-Agent': get_userAgent()}
114
+ )
115
+ print("Response for _extract_season_sb_ids:", response_page.status_code, " season index:", season['tvSeasonNumber'])
116
+
117
+ soup = BeautifulSoup(response_page.text, 'html.parser')
118
+
119
+ # Try first with 'Episodi', then with 'Puntate intere'
120
+ link = soup.find('a', string='Episodi')
121
+ if not link:
122
+ #print("Using word: Puntate intere")
123
+ link = soup.find('a', string='Puntate intere')
124
+
125
+ if link and link.has_attr('href'):
126
+ if not link.string == 'Puntate intere':
127
+ print("Using word: Episodi")
128
+ season['sb'] = link['href'].split(',')[-1]
129
+ else:
130
+ logging.warning(f"Link 'Episodi' or 'Puntate intere' not found for season {season['tvSeasonNumber']}")
164
131
 
165
132
  def _get_season_episodes(self, season):
166
- """Ottiene gli episodi per una stagione specifica"""
133
+ """Get episodes for a specific season"""
167
134
  if not season.get('sb'):
168
135
  return
169
136
 
170
137
  episode_headers = {
171
- 'origin': 'https://mediasetinfinity.mediaset.it',
172
- 'referer': 'https://mediasetinfinity.mediaset.it/',
173
138
  'user-agent': get_userAgent(),
174
139
  }
175
140
  params = {
@@ -179,9 +144,8 @@ class GetSerieInfo:
179
144
  }
180
145
  episode_url = f"https://feed.entertainment.tv.theplatform.eu/f/{self.public_id}/mediaset-prod-all-programs-v2"
181
146
 
182
- episode_response = requests.get(episode_url, headers=episode_headers, params=params, impersonate="chrome"
183
- , allow_redirects=True)
184
- print("Risposta per _get_season_episodes:", episode_response.status_code)
147
+ with httpx.Client(timeout=max_timeout, follow_redirects=True) as client:
148
+ episode_response = client.get(episode_url, headers=episode_headers, params=params)
185
149
 
186
150
  if episode_response.status_code == 200:
187
151
  episode_data = episode_response.json()
@@ -192,7 +156,8 @@ class GetSerieInfo:
192
156
  'id': entry.get('guid'),
193
157
  'title': entry.get('title'),
194
158
  'duration': int(entry.get('mediasetprogram$duration', 0) / 60) if entry.get('mediasetprogram$duration') else 0,
195
- 'url': entry.get('media', [{}])[0].get('publicUrl') if entry.get('media') else None
159
+ 'url': entry.get('media', [{}])[0].get('publicUrl') if entry.get('media') else None,
160
+ 'name': entry.get('title')
196
161
  }
197
162
  season['episodes'].append(episode_info)
198
163
 
@@ -242,22 +207,22 @@ class GetSerieInfo:
242
207
  logging.error(f"Error in collect_season: {str(e)}")
243
208
 
244
209
  def _populate_seasons_manager(self):
245
- """Popola il seasons_manager con i dati raccolti"""
210
+ """Populate the seasons_manager with collected data - ONLY for seasons with episodes"""
211
+ seasons_with_episodes = 0
212
+
246
213
  for season_data in self.stagioni_disponibili:
247
- season_obj = self.seasons_manager.add_season({
248
- 'number': season_data['tvSeasonNumber'],
249
- 'name': f"Stagione {season_data['tvSeasonNumber']}"
250
- })
251
214
 
252
- if season_obj and season_data.get('episodes'):
253
- for idx, episode in enumerate(season_data['episodes'], 1):
254
- season_obj.episodes.add({
255
- 'id': episode['id'],
256
- 'number': idx,
257
- 'name': episode['title'],
258
- 'url': episode['url'],
259
- 'duration': episode['duration']
260
- })
215
+ # Add season to manager ONLY if it has episodes
216
+ if season_data.get('episodes') and len(season_data['episodes']) > 0:
217
+ season_obj = self.seasons_manager.add_season({
218
+ 'number': season_data['tvSeasonNumber'],
219
+ 'name': f"Season {season_data['tvSeasonNumber']}"
220
+ })
221
+
222
+ if season_obj:
223
+ for episode in season_data['episodes']:
224
+ season_obj.episodes.add(episode)
225
+ seasons_with_episodes += 1
261
226
 
262
227
  # ------------- FOR GUI -------------
263
228
  def getNumberSeason(self) -> int:
@@ -275,10 +240,14 @@ class GetSerieInfo:
275
240
  """
276
241
  if not self.seasons_manager.seasons:
277
242
  self.collect_season()
278
-
279
- # Get season directly by its number
280
- season = self.seasons_manager.get_season_by_number(season_number)
281
- return season.episodes.episodes if season else []
243
+
244
+ # Convert 1-based user input to 0-based array index
245
+ season_index = season_number - 1
246
+
247
+ # Get season by index in the available seasons list
248
+ season = self.seasons_manager.seasons[season_index]
249
+
250
+ return season.episodes.episodes
282
251
 
283
252
  def selectEpisode(self, season_number: int, episode_index: int) -> dict:
284
253
  """
@@ -289,4 +258,4 @@ class GetSerieInfo:
289
258
  logging.error(f"Episode index {episode_index} is out of range for season {season_number}")
290
259
  return None
291
260
 
292
- return episodes[episode_index]
261
+ return episodes[episode_index]
@@ -100,10 +100,13 @@ def process_search_result(select_title, selections=None):
100
100
  episode_selection = selections.get('episode')
101
101
 
102
102
  download_series(select_title, season_selection, episode_selection)
103
+ media_search_manager.clear()
104
+ table_show_manager.clear()
103
105
  return True
104
106
 
105
107
  else:
106
108
  download_film(select_title)
109
+ table_show_manager.clear()
107
110
  return True
108
111
 
109
112
  def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
@@ -120,40 +123,37 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
120
123
  bot = None
121
124
  if site_constant.TELEGRAM_BOT:
122
125
  bot = get_bot_instance()
123
-
126
+
124
127
  if direct_item:
125
128
  select_title = MediaItem(**direct_item)
126
- process_search_result(select_title, selections)
127
- return True
129
+ result = process_search_result(select_title, selections)
130
+ return result
128
131
 
129
132
  # Get the user input for the search term
130
133
  actual_search_query = get_user_input(string_to_search)
131
134
 
132
- # Handle cases where user input is empty, or 'back' was handled (sys.exit or None return)
135
+ # Handle empty input
133
136
  if not actual_search_query:
134
137
  if bot:
135
- if actual_search_query is None: # Specifically for timeout from bot.ask or failed restart
138
+ if actual_search_query is None:
136
139
  bot.send_message("Search term not provided or operation cancelled. Returning.", None)
137
- return
138
-
139
- # Perform the database search
140
+ return False
141
+
142
+ # Search on database
140
143
  len_database = title_search(actual_search_query)
141
144
 
142
145
  # If only the database is needed, return the manager
143
146
  if get_onlyDatabase:
144
147
  return media_search_manager
145
-
148
+
146
149
  if len_database > 0:
147
150
  select_title = get_select_title(table_show_manager, media_search_manager, len_database)
148
- process_search_result(select_title, selections)
149
- return True
151
+ result = process_search_result(select_title, selections)
152
+ return result
150
153
 
151
154
  else:
152
155
  if bot:
153
156
  bot.send_message(f"No results found for: '{actual_search_query}'", None)
154
157
  else:
155
158
  console.print(f"\n[red]Nothing matching was found for[white]: [purple]{actual_search_query}")
156
-
157
- # Do not call search() recursively here to avoid infinite loops on no results.
158
- # The flow should return to the caller (e.g., main menu in run.py).
159
- return
159
+ return False
@@ -58,7 +58,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
58
58
 
59
59
  # Get episode information
60
60
  obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
61
- 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")
61
+ console.print(f"[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")
62
62
 
63
63
  # Define filename and path
64
64
  mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4"
@@ -135,6 +135,10 @@ def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, dow
135
135
  episodes = scrape_serie.getEpisodeSeasons(index_season_selected)
136
136
  episodes_count = len(episodes)
137
137
 
138
+ if episodes_count == 0:
139
+ console.print(f"[red]No episodes found for season {index_season_selected}")
140
+ return
141
+
138
142
  if download_all:
139
143
  for i_episode in range(1, episodes_count + 1):
140
144
  path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
@@ -98,12 +98,16 @@ def process_search_result(select_title, selections=None):
98
98
  episode_selection = selections.get('episode')
99
99
 
100
100
  download_series(select_title, season_selection, episode_selection)
101
+ media_search_manager.clear()
102
+ table_show_manager.clear()
101
103
  return True
102
104
 
103
105
  else:
104
106
  download_film(select_title)
107
+ table_show_manager.clear()
105
108
  return True
106
109
 
110
+
107
111
  def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
108
112
  """
109
113
  Main function of the application for search.
@@ -121,37 +125,35 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
121
125
  bot = get_bot_instance()
122
126
 
123
127
  if direct_item:
124
- select_title_obj = MediaItem(**direct_item)
125
- process_search_result(select_title_obj, selections)
126
- return True
128
+ select_title = MediaItem(**direct_item)
129
+ result = process_search_result(select_title, selections)
130
+ return result
127
131
 
132
+ # Get the user input for the search term
128
133
  actual_search_query = get_user_input(string_to_search)
129
134
 
130
- # Handle cases where user input is empty, or 'back' was handled (sys.exit or None return)
135
+ # Handle empty input
131
136
  if not actual_search_query:
132
137
  if bot:
133
- if actual_search_query is None: # Specifically for timeout from bot.ask or failed restart
138
+ if actual_search_query is None:
134
139
  bot.send_message("Search term not provided or operation cancelled. Returning.", None)
135
- return
140
+ return False
136
141
 
137
- # Perform search on the database using the obtained query
142
+ # Search on database
138
143
  len_database = title_search(actual_search_query)
139
144
 
140
- # If only the database object (media_search_manager populated by title_search) is needed
145
+ # If only the database is needed, return the manager
141
146
  if get_onlyDatabase:
142
147
  return media_search_manager
143
148
 
144
149
  if len_database > 0:
145
150
  select_title = get_select_title(table_show_manager, media_search_manager, len_database)
146
- process_search_result(select_title, selections)
147
- return True
151
+ result = process_search_result(select_title, selections)
152
+ return result
148
153
 
149
154
  else:
150
155
  if bot:
151
156
  bot.send_message(f"No results found for: '{actual_search_query}'", None)
152
157
  else:
153
158
  console.print(f"\n[red]Nothing matching was found for[white]: [purple]{actual_search_query}")
154
-
155
- # Do not call search() recursively here to avoid infinite loops on no results.
156
- # The flow should return to the caller (e.g., main menu in run.py).
157
- return
159
+ return False
@@ -99,10 +99,13 @@ def process_search_result(select_title, selections=None):
99
99
  episode_selection = selections.get('episode')
100
100
 
101
101
  download_series(select_title, season_selection, episode_selection)
102
+ media_search_manager.clear()
103
+ table_show_manager.clear()
102
104
  return True
103
105
 
104
106
  else:
105
107
  download_film(select_title)
108
+ table_show_manager.clear()
106
109
  return True
107
110
 
108
111
  def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
@@ -122,20 +125,20 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
122
125
 
123
126
  if direct_item:
124
127
  select_title = MediaItem(**direct_item)
125
- process_search_result(select_title, selections)
126
- return True
128
+ result = process_search_result(select_title, selections)
129
+ return result
127
130
 
128
131
  # Get the user input for the search term
129
132
  actual_search_query = get_user_input(string_to_search)
130
133
 
131
- # Handle cases where user input is empty, or 'back' was handled (sys.exit or None return)
134
+ # Handle empty input
132
135
  if not actual_search_query:
133
136
  if bot:
134
- if actual_search_query is None: # Specifically for timeout from bot.ask or failed restart
137
+ if actual_search_query is None:
135
138
  bot.send_message("Search term not provided or operation cancelled. Returning.", None)
136
- return
139
+ return False
137
140
 
138
- # Perform the database search
141
+ # Search on database
139
142
  len_database = title_search(actual_search_query)
140
143
 
141
144
  # If only the database is needed, return the manager
@@ -144,15 +147,12 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
144
147
 
145
148
  if len_database > 0:
146
149
  select_title = get_select_title(table_show_manager, media_search_manager, len_database)
147
- process_search_result(select_title, selections)
148
- return True
150
+ result = process_search_result(select_title, selections)
151
+ return result
149
152
 
150
153
  else:
151
154
  if bot:
152
155
  bot.send_message(f"No results found for: '{actual_search_query}'", None)
153
156
  else:
154
157
  console.print(f"\n[red]Nothing matching was found for[white]: [purple]{actual_search_query}")
155
-
156
- # Do not call search() recursively here to avoid infinite loops on no results.
157
- # The flow should return to the caller (e.g., main menu in run.py).
158
- return
158
+ return False
@@ -92,6 +92,10 @@ def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, dow
92
92
  episodes = scrape_serie.getEpisodeSeasons(index_season_selected)
93
93
  episodes_count = len(episodes)
94
94
 
95
+ if episodes_count == 0:
96
+ console.print(f"[red]No episodes found for season {index_season_selected}")
97
+ return
98
+
95
99
  if download_all:
96
100
  for i_episode in range(1, episodes_count + 1):
97
101
  path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
@@ -19,7 +19,6 @@ class MediaItemData(TypedDict, total=False):
19
19
  slug: str # SC
20
20
 
21
21
 
22
-
23
22
  class MediaItemMeta(type):
24
23
  def __new__(cls, name, bases, dct):
25
24
  def init(self, **kwargs):
@@ -26,14 +26,6 @@ def dynamic_format_number(number_str: str) -> str:
26
26
  """
27
27
  Formats an episode number string, intelligently handling both integer and decimal episode numbers.
28
28
 
29
- This function is designed to handle various episode number formats commonly found in media series:
30
- 1. For integer episode numbers less than 10 (e.g., 1, 2, ..., 9), it adds a leading zero (e.g., 01, 02, ..., 09)
31
- 2. For integer episode numbers 10 and above, it preserves the original format without adding leading zeros
32
- 3. For decimal episode numbers (e.g., "7.5", "10.5"), it preserves the decimal format exactly as provided
33
-
34
- The function is particularly useful for media file naming conventions where special episodes
35
- or OVAs may have decimal notations (like episode 7.5) which should be preserved in their original format.
36
-
37
29
  Parameters:
38
30
  - number_str (str): The episode number as a string, which may contain integers or decimals.
39
31
 
@@ -104,7 +96,7 @@ def manage_selection(cmd_insert: str, max_count: int) -> List[int]:
104
96
  list_selection = list(range(1, max_count + 1))
105
97
  break
106
98
 
107
- cmd_insert = msg.ask("[red]Invalid input. Please enter a valid command: ")
99
+ cmd_insert = msg.ask("[red]Invalid input. Please enter a valid command")
108
100
 
109
101
  logging.info(f"List return: {list_selection}")
110
102
  return list_selection
@@ -145,7 +137,6 @@ def map_episode_title(tv_name: str, number_season: int, episode_number: int, epi
145
137
  return map_episode_temp
146
138
 
147
139
 
148
- # --> for season
149
140
  def validate_selection(list_season_select: List[int], seasons_count: int) -> List[int]:
150
141
  """
151
142
  Validates and adjusts the selected seasons based on the available seasons.
@@ -182,7 +173,6 @@ def validate_selection(list_season_select: List[int], seasons_count: int) -> Lis
182
173
  list_season_select = list(map(int, input_seasons.split(',')))
183
174
 
184
175
 
185
- # --> for episode
186
176
  def validate_episode_selection(list_episode_select: List[int], episodes_count: int) -> List[int]:
187
177
  """
188
178
  Validates and adjusts the selected episodes based on the available episodes.
@@ -8,6 +8,7 @@ from rich.console import Console
8
8
  from StreamingCommunity.Api.Template.config_loader import site_constant
9
9
  from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
10
10
 
11
+
11
12
  # Variable
12
13
  console = Console()
13
14
  available_colors = ['red', 'magenta', 'yellow', 'cyan', 'green', 'blue', 'white']
@@ -124,10 +125,8 @@ def get_select_title(table_show_manager, media_search_manager, num_results_avail
124
125
 
125
126
  else:
126
127
  console.print("\n[red]Indice errato o non valido.")
127
- # sys.exit(0)
128
128
  return None
129
129
 
130
130
  except ValueError:
131
131
  console.print("\n[red]Input non numerico ricevuto dalla tabella.")
132
- # sys.exit(0)
133
- return None
132
+ return None
@@ -7,6 +7,7 @@ import shutil
7
7
  # External libraries
8
8
  from rich.console import Console
9
9
  from rich.panel import Panel
10
+ from rich.table import Table
10
11
 
11
12
 
12
13
  # Internal utilities
@@ -64,23 +65,59 @@ class DASH_Downloader:
64
65
  self.parser = MPDParser(self.mpd_url)
65
66
  self.parser.parse(custom_headers)
66
67
 
67
- # Video info
68
- selected_video, list_available_resolution, filter_custom_resolution, downloadable_video = self.parser.select_video(FILTER_CUSTOM_REOLUTION)
69
- console.print(
70
- f"[cyan bold]Video [/cyan bold] [green]Available:[/green] [purple]{', '.join(list_available_resolution)}[/purple] | "
71
- f"[red]Set:[/red] [purple]{filter_custom_resolution}[/purple] | "
72
- f"[yellow]Downloadable:[/yellow] [purple]{downloadable_video}[/purple]"
73
- )
74
- self.selected_video = selected_video
75
-
76
- # Audio info
77
- selected_audio, list_available_audio_langs, filter_custom_audio, downloadable_audio = self.parser.select_audio(DOWNLOAD_SPECIFIC_AUDIO)
78
- console.print(
79
- f"[cyan bold]Audio [/cyan bold] [green]Available:[/green] [purple]{', '.join(list_available_audio_langs)}[/purple] | "
80
- f"[red]Set:[/red] [purple]{filter_custom_audio}[/purple] | "
81
- f"[yellow]Downloadable:[/yellow] [purple]{downloadable_audio}[/purple]"
82
- )
83
- self.selected_audio = selected_audio
68
+ def calculate_column_widths():
69
+ """Calculate optimal column widths based on content."""
70
+ data_rows = []
71
+
72
+ # Video info
73
+ selected_video, list_available_resolution, filter_custom_resolution, downloadable_video = self.parser.select_video(FILTER_CUSTOM_REOLUTION)
74
+ self.selected_video = selected_video
75
+
76
+ available_video = ', '.join(list_available_resolution) if list_available_resolution else "Nothing"
77
+ set_video = str(filter_custom_resolution) if filter_custom_resolution else "Nothing"
78
+ downloadable_video_str = str(downloadable_video) if downloadable_video else "Nothing"
79
+
80
+ data_rows.append(["Video", available_video, set_video, downloadable_video_str])
81
+
82
+ # Audio info
83
+ selected_audio, list_available_audio_langs, filter_custom_audio, downloadable_audio = self.parser.select_audio(DOWNLOAD_SPECIFIC_AUDIO)
84
+ self.selected_audio = selected_audio
85
+
86
+ available_audio = ', '.join(list_available_audio_langs) if list_available_audio_langs else "Nothing"
87
+ set_audio = str(filter_custom_audio) if filter_custom_audio else "Nothing"
88
+ downloadable_audio_str = str(downloadable_audio) if downloadable_audio else "Nothing"
89
+
90
+ data_rows.append(["Audio", available_audio, set_audio, downloadable_audio_str])
91
+
92
+ # Calculate max width for each column
93
+ headers = ["Type", "Available", "Set", "Downloadable"]
94
+ max_widths = [len(header) for header in headers]
95
+
96
+ for row in data_rows:
97
+ for i, cell in enumerate(row):
98
+ max_widths[i] = max(max_widths[i], len(str(cell)))
99
+
100
+ # Add some padding
101
+ max_widths = [w + 2 for w in max_widths]
102
+
103
+ return data_rows, max_widths
104
+
105
+ data_rows, column_widths = calculate_column_widths()
106
+
107
+ # Create table with dynamic widths
108
+ table = Table(show_header=True, header_style="bold cyan", border_style="blue")
109
+ table.add_column("Type", style="cyan bold", width=column_widths[0])
110
+ table.add_column("Available", style="green", width=column_widths[1])
111
+ table.add_column("Set", style="red", width=column_widths[2])
112
+ table.add_column("Downloadable", style="yellow", width=column_widths[3])
113
+
114
+ # Add all rows to the table
115
+ for row in data_rows:
116
+ table.add_row(*row)
117
+
118
+ console.print("[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan]")
119
+ console.print(table)
120
+ console.print("")
84
121
 
85
122
  def get_representation_by_type(self, typ):
86
123
  if typ == "video":
@@ -212,6 +249,7 @@ class DASH_Downloader:
212
249
  f"[cyan]Output: [bold]{os.path.abspath(output_file)}[/bold]"
213
250
  )
214
251
 
252
+ print("")
215
253
  console.print(Panel(
216
254
  panel_content,
217
255
  title=f"{os.path.basename(output_file.replace('.mp4', ''))}",