StreamingCommunity 3.2.1__py3-none-any.whl → 3.2.7__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 (67) hide show
  1. StreamingCommunity/Api/Player/Helper/Vixcloud/util.py +4 -0
  2. StreamingCommunity/Api/Player/hdplayer.py +2 -2
  3. StreamingCommunity/Api/Player/mixdrop.py +1 -1
  4. StreamingCommunity/Api/Player/vixcloud.py +4 -5
  5. StreamingCommunity/Api/Site/altadefinizione/film.py +2 -2
  6. StreamingCommunity/Api/Site/altadefinizione/series.py +1 -1
  7. StreamingCommunity/Api/Site/animeunity/serie.py +1 -1
  8. StreamingCommunity/Api/Site/animeworld/film.py +1 -1
  9. StreamingCommunity/Api/Site/animeworld/serie.py +1 -2
  10. StreamingCommunity/Api/Site/cb01new/film.py +1 -1
  11. StreamingCommunity/Api/Site/crunchyroll/__init__.py +103 -0
  12. StreamingCommunity/Api/Site/crunchyroll/film.py +82 -0
  13. StreamingCommunity/Api/Site/crunchyroll/series.py +186 -0
  14. StreamingCommunity/Api/Site/crunchyroll/site.py +113 -0
  15. StreamingCommunity/Api/Site/crunchyroll/util/ScrapeSerie.py +238 -0
  16. StreamingCommunity/Api/Site/crunchyroll/util/get_license.py +227 -0
  17. StreamingCommunity/Api/Site/guardaserie/series.py +1 -2
  18. StreamingCommunity/Api/Site/guardaserie/site.py +1 -2
  19. StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +9 -8
  20. StreamingCommunity/Api/Site/mediasetinfinity/__init__.py +96 -0
  21. StreamingCommunity/Api/Site/mediasetinfinity/film.py +85 -0
  22. StreamingCommunity/Api/Site/mediasetinfinity/series.py +185 -0
  23. StreamingCommunity/Api/Site/mediasetinfinity/site.py +112 -0
  24. StreamingCommunity/Api/Site/mediasetinfinity/util/ScrapeSerie.py +259 -0
  25. StreamingCommunity/Api/Site/mediasetinfinity/util/fix_mpd.py +64 -0
  26. StreamingCommunity/Api/Site/mediasetinfinity/util/get_license.py +214 -0
  27. StreamingCommunity/Api/Site/raiplay/film.py +2 -2
  28. StreamingCommunity/Api/Site/raiplay/series.py +2 -1
  29. StreamingCommunity/Api/Site/streamingcommunity/__init__.py +6 -17
  30. StreamingCommunity/Api/Site/streamingcommunity/film.py +3 -3
  31. StreamingCommunity/Api/Site/streamingcommunity/series.py +11 -11
  32. StreamingCommunity/Api/Site/streamingcommunity/site.py +2 -4
  33. StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +3 -6
  34. StreamingCommunity/Api/Site/streamingwatch/__init__.py +6 -14
  35. StreamingCommunity/Api/Site/streamingwatch/film.py +3 -3
  36. StreamingCommunity/Api/Site/streamingwatch/series.py +9 -9
  37. StreamingCommunity/Api/Site/streamingwatch/site.py +5 -7
  38. StreamingCommunity/Api/Site/streamingwatch/util/ScrapeSerie.py +2 -2
  39. StreamingCommunity/Lib/Downloader/DASH/cdm_helpher.py +131 -0
  40. StreamingCommunity/Lib/Downloader/DASH/decrypt.py +79 -0
  41. StreamingCommunity/Lib/Downloader/DASH/downloader.py +218 -0
  42. StreamingCommunity/Lib/Downloader/DASH/parser.py +249 -0
  43. StreamingCommunity/Lib/Downloader/DASH/segments.py +332 -0
  44. StreamingCommunity/Lib/Downloader/HLS/downloader.py +10 -30
  45. StreamingCommunity/Lib/Downloader/HLS/segments.py +146 -263
  46. StreamingCommunity/Lib/Downloader/MP4/downloader.py +0 -5
  47. StreamingCommunity/Lib/FFmpeg/capture.py +3 -3
  48. StreamingCommunity/Lib/FFmpeg/command.py +1 -1
  49. StreamingCommunity/TelegramHelp/config.json +3 -7
  50. StreamingCommunity/Upload/version.py +1 -1
  51. StreamingCommunity/Util/bento4_installer.py +191 -0
  52. StreamingCommunity/Util/config_json.py +1 -1
  53. StreamingCommunity/Util/headers.py +0 -3
  54. StreamingCommunity/Util/os.py +36 -46
  55. StreamingCommunity/__init__.py +2 -1
  56. StreamingCommunity/run.py +11 -10
  57. {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/METADATA +7 -9
  58. streamingcommunity-3.2.7.dist-info/RECORD +111 -0
  59. StreamingCommunity/Api/Site/1337xx/__init__.py +0 -72
  60. StreamingCommunity/Api/Site/1337xx/site.py +0 -82
  61. StreamingCommunity/Api/Site/1337xx/title.py +0 -61
  62. StreamingCommunity/Lib/Proxies/proxy.py +0 -72
  63. streamingcommunity-3.2.1.dist-info/RECORD +0 -96
  64. {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/WHEEL +0 -0
  65. {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/entry_points.txt +0 -0
  66. {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/licenses/LICENSE +0 -0
  67. {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,238 @@
1
+ # 16.03.25
2
+
3
+ import logging
4
+
5
+
6
+ # External libraries
7
+ from curl_cffi import requests
8
+
9
+
10
+ # Internal utilities
11
+ from StreamingCommunity.Util.headers import get_headers
12
+ from StreamingCommunity.Util.config_json import config_manager
13
+ from StreamingCommunity.Api.Player.Helper.Vixcloud.util import SeasonManager
14
+ from .get_license import get_auth_token, generate_device_id
15
+
16
+
17
+ # Variable
18
+ max_timeout = config_manager.get_int("REQUESTS", "timeout")
19
+
20
+
21
+ def get_series_seasons(series_id, headers, params):
22
+ """
23
+ Fetches the seasons for a given series ID from Crunchyroll.
24
+ """
25
+ url = f'https://www.crunchyroll.com/content/v2/cms/series/{series_id}/seasons'
26
+ response = requests.get(
27
+ url,
28
+ params=params,
29
+ headers=headers,
30
+ impersonate="chrome110"
31
+ )
32
+ return response
33
+
34
+
35
+ def get_season_episodes(season_id, headers, params):
36
+ """
37
+ Fetches the episodes for a given season ID from Crunchyroll.
38
+ """
39
+ url = f'https://www.crunchyroll.com/content/v2/cms/seasons/{season_id}/episodes'
40
+ response = requests.get(
41
+ url,
42
+ params=params,
43
+ headers=headers,
44
+ impersonate="chrome110"
45
+ )
46
+ return response
47
+
48
+ def delete_stream_episode(episode_id, stream_id, headers):
49
+ """
50
+ Deletes a specific stream episode by episode ID and stream ID.
51
+ """
52
+ url = f'https://www.crunchyroll.com/playback/v1/token/{episode_id}/{stream_id}'
53
+ headers = get_headers()
54
+
55
+ response = requests.delete(
56
+ url,
57
+ headers=headers,
58
+ impersonate="chrome110"
59
+ )
60
+
61
+ if response.status_code == 204:
62
+ return True
63
+
64
+ else:
65
+ logging.error(f"Failed to delete stream episode: {response.status_code} - {response.text}")
66
+ return False
67
+
68
+
69
+ class GetSerieInfo:
70
+ def __init__(self, series_id):
71
+ """
72
+ Initialize the GetSerieInfo class for scraping TV series information using Crunchyroll API.
73
+
74
+ Args:
75
+ - series_id (str): The Crunchyroll series ID.
76
+ """
77
+ self.series_id = series_id
78
+ self.seasons_manager = SeasonManager()
79
+ self.headers = get_headers()
80
+ self.headers['authorization'] = f"Bearer {get_auth_token(generate_device_id()).access_token}"
81
+ self.params = {
82
+ 'force_locale': '',
83
+ 'preferred_audio_language': 'it-IT',
84
+ 'locale': 'it-IT',
85
+ }
86
+ self.series_name = None
87
+ self._episodes_cache = {}
88
+
89
+ def collect_season(self) -> None:
90
+ """
91
+ Retrieve all seasons using Crunchyroll API, but NOT episodes.
92
+ """
93
+ response = get_series_seasons(self.series_id, self.headers, self.params)
94
+
95
+ if response.status_code != 200:
96
+ logging.error(f"Failed to fetch seasons for series {self.series_id}")
97
+ return
98
+
99
+ # Get the JSON response
100
+ data = response.json()
101
+ seasons = data.get("data", [])
102
+
103
+ # Set series name from first season if available
104
+ if seasons:
105
+ self.series_name = seasons[0].get("series_title") or seasons[0].get("title")
106
+
107
+ for season in seasons:
108
+ season_num = season.get("season_number", 0)
109
+ season_name = season.get("title", f"Season {season_num}")
110
+
111
+ self.seasons_manager.add_season({
112
+ 'number': season_num,
113
+ 'name': season_name,
114
+ 'id': season.get('id')
115
+ })
116
+
117
+ def _fetch_episodes_for_season(self, season_number: int):
118
+ """
119
+ Fetch and cache episodes for a specific season number.
120
+ """
121
+ season = self.seasons_manager.get_season_by_number(season_number)
122
+
123
+ if not season or getattr(season, 'id', None) is None:
124
+ logging.error(f"Season {season_number} not found or missing id.")
125
+ return []
126
+
127
+ season_id = season.id
128
+ ep_response = get_season_episodes(season_id, self.headers, self.params)
129
+ if ep_response.status_code != 200:
130
+ logging.error(f"Failed to fetch episodes for season {season_id}")
131
+ return []
132
+
133
+ ep_data = ep_response.json()
134
+ episodes = ep_data.get("data", [])
135
+ episode_list = []
136
+
137
+ for ep in episodes:
138
+ ep_num = ep.get("episode_number")
139
+ ep_title = ep.get("title", f"Episodio {ep_num}")
140
+ ep_id = ep.get("id")
141
+ ep_url = f"https://www.crunchyroll.com/watch/{ep_id}"
142
+
143
+ episode_list.append({
144
+ 'number': ep_num,
145
+ 'name': ep_title,
146
+ 'url': ep_url,
147
+ 'duration': int(ep.get('duration_ms', 0) / 60000),
148
+ })
149
+
150
+ self._episodes_cache[season_number] = episode_list
151
+ return episode_list
152
+
153
+ def _get_episode_audio_locales_and_urls(self, episode_id):
154
+ """
155
+ Fetch available audio locales and their URLs for a given episode ID.
156
+ Returns: (audio_locales, urls_by_locale)
157
+ """
158
+ url = f'https://www.crunchyroll.com/content/v2/cms/objects/{episode_id}'
159
+ headers = self.headers.copy()
160
+ params = {
161
+ 'ratings': 'true',
162
+ 'locale': 'it-IT',
163
+ }
164
+ response = requests.get(
165
+ url,
166
+ params=params,
167
+ headers=headers,
168
+ impersonate="chrome110"
169
+ )
170
+
171
+ if response.status_code != 200:
172
+ logging.warning(f"Failed to fetch audio locales for episode {episode_id}")
173
+ return [], {}
174
+
175
+ data = response.json()
176
+ try:
177
+ versions = data["data"][0]['episode_metadata'].get("versions", [])
178
+
179
+ audio_locales = []
180
+ urls_by_locale = {}
181
+
182
+ for v in versions:
183
+ locale = v.get("audio_locale")
184
+ guid = v.get("guid")
185
+
186
+ if locale and guid:
187
+ audio_locales.append(locale)
188
+ urls_by_locale[locale] = f"https://www.crunchyroll.com/it/watch/{guid}"
189
+ #print(f"Locale: {locale}, URL: {urls_by_locale[locale]}")
190
+
191
+ return audio_locales, urls_by_locale
192
+
193
+ except Exception as e:
194
+ logging.error(f"Error parsing audio locales for episode {episode_id}: {e}")
195
+ return [], {}
196
+
197
+ # ------------- FOR GUI -------------
198
+ def getNumberSeason(self) -> int:
199
+ """
200
+ Get the total number of seasons available for the series.
201
+ """
202
+ if not self.seasons_manager.seasons:
203
+ self.collect_season()
204
+
205
+ return len(self.seasons_manager.seasons)
206
+
207
+ def getEpisodeSeasons(self, season_number: int) -> list:
208
+ """
209
+ Get all episodes for a specific season (fetches only when needed).
210
+ """
211
+ if not self.seasons_manager.seasons:
212
+ self.collect_season()
213
+ if season_number not in self._episodes_cache:
214
+ episodes = self._fetch_episodes_for_season(season_number)
215
+ else:
216
+ episodes = self._episodes_cache[season_number]
217
+ return episodes
218
+
219
+ def selectEpisode(self, season_number: int, episode_index: int) -> dict:
220
+ """
221
+ Get information for a specific episode in a specific season.
222
+ """
223
+ episodes = self.getEpisodeSeasons(season_number)
224
+ if not episodes or episode_index < 0 or episode_index >= len(episodes):
225
+ logging.error(f"Episode index {episode_index} is out of range for season {season_number}")
226
+ return None
227
+
228
+ episode = episodes[episode_index]
229
+ episode_id = episode.get("url", "").split("/")[-1] if "url" in episode else None
230
+
231
+ # Update only the episode URL if available in it-IT or en-US
232
+ _, urls_by_locale = self._get_episode_audio_locales_and_urls(episode_id)
233
+ new_url = urls_by_locale.get("it-IT") or urls_by_locale.get("en-US")
234
+
235
+ if new_url:
236
+ episode["url"] = new_url
237
+
238
+ return episode
@@ -0,0 +1,227 @@
1
+ # 28.07.25
2
+
3
+ import uuid
4
+ from dataclasses import dataclass, field
5
+ from typing import Optional, Dict, Any
6
+
7
+
8
+ # External library
9
+ from curl_cffi.requests import Session
10
+
11
+
12
+ # Internal utilities
13
+ from StreamingCommunity.Util.config_json import config_manager
14
+ from StreamingCommunity.Util.headers import get_userAgent, get_headers
15
+
16
+
17
+ # Variable
18
+ device_id = None
19
+ auth_basic = 'bm9haWhkZXZtXzZpeWcwYThsMHE6'
20
+ etp_rt = config_manager.get_dict("SITE_LOGIN", "crunchyroll")['etp_rt']
21
+ x_cr_tab_id = config_manager.get_dict("SITE_LOGIN", "crunchyroll")['x_cr_tab_id']
22
+
23
+
24
+ @dataclass
25
+ class Token:
26
+ access_token: Optional[str] = None
27
+ refresh_token: Optional[str] = None
28
+ expires_in: Optional[int] = None
29
+ token_type: Optional[str] = None
30
+ scope: Optional[str] = None
31
+ country: Optional[str] = None
32
+ account_id: Optional[str] = None
33
+ profile_id: Optional[str] = None
34
+ extra: Dict[str, Any] = field(default_factory=dict)
35
+
36
+
37
+ @dataclass
38
+ class Account:
39
+ account_id: Optional[str] = None
40
+ external_id: Optional[str] = None
41
+ email: Optional[str] = None
42
+ extra: Dict[str, Any] = field(default_factory=dict)
43
+
44
+
45
+ @dataclass
46
+ class Profile:
47
+ profile_id: Optional[str] = None
48
+ email: Optional[str] = None
49
+ profile_name: Optional[str] = None
50
+ extra: Dict[str, Any] = field(default_factory=dict)
51
+
52
+
53
+
54
+ def generate_device_id():
55
+ global device_id
56
+
57
+ if device_id is not None:
58
+ return device_id
59
+
60
+ device_id = str(uuid.uuid4())
61
+ return device_id
62
+
63
+
64
+ def get_auth_token(device_id):
65
+ with Session(impersonate="chrome110") as session:
66
+ cookies = {
67
+ 'etp_rt': etp_rt,
68
+ }
69
+ response = session.post(
70
+ 'https://www.crunchyroll.com/auth/v1/token',
71
+ headers={
72
+ 'authorization': f'Basic {auth_basic}',
73
+ 'user-agent': get_userAgent(),
74
+ },
75
+ data={
76
+ 'device_id': device_id,
77
+ 'device_type': 'Chrome on Windows',
78
+ 'grant_type': 'etp_rt_cookie',
79
+ },
80
+ cookies=cookies
81
+ )
82
+ if response.status_code == 400:
83
+ print("Error 400: Please enter a correct 'etp_rt' value in config.json. You can find the value in the request headers.")
84
+
85
+ # Get the JSON response
86
+ data = response.json()
87
+ known = {
88
+ 'access_token', 'refresh_token', 'expires_in', 'token_type', 'scope',
89
+ 'country', 'account_id', 'profile_id'
90
+ }
91
+ extra = {k: v for k, v in data.items() if k not in known}
92
+ return Token(
93
+ access_token=data.get('access_token'),
94
+ refresh_token=data.get('refresh_token'),
95
+ expires_in=data.get('expires_in'),
96
+ token_type=data.get('token_type'),
97
+ scope=data.get('scope'),
98
+ country=data.get('country'),
99
+ account_id=data.get('account_id'),
100
+ profile_id=data.get('profile_id'),
101
+ extra=extra
102
+ )
103
+
104
+
105
+ def get_account(token: Token, device_id):
106
+ with Session(impersonate="chrome110") as session:
107
+ country = (token.country or "IT")
108
+ cookies = {
109
+ 'device_id': device_id,
110
+ 'c_locale': f'{country.lower()}-{country.upper()}',
111
+ }
112
+ response = session.get(
113
+ 'https://www.crunchyroll.com/accounts/v1/me',
114
+ headers={
115
+ 'authorization': f'Bearer {token.access_token}',
116
+ 'user-agent': get_userAgent(),
117
+ },
118
+ cookies=cookies
119
+ )
120
+ response.raise_for_status()
121
+
122
+ # Get the JSON response
123
+ data = response.json()
124
+ known = {
125
+ 'account_id', 'external_id', 'email'
126
+ }
127
+ extra = {k: v for k, v in data.items() if k not in known}
128
+ return Account(
129
+ account_id=data.get('account_id'),
130
+ external_id=data.get('external_id'),
131
+ email=data.get('email'),
132
+ extra=extra
133
+ )
134
+
135
+
136
+ def get_profiles(token: Token, device_id):
137
+ with Session(impersonate="chrome110") as session:
138
+ country = token.country
139
+ cookies = {
140
+ 'device_id': device_id,
141
+ 'c_locale': f'{country.lower()}-{country.upper()}',
142
+ }
143
+ response = session.get(
144
+ 'https://www.crunchyroll.com/accounts/v1/me/multiprofile',
145
+ headers={
146
+ 'authorization': f'Bearer {token.access_token}',
147
+ 'user-agent': get_userAgent(),
148
+ },
149
+ cookies=cookies
150
+ )
151
+ response.raise_for_status()
152
+
153
+ # Get the JSON response
154
+ data = response.json()
155
+ profiles = []
156
+ for p in data.get('profiles', []):
157
+ known = {
158
+ 'profile_id', 'email', 'profile_name'
159
+ }
160
+ extra = {k: v for k, v in p.items() if k not in known}
161
+ profiles.append(Profile(
162
+ profile_id=p.get('profile_id'),
163
+ email=p.get('email'),
164
+ profile_name=p.get('profile_name'),
165
+ extra=extra
166
+ ))
167
+ return profiles
168
+
169
+
170
+ def cr_login_session(device_id: str, email: str, password: str):
171
+ """
172
+ Esegue una richiesta di login a Crunchyroll SSO usando curl_cffi.requests.
173
+ """
174
+ cookies = {
175
+ 'device_id': device_id,
176
+ }
177
+ data = (
178
+ f'{{"email":"{email}","password":"{password}","eventSettings":{{}}}}'
179
+ )
180
+ with Session(impersonate="chrome110") as session:
181
+ response = session.post(
182
+ 'https://sso.crunchyroll.com/api/login',
183
+ cookies=cookies,
184
+ headers=get_headers(),
185
+ data=data
186
+ )
187
+ response.raise_for_status()
188
+ return response
189
+
190
+
191
+ def get_playback_session(token: Token, device_id: str, url_id: str):
192
+ """
193
+ Crea una sessione per ottenere i dati di playback da Crunchyroll.
194
+ """
195
+ cookies = {
196
+ 'device_id': device_id,
197
+ 'etp_rt': etp_rt
198
+ }
199
+ headers = {
200
+ 'authorization': f'Bearer {token.access_token}',
201
+ 'user-agent': get_userAgent(),
202
+ 'x-cr-tab-id': x_cr_tab_id
203
+ }
204
+
205
+ with Session(impersonate="chrome110") as session:
206
+ response = session.get(
207
+ f'https://www.crunchyroll.com/playback/v3/{url_id}/web/chrome/play',
208
+ cookies=cookies,
209
+ headers=headers
210
+ )
211
+
212
+ if (response.status_code == 403):
213
+ raise Exception("Playback is Rejected: The current subscription does not have access to this content")
214
+
215
+ if (response.status_code == 420):
216
+ raise Exception("TOO_MANY_ACTIVE_STREAMS. Wait a few minutes and try again.")
217
+
218
+ response.raise_for_status()
219
+
220
+ # Get the JSON response
221
+ data = response.json()
222
+
223
+ if data.get('error') == 'Playback is Rejected':
224
+ raise Exception("Playback is Rejected: Premium required")
225
+
226
+ url = data.get('url')
227
+ return url, headers
@@ -1,7 +1,6 @@
1
1
  # 13.06.24
2
2
 
3
3
  import os
4
- import logging
5
4
  from typing import Tuple
6
5
 
7
6
 
@@ -12,7 +11,6 @@ from rich.prompt import Prompt
12
11
 
13
12
  # Internal utilities
14
13
  from StreamingCommunity.Util.message import start_message
15
- from StreamingCommunity.Lib.Downloader import HLS_Downloader
16
14
 
17
15
 
18
16
  # Logic class
@@ -30,6 +28,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
30
28
 
31
29
  # Player
32
30
  from .util.ScrapeSerie import GetSerieInfo
31
+ from StreamingCommunity import HLS_Downloader
33
32
  from StreamingCommunity.Api.Player.supervideo import VideoSource
34
33
 
35
34
 
@@ -59,9 +59,8 @@ def title_search(query: str) -> int:
59
59
 
60
60
  # Create soup and find table
61
61
  soup = BeautifulSoup(response.text, "html.parser")
62
- table_content = soup.find('div', class_="recent-posts")
63
62
 
64
- for serie_div in table_content.find_all('div', class_='post-thumb'):
63
+ for serie_div in soup.find_all('div', class_='entry'):
65
64
  try:
66
65
 
67
66
  title = serie_div.find('a').get("title")
@@ -40,26 +40,27 @@ class GetSerieInfo:
40
40
  Retrieves the number of seasons of a TV series.
41
41
 
42
42
  Returns:
43
- int: Number of seasons of the TV series.
43
+ int: Number of seasons of the TV series. Returns -1 if parsing fails.
44
44
  """
45
45
  try:
46
-
47
46
  # Make an HTTP request to the series URL
48
47
  response = httpx.get(self.url, headers=self.headers, timeout=max_timeout, follow_redirects=True)
49
48
  response.raise_for_status()
50
49
 
50
+ # Find the seasons container
51
51
  soup = BeautifulSoup(response.text, "html.parser")
52
-
53
52
  table_content = soup.find('div', class_="tt_season")
54
53
  seasons_number = len(table_content.find_all("li"))
55
- self.tv_name = soup.find("h1", class_="entry-title").get_text(strip=True)
54
+
55
+ # Try to get the title, with fallback
56
+ title_element = soup.find("h1", class_="entry-title")
57
+ self.tv_name = title_element.get_text(strip=True) if title_element else "Unknown Title"
56
58
 
57
59
  return seasons_number
58
60
 
59
61
  except Exception as e:
60
- logging.error(f"Error parsing HTML page: {e}")
61
-
62
- return -1
62
+ logging.error(f"Error parsing HTML page: {str(e)}")
63
+ return -1
63
64
 
64
65
  def get_episode_number(self, n_season: int) -> List[Dict[str, str]]:
65
66
  """
@@ -88,7 +89,7 @@ class GetSerieInfo:
88
89
  for episode_div in episode_content:
89
90
  index = episode_div.find("a").get("data-num")
90
91
  link = episode_div.find("a").get("data-link")
91
- name = episode_div.find("a").get("data-title")
92
+ name = episode_div.find("a").get("data-num")
92
93
 
93
94
  obj_episode = {
94
95
  'number': index,
@@ -0,0 +1,96 @@
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 = 3
23
+ _useFor = "Film_&_Serie"
24
+ _priority = 0
25
+ _engineDownload = "hls"
26
+ _deprecate = False
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
+ if string_to_search is None:
37
+ string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
38
+
39
+ return msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
40
+
41
+ def process_search_result(select_title, selections=None):
42
+ """
43
+ Handles the search result and initiates the download for either a film or series.
44
+
45
+ Parameters:
46
+ select_title (MediaItem): The selected media item
47
+ selections (dict, optional): Dictionary containing selection inputs that bypass manual input
48
+ {'season': season_selection, 'episode': episode_selection}
49
+ """
50
+ if select_title.type == 'tv':
51
+ season_selection = None
52
+ episode_selection = None
53
+
54
+ if selections:
55
+ season_selection = selections.get('season')
56
+ episode_selection = selections.get('episode')
57
+
58
+ download_series(select_title, season_selection, episode_selection)
59
+
60
+ else:
61
+ download_film(select_title)
62
+
63
+ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
64
+ """
65
+ Main function of the application for search.
66
+
67
+ Parameters:
68
+ string_to_search (str, optional): String to search for
69
+ get_onlyDatabase (bool, optional): If True, return only the database object
70
+ direct_item (dict, optional): Direct item to process (bypass search)
71
+ selections (dict, optional): Dictionary containing selection inputs that bypass manual input
72
+ {'season': season_selection, 'episode': episode_selection}
73
+ """
74
+ if direct_item:
75
+ select_title = MediaItem(**direct_item)
76
+ process_search_result(select_title, selections)
77
+ return
78
+
79
+ if string_to_search is None:
80
+ string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
81
+
82
+ # Search on database
83
+ len_database = title_search(string_to_search)
84
+
85
+ # If only the database is needed, return the manager
86
+ if get_onlyDatabase:
87
+ return media_search_manager
88
+
89
+ if len_database > 0:
90
+ select_title = get_select_title(table_show_manager, media_search_manager,len_database)
91
+ process_search_result(select_title, selections)
92
+
93
+ else:
94
+ # If no results are found, ask again
95
+ console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
96
+ search()