StreamingCommunity 3.3.3__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.
- StreamingCommunity/Api/Site/altadefinizione/__init__.py +17 -18
- StreamingCommunity/Api/Site/altadefinizione/series.py +4 -0
- StreamingCommunity/Api/Site/animeunity/__init__.py +14 -15
- StreamingCommunity/Api/Site/animeunity/serie.py +1 -1
- StreamingCommunity/Api/Site/animeworld/__init__.py +15 -13
- StreamingCommunity/Api/Site/animeworld/serie.py +1 -1
- StreamingCommunity/Api/Site/crunchyroll/__init__.py +16 -17
- StreamingCommunity/Api/Site/crunchyroll/series.py +6 -1
- StreamingCommunity/Api/Site/guardaserie/__init__.py +17 -19
- StreamingCommunity/Api/Site/guardaserie/series.py +4 -0
- StreamingCommunity/Api/Site/guardaserie/site.py +2 -7
- StreamingCommunity/Api/Site/mediasetinfinity/__init__.py +15 -15
- StreamingCommunity/Api/Site/mediasetinfinity/series.py +4 -0
- StreamingCommunity/Api/Site/mediasetinfinity/site.py +12 -2
- StreamingCommunity/Api/Site/mediasetinfinity/util/ScrapeSerie.py +67 -98
- StreamingCommunity/Api/Site/raiplay/__init__.py +15 -15
- StreamingCommunity/Api/Site/raiplay/series.py +5 -1
- StreamingCommunity/Api/Site/streamingcommunity/__init__.py +16 -14
- StreamingCommunity/Api/Site/streamingwatch/__init__.py +12 -12
- StreamingCommunity/Api/Site/streamingwatch/series.py +4 -0
- StreamingCommunity/Api/Template/Class/SearchType.py +0 -1
- StreamingCommunity/Api/Template/Util/manage_ep.py +1 -11
- StreamingCommunity/Api/Template/site.py +2 -3
- StreamingCommunity/Lib/Downloader/DASH/decrypt.py +4 -1
- StreamingCommunity/Lib/Downloader/DASH/downloader.py +55 -17
- StreamingCommunity/Lib/Downloader/DASH/segments.py +73 -17
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +282 -152
- StreamingCommunity/Lib/Downloader/HLS/segments.py +1 -5
- StreamingCommunity/Lib/FFmpeg/capture.py +1 -1
- StreamingCommunity/Lib/FFmpeg/command.py +6 -6
- StreamingCommunity/Lib/FFmpeg/util.py +11 -30
- StreamingCommunity/Lib/M3U8/estimator.py +27 -13
- StreamingCommunity/Upload/update.py +2 -2
- StreamingCommunity/Upload/version.py +1 -1
- StreamingCommunity/Util/installer/__init__.py +11 -0
- StreamingCommunity/Util/installer/device_install.py +1 -1
- StreamingCommunity/Util/os.py +2 -6
- StreamingCommunity/Util/table.py +40 -8
- StreamingCommunity/run.py +15 -8
- {streamingcommunity-3.3.3.dist-info → streamingcommunity-3.3.6.dist-info}/METADATA +38 -51
- {streamingcommunity-3.3.3.dist-info → streamingcommunity-3.3.6.dist-info}/RECORD +45 -44
- {streamingcommunity-3.3.3.dist-info → streamingcommunity-3.3.6.dist-info}/WHEEL +0 -0
- {streamingcommunity-3.3.3.dist-info → streamingcommunity-3.3.6.dist-info}/entry_points.txt +0 -0
- {streamingcommunity-3.3.3.dist-info → streamingcommunity-3.3.6.dist-info}/licenses/LICENSE +0 -0
- {streamingcommunity-3.3.3.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
|
-
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
"""
|
|
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
|
-
#
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
"""
|
|
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
|
-
|
|
183
|
-
|
|
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
|
-
"""
|
|
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
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
#
|
|
280
|
-
|
|
281
|
-
|
|
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
|
|
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
|
|
135
|
+
# Handle empty input
|
|
133
136
|
if not actual_search_query:
|
|
134
137
|
if bot:
|
|
135
|
-
if actual_search_query is None:
|
|
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
|
-
#
|
|
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
|
|
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
|
-
|
|
125
|
-
process_search_result(
|
|
126
|
-
return
|
|
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
|
|
135
|
+
# Handle empty input
|
|
131
136
|
if not actual_search_query:
|
|
132
137
|
if bot:
|
|
133
|
-
if actual_search_query is None:
|
|
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
|
-
#
|
|
142
|
+
# Search on database
|
|
138
143
|
len_database = title_search(actual_search_query)
|
|
139
144
|
|
|
140
|
-
# If only the database
|
|
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
|
|
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
|
|
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
|
-
|
|
134
|
+
# Handle empty input
|
|
132
135
|
if not actual_search_query:
|
|
133
136
|
if bot:
|
|
134
|
-
if actual_search_query is None:
|
|
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
|
-
#
|
|
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
|
|
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)
|
|
@@ -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
|
-
|
|
133
|
-
return None
|
|
132
|
+
return None
|
|
@@ -9,6 +9,9 @@ import logging
|
|
|
9
9
|
from rich.console import Console
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
# Internal utilities
|
|
13
|
+
from StreamingCommunity.Util.os import get_mp4decrypt_path
|
|
14
|
+
|
|
12
15
|
# Variable
|
|
13
16
|
console = Console()
|
|
14
17
|
|
|
@@ -45,7 +48,7 @@ def decrypt_with_mp4decrypt(encrypted_path, kid, key, output_path=None, cleanup=
|
|
|
45
48
|
output_path = os.path.splitext(encrypted_path)[0] + "_decrypted.mp4"
|
|
46
49
|
|
|
47
50
|
key_format = f"{kid.lower()}:{key.lower()}"
|
|
48
|
-
cmd = [
|
|
51
|
+
cmd = [get_mp4decrypt_path(), "--key", key_format, encrypted_path, output_path]
|
|
49
52
|
logging.info(f"Running command: {' '.join(cmd)}")
|
|
50
53
|
|
|
51
54
|
try:
|