StreamingCommunity 3.2.8__py3-none-any.whl → 3.3.0__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/Player/Helper/Vixcloud/util.py +2 -1
- StreamingCommunity/Api/Player/hdplayer.py +2 -2
- StreamingCommunity/Api/Player/sweetpixel.py +5 -8
- StreamingCommunity/Api/Site/altadefinizione/__init__.py +32 -15
- StreamingCommunity/Api/Site/altadefinizione/film.py +10 -8
- StreamingCommunity/Api/Site/altadefinizione/series.py +9 -7
- StreamingCommunity/Api/Site/altadefinizione/site.py +1 -1
- StreamingCommunity/Api/Site/animeunity/__init__.py +31 -15
- StreamingCommunity/Api/Site/animeunity/serie.py +2 -2
- StreamingCommunity/Api/Site/animeworld/__init__.py +33 -7
- StreamingCommunity/Api/Site/animeworld/site.py +3 -5
- StreamingCommunity/Api/Site/animeworld/util/ScrapeSerie.py +8 -10
- StreamingCommunity/Api/Site/crunchyroll/__init__.py +44 -12
- StreamingCommunity/Api/Site/crunchyroll/film.py +9 -7
- StreamingCommunity/Api/Site/crunchyroll/series.py +9 -7
- StreamingCommunity/Api/Site/crunchyroll/site.py +16 -1
- StreamingCommunity/Api/Site/guardaserie/__init__.py +36 -10
- StreamingCommunity/Api/Site/guardaserie/series.py +8 -6
- StreamingCommunity/Api/Site/guardaserie/site.py +0 -3
- StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +1 -2
- StreamingCommunity/Api/Site/mediasetinfinity/__init__.py +37 -12
- StreamingCommunity/Api/Site/mediasetinfinity/film.py +10 -16
- StreamingCommunity/Api/Site/mediasetinfinity/series.py +12 -18
- StreamingCommunity/Api/Site/mediasetinfinity/site.py +18 -3
- StreamingCommunity/Api/Site/mediasetinfinity/util/ScrapeSerie.py +214 -180
- StreamingCommunity/Api/Site/mediasetinfinity/util/get_license.py +2 -31
- StreamingCommunity/Api/Site/raiplay/__init__.py +47 -12
- StreamingCommunity/Api/Site/raiplay/film.py +42 -10
- StreamingCommunity/Api/Site/raiplay/series.py +53 -11
- StreamingCommunity/Api/Site/raiplay/site.py +4 -1
- StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py +2 -1
- StreamingCommunity/Api/Site/raiplay/util/get_license.py +40 -0
- StreamingCommunity/Api/Site/streamingcommunity/__init__.py +5 -8
- StreamingCommunity/Api/Site/streamingcommunity/film.py +7 -5
- StreamingCommunity/Api/Site/streamingcommunity/series.py +9 -7
- StreamingCommunity/Api/Site/streamingcommunity/site.py +8 -3
- StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +5 -2
- StreamingCommunity/Api/Site/streamingwatch/__init__.py +43 -9
- StreamingCommunity/Api/Site/streamingwatch/film.py +7 -5
- StreamingCommunity/Api/Site/streamingwatch/series.py +8 -6
- StreamingCommunity/Api/Site/streamingwatch/site.py +3 -1
- StreamingCommunity/Api/Site/streamingwatch/util/ScrapeSerie.py +3 -3
- StreamingCommunity/Api/Template/Util/__init__.py +10 -1
- StreamingCommunity/Api/Template/Util/manage_ep.py +4 -4
- StreamingCommunity/Api/Template/__init__.py +5 -1
- StreamingCommunity/Api/Template/site.py +10 -6
- StreamingCommunity/Lib/Downloader/DASH/cdm_helpher.py +13 -12
- StreamingCommunity/Lib/Downloader/DASH/decrypt.py +1 -1
- StreamingCommunity/Lib/Downloader/DASH/downloader.py +24 -22
- StreamingCommunity/Lib/Downloader/DASH/parser.py +1 -1
- StreamingCommunity/Lib/Downloader/DASH/segments.py +4 -3
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +11 -9
- StreamingCommunity/Lib/Downloader/HLS/segments.py +4 -9
- StreamingCommunity/Lib/Downloader/MP4/downloader.py +25 -6
- StreamingCommunity/Lib/Downloader/TOR/downloader.py +3 -5
- StreamingCommunity/Lib/Downloader/__init__.py +9 -1
- StreamingCommunity/Lib/FFmpeg/__init__.py +10 -1
- StreamingCommunity/Lib/FFmpeg/command.py +4 -6
- StreamingCommunity/Lib/FFmpeg/util.py +1 -1
- StreamingCommunity/Lib/M3U8/__init__.py +9 -1
- StreamingCommunity/Lib/M3U8/decryptor.py +8 -4
- StreamingCommunity/Lib/M3U8/estimator.py +0 -6
- StreamingCommunity/Lib/M3U8/parser.py +1 -1
- StreamingCommunity/Lib/M3U8/url_fixer.py +1 -1
- StreamingCommunity/Lib/TMBD/__init__.py +6 -1
- StreamingCommunity/TelegramHelp/config.json +1 -5
- StreamingCommunity/TelegramHelp/telegram_bot.py +9 -10
- StreamingCommunity/Upload/update.py +2 -2
- StreamingCommunity/Upload/version.py +1 -1
- StreamingCommunity/Util/config_json.py +139 -59
- StreamingCommunity/Util/http_client.py +201 -0
- StreamingCommunity/Util/message.py +1 -1
- StreamingCommunity/Util/os.py +8 -5
- StreamingCommunity/Util/table.py +3 -3
- StreamingCommunity/__init__.py +9 -1
- StreamingCommunity/run.py +394 -258
- {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.3.0.dist-info}/METADATA +147 -45
- streamingcommunity-3.3.0.dist-info/RECORD +110 -0
- StreamingCommunity/Api/Site/cb01new/__init__.py +0 -72
- StreamingCommunity/Api/Site/cb01new/film.py +0 -62
- StreamingCommunity/Api/Site/cb01new/site.py +0 -78
- streamingcommunity-3.2.8.dist-info/RECORD +0 -111
- {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.3.0.dist-info}/WHEEL +0 -0
- {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.3.0.dist-info}/entry_points.txt +0 -0
- {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.3.0.dist-info}/licenses/LICENSE +0 -0
- {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.3.0.dist-info}/top_level.txt +0 -0
|
@@ -54,13 +54,13 @@ def download_film(select_title: MediaItem) -> str:
|
|
|
54
54
|
query_params = parse_qs(parsed_url.query)
|
|
55
55
|
|
|
56
56
|
# Download the episode
|
|
57
|
-
|
|
57
|
+
dash_process = DASH_Downloader(
|
|
58
58
|
cdm_device=get_wvd_path(),
|
|
59
59
|
license_url='https://www.crunchyroll.com/license/v1/license/widevine',
|
|
60
60
|
mpd_url=mpd_url,
|
|
61
61
|
output_path=os.path.join(mp4_path, mp4_name),
|
|
62
62
|
)
|
|
63
|
-
|
|
63
|
+
dash_process.parse_manifest(custom_headers=mpd_headers)
|
|
64
64
|
|
|
65
65
|
# Create headers for license request
|
|
66
66
|
license_headers = mpd_headers.copy()
|
|
@@ -69,14 +69,16 @@ def download_film(select_title: MediaItem) -> str:
|
|
|
69
69
|
"x-cr-video-token": query_params['playbackGuid'][0],
|
|
70
70
|
})
|
|
71
71
|
|
|
72
|
-
if
|
|
73
|
-
|
|
72
|
+
if dash_process.download_and_decrypt(custom_headers=license_headers):
|
|
73
|
+
dash_process.finalize_output()
|
|
74
74
|
|
|
75
75
|
# Get final output path and status
|
|
76
|
-
status =
|
|
76
|
+
status = dash_process.get_status()
|
|
77
77
|
|
|
78
78
|
if status['error'] is not None and status['path']:
|
|
79
|
-
try:
|
|
80
|
-
|
|
79
|
+
try:
|
|
80
|
+
os.remove(status['path'])
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
81
83
|
|
|
82
84
|
return status['path'], status['stopped']
|
|
@@ -71,13 +71,13 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
71
71
|
query_params = parse_qs(parsed_url.query)
|
|
72
72
|
|
|
73
73
|
# Download the episode
|
|
74
|
-
|
|
74
|
+
dash_process = DASH_Downloader(
|
|
75
75
|
cdm_device=get_wvd_path(),
|
|
76
76
|
license_url='https://www.crunchyroll.com/license/v1/license/widevine',
|
|
77
77
|
mpd_url=mpd_url,
|
|
78
78
|
output_path=os.path.join(mp4_path, mp4_name),
|
|
79
79
|
)
|
|
80
|
-
|
|
80
|
+
dash_process.parse_manifest(custom_headers=mpd_headers)
|
|
81
81
|
|
|
82
82
|
# Create headers for license request
|
|
83
83
|
license_headers = mpd_headers.copy()
|
|
@@ -86,15 +86,17 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
86
86
|
"x-cr-video-token": query_params['playbackGuid'][0],
|
|
87
87
|
})
|
|
88
88
|
|
|
89
|
-
if
|
|
90
|
-
|
|
89
|
+
if dash_process.download_and_decrypt(custom_headers=license_headers):
|
|
90
|
+
dash_process.finalize_output()
|
|
91
91
|
|
|
92
92
|
# Get final output path and status
|
|
93
|
-
status =
|
|
93
|
+
status = dash_process.get_status()
|
|
94
94
|
|
|
95
95
|
if status['error'] is not None and status['path']:
|
|
96
|
-
try:
|
|
97
|
-
|
|
96
|
+
try:
|
|
97
|
+
os.remove(status['path'])
|
|
98
|
+
except Exception:
|
|
99
|
+
pass
|
|
98
100
|
|
|
99
101
|
# Delete episode stream
|
|
100
102
|
delete_stream_episode(url_id, query_params['playbackGuid'][0], mpd_headers)
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# 16.03.25
|
|
2
2
|
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
3
6
|
|
|
4
7
|
# External libraries
|
|
5
8
|
from curl_cffi import requests
|
|
@@ -8,6 +11,7 @@ from rich.console import Console
|
|
|
8
11
|
|
|
9
12
|
# Internal utilities
|
|
10
13
|
from StreamingCommunity.Util.config_json import config_manager
|
|
14
|
+
from StreamingCommunity.Util.os import get_wvd_path
|
|
11
15
|
from StreamingCommunity.Util.headers import get_headers
|
|
12
16
|
from StreamingCommunity.Util.table import TVShowManager
|
|
13
17
|
|
|
@@ -38,8 +42,19 @@ def title_search(query: str) -> int:
|
|
|
38
42
|
media_search_manager.clear()
|
|
39
43
|
table_show_manager.clear()
|
|
40
44
|
|
|
45
|
+
# Check CDM file before usage
|
|
46
|
+
cdm_device_path = get_wvd_path()
|
|
47
|
+
if not cdm_device_path or not isinstance(cdm_device_path, (str, bytes, os.PathLike)) or not os.path.isfile(cdm_device_path):
|
|
48
|
+
console.print(f"[bold red] CDM file not found or invalid path: {cdm_device_path}[/bold red]")
|
|
49
|
+
sys.exit(0)
|
|
50
|
+
|
|
51
|
+
# Check if x_cr_tab_id or etp_rt is present
|
|
52
|
+
if config_manager.get_dict("SITE_LOGIN", "crunchyroll")['x_cr_tab_id'] is None or config_manager.get_dict("SITE_LOGIN", "crunchyroll")['x_cr_tab_id'] == "" or config_manager.get_dict("SITE_LOGIN", "crunchyroll")['etp_rt'] is None or config_manager.get_dict("SITE_LOGIN", "crunchyroll")['etp_rt'] == "":
|
|
53
|
+
console.print(f"[bold red] x_cr_tab_id or etp_rt is missing or empty.[/bold red]")
|
|
54
|
+
sys.exit(0)
|
|
55
|
+
|
|
41
56
|
# Build new Crunchyroll API search URL
|
|
42
|
-
api_url =
|
|
57
|
+
api_url = "https://www.crunchyroll.com/content/v2/discover/search"
|
|
43
58
|
|
|
44
59
|
params = {
|
|
45
60
|
"q": query,
|
|
@@ -12,6 +12,7 @@ from rich.prompt import Prompt
|
|
|
12
12
|
from StreamingCommunity.Api.Template import get_select_title
|
|
13
13
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
14
14
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
15
|
+
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
# Logic class
|
|
@@ -39,6 +40,14 @@ def process_search_result(select_title, selections=None):
|
|
|
39
40
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
|
40
41
|
{'season': season_selection, 'episode': episode_selection}
|
|
41
42
|
"""
|
|
43
|
+
if not select_title:
|
|
44
|
+
if site_constant.TELEGRAM_BOT:
|
|
45
|
+
bot = get_bot_instance()
|
|
46
|
+
bot.send_message("No title selected or selection cancelled.", None)
|
|
47
|
+
else:
|
|
48
|
+
console.print("[yellow]No title selected or selection cancelled.")
|
|
49
|
+
return
|
|
50
|
+
|
|
42
51
|
season_selection = None
|
|
43
52
|
episode_selection = None
|
|
44
53
|
|
|
@@ -59,27 +68,44 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
|
|
|
59
68
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
|
60
69
|
{'season': season_selection, 'episode': episode_selection}
|
|
61
70
|
"""
|
|
71
|
+
bot = None
|
|
72
|
+
if site_constant.TELEGRAM_BOT:
|
|
73
|
+
try:
|
|
74
|
+
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
|
|
75
|
+
bot = get_bot_instance()
|
|
76
|
+
except Exception:
|
|
77
|
+
bot = None
|
|
78
|
+
|
|
62
79
|
if direct_item:
|
|
63
80
|
select_title = MediaItem(**direct_item)
|
|
64
81
|
process_search_result(select_title, selections)
|
|
65
82
|
return
|
|
66
83
|
|
|
67
84
|
if string_to_search is None:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
85
|
+
actual_search_query = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip()
|
|
86
|
+
else:
|
|
87
|
+
actual_search_query = string_to_search
|
|
88
|
+
|
|
89
|
+
# Handle empty input
|
|
90
|
+
if not actual_search_query:
|
|
91
|
+
if bot:
|
|
92
|
+
if actual_search_query is None:
|
|
93
|
+
bot.send_message("Search term not provided or operation cancelled. Returning.", None)
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
# Search on database (preserve quote_plus usage)
|
|
97
|
+
len_database = title_search(quote_plus(actual_search_query))
|
|
72
98
|
|
|
73
99
|
# If only the database is needed, return the manager
|
|
74
100
|
if get_onlyDatabase:
|
|
75
101
|
return media_search_manager
|
|
76
102
|
|
|
77
103
|
if len_database > 0:
|
|
78
|
-
select_title = get_select_title(table_show_manager, media_search_manager,len_database)
|
|
104
|
+
select_title = get_select_title(table_show_manager, media_search_manager, len_database)
|
|
79
105
|
process_search_result(select_title, selections)
|
|
80
|
-
|
|
81
106
|
else:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
107
|
+
if bot:
|
|
108
|
+
bot.send_message(f"No results found for: '{actual_search_query}'", None)
|
|
109
|
+
else:
|
|
110
|
+
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{actual_search_query}")
|
|
111
|
+
return
|
|
@@ -55,7 +55,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scap
|
|
|
55
55
|
# Get episode information
|
|
56
56
|
obj_episode = scape_info_serie.selectEpisode(index_season_selected, index_episode_selected-1)
|
|
57
57
|
index_season_selected = dynamic_format_number(str(index_season_selected))
|
|
58
|
-
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")
|
|
58
|
+
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{scape_info_serie.tv_name}[/cyan] \\ [bold magenta]{obj_episode.get('name')}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
|
|
59
59
|
|
|
60
60
|
# Define filename and path for the downloaded video
|
|
61
61
|
mp4_name = f"{map_episode_title(scape_info_serie.tv_name, index_season_selected, index_episode_selected, obj_episode.get('name'))}.mp4"
|
|
@@ -68,16 +68,18 @@ def download_video(index_season_selected: int, index_episode_selected: int, scap
|
|
|
68
68
|
master_playlist = video_source.get_playlist()
|
|
69
69
|
|
|
70
70
|
# Download the film using the m3u8 playlist, and output filename
|
|
71
|
-
|
|
71
|
+
hls_process = HLS_Downloader(
|
|
72
72
|
m3u8_url=master_playlist,
|
|
73
73
|
output_path=os.path.join(mp4_path, mp4_name)
|
|
74
74
|
).start()
|
|
75
75
|
|
|
76
|
-
if
|
|
77
|
-
try:
|
|
78
|
-
|
|
76
|
+
if hls_process['error'] is not None:
|
|
77
|
+
try:
|
|
78
|
+
os.remove(hls_process['path'])
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
79
81
|
|
|
80
|
-
return
|
|
82
|
+
return hls_process['path'], hls_process['stopped']
|
|
81
83
|
|
|
82
84
|
|
|
83
85
|
def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int, download_all: bool = False, episode_selection: str = None) -> None:
|
|
@@ -53,8 +53,7 @@ class GetSerieInfo:
|
|
|
53
53
|
seasons_number = len(table_content.find_all("li"))
|
|
54
54
|
|
|
55
55
|
# Try to get the title, with fallback
|
|
56
|
-
|
|
57
|
-
self.tv_name = title_element.get_text(strip=True) if title_element else "Unknown Title"
|
|
56
|
+
self.tv_name = soup.find('h1', class_="front_title").get_text(strip=True) if soup.find('h1', class_="front_title") else "Unknown Series"
|
|
58
57
|
|
|
59
58
|
return seasons_number
|
|
60
59
|
|
|
@@ -10,6 +10,7 @@ from rich.prompt import Prompt
|
|
|
10
10
|
from StreamingCommunity.Api.Template import get_select_title
|
|
11
11
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
12
12
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
13
|
+
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
# Logic class
|
|
@@ -22,7 +23,7 @@ from .film import download_film
|
|
|
22
23
|
indice = 3
|
|
23
24
|
_useFor = "Film_&_Serie"
|
|
24
25
|
_priority = 0
|
|
25
|
-
_engineDownload = "
|
|
26
|
+
_engineDownload = "dash"
|
|
26
27
|
_deprecate = False
|
|
27
28
|
|
|
28
29
|
msg = Prompt()
|
|
@@ -47,6 +48,14 @@ def process_search_result(select_title, selections=None):
|
|
|
47
48
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
|
48
49
|
{'season': season_selection, 'episode': episode_selection}
|
|
49
50
|
"""
|
|
51
|
+
if not select_title:
|
|
52
|
+
if site_constant.TELEGRAM_BOT:
|
|
53
|
+
bot = get_bot_instance()
|
|
54
|
+
bot.send_message("No title selected or selection cancelled.", None)
|
|
55
|
+
else:
|
|
56
|
+
console.print("[yellow]No title selected or selection cancelled.")
|
|
57
|
+
return
|
|
58
|
+
|
|
50
59
|
if select_title.type == 'tv':
|
|
51
60
|
season_selection = None
|
|
52
61
|
episode_selection = None
|
|
@@ -71,26 +80,42 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
|
|
|
71
80
|
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
|
72
81
|
{'season': season_selection, 'episode': episode_selection}
|
|
73
82
|
"""
|
|
83
|
+
bot = None
|
|
84
|
+
if site_constant.TELEGRAM_BOT:
|
|
85
|
+
bot = get_bot_instance()
|
|
86
|
+
|
|
74
87
|
if direct_item:
|
|
75
88
|
select_title = MediaItem(**direct_item)
|
|
76
89
|
process_search_result(select_title, selections)
|
|
77
90
|
return
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
#
|
|
83
|
-
|
|
91
|
+
|
|
92
|
+
# Get the user input for the search term
|
|
93
|
+
actual_search_query = get_user_input(string_to_search)
|
|
94
|
+
|
|
95
|
+
# Handle cases where user input is empty, or 'back' was handled (sys.exit or None return)
|
|
96
|
+
if not actual_search_query:
|
|
97
|
+
if bot:
|
|
98
|
+
if actual_search_query is None: # Specifically for timeout from bot.ask or failed restart
|
|
99
|
+
bot.send_message("Search term not provided or operation cancelled. Returning.", None)
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
# Perform the database search
|
|
103
|
+
len_database = title_search(actual_search_query)
|
|
84
104
|
|
|
85
105
|
# If only the database is needed, return the manager
|
|
86
106
|
if get_onlyDatabase:
|
|
87
107
|
return media_search_manager
|
|
88
|
-
|
|
108
|
+
|
|
89
109
|
if len_database > 0:
|
|
90
|
-
select_title = get_select_title(table_show_manager, media_search_manager,len_database)
|
|
110
|
+
select_title = get_select_title(table_show_manager, media_search_manager, len_database)
|
|
91
111
|
process_search_result(select_title, selections)
|
|
92
112
|
|
|
93
113
|
else:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
114
|
+
if bot:
|
|
115
|
+
bot.send_message(f"No results found for: '{actual_search_query}'", None)
|
|
116
|
+
else:
|
|
117
|
+
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{actual_search_query}")
|
|
118
|
+
|
|
119
|
+
# Do not call search() recursively here to avoid infinite loops on no results.
|
|
120
|
+
# The flow should return to the caller (e.g., main menu in run.py).
|
|
121
|
+
return
|
|
@@ -49,37 +49,31 @@ def download_film(select_title: MediaItem) -> Tuple[str, bool]:
|
|
|
49
49
|
# Generate mpd and license URLs
|
|
50
50
|
bearer = get_bearer_token()
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
episode_id = select_title.url.split('_')[-1]
|
|
54
|
-
if "http" in episode_id:
|
|
55
|
-
try: episode_id = select_title.url.split('/')[-1]
|
|
56
|
-
except Exception:
|
|
57
|
-
console.print(f"[red]Error:[/red] Failed to parse episode ID from URL: {select_title.url}")
|
|
58
|
-
return None, True
|
|
59
|
-
|
|
60
|
-
playback_json = get_playback_url(bearer, episode_id)
|
|
52
|
+
playback_json = get_playback_url(bearer, select_title.id)
|
|
61
53
|
tracking_info = get_tracking_info(bearer, playback_json)[0]
|
|
62
54
|
|
|
63
55
|
license_url = generate_license_url(bearer, tracking_info)
|
|
64
56
|
mpd_url = get_manifest(tracking_info['video_src'])
|
|
65
57
|
|
|
66
58
|
# Download the episode
|
|
67
|
-
|
|
59
|
+
dash_process = DASH_Downloader(
|
|
68
60
|
cdm_device=get_wvd_path(),
|
|
69
61
|
license_url=license_url,
|
|
70
62
|
mpd_url=mpd_url,
|
|
71
63
|
output_path=mp4_path,
|
|
72
64
|
)
|
|
73
|
-
|
|
65
|
+
dash_process.parse_manifest(custom_headers=get_headers())
|
|
74
66
|
|
|
75
|
-
if
|
|
76
|
-
|
|
67
|
+
if dash_process.download_and_decrypt():
|
|
68
|
+
dash_process.finalize_output()
|
|
77
69
|
|
|
78
70
|
# Get final output path and status
|
|
79
|
-
status =
|
|
71
|
+
status = dash_process.get_status()
|
|
80
72
|
|
|
81
73
|
if status['error'] is not None and status['path']:
|
|
82
|
-
try:
|
|
83
|
-
|
|
74
|
+
try:
|
|
75
|
+
os.remove(status['path'])
|
|
76
|
+
except Exception:
|
|
77
|
+
pass
|
|
84
78
|
|
|
85
79
|
return status['path'], status['stopped']
|
|
@@ -56,7 +56,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
56
56
|
|
|
57
57
|
# Get episode information
|
|
58
58
|
obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
|
|
59
|
-
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")
|
|
59
|
+
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")
|
|
60
60
|
|
|
61
61
|
# Define filename and path for the downloaded video
|
|
62
62
|
mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4"
|
|
@@ -64,39 +64,33 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
64
64
|
|
|
65
65
|
# Generate mpd and license URLs
|
|
66
66
|
bearer = get_bearer_token()
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
episode_id = obj_episode.url.split('_')[-1]
|
|
70
|
-
if "http" in episode_id:
|
|
71
|
-
try: episode_id = obj_episode.url.split('/')[-1]
|
|
72
|
-
except Exception:
|
|
73
|
-
console.print(f"[red]Error:[/red] Failed to parse episode ID from URL: {obj_episode.url}")
|
|
74
|
-
return None, True
|
|
75
|
-
|
|
76
|
-
playback_json = get_playback_url(bearer, episode_id)
|
|
67
|
+
|
|
68
|
+
playback_json = get_playback_url(bearer, obj_episode.id)
|
|
77
69
|
tracking_info = get_tracking_info(bearer, playback_json)[0]
|
|
78
70
|
|
|
79
71
|
license_url = generate_license_url(bearer, tracking_info)
|
|
80
72
|
mpd_url = get_manifest(tracking_info['video_src'])
|
|
81
73
|
|
|
82
74
|
# Download the episode
|
|
83
|
-
|
|
75
|
+
dash_process = DASH_Downloader(
|
|
84
76
|
cdm_device=get_wvd_path(),
|
|
85
77
|
license_url=license_url,
|
|
86
78
|
mpd_url=mpd_url,
|
|
87
79
|
output_path=os.path.join(mp4_path, mp4_name),
|
|
88
80
|
)
|
|
89
|
-
|
|
81
|
+
dash_process.parse_manifest(custom_headers=get_headers())
|
|
90
82
|
|
|
91
|
-
if
|
|
92
|
-
|
|
83
|
+
if dash_process.download_and_decrypt():
|
|
84
|
+
dash_process.finalize_output()
|
|
93
85
|
|
|
94
86
|
# Get final output path and status
|
|
95
|
-
status =
|
|
87
|
+
status = dash_process.get_status()
|
|
96
88
|
|
|
97
89
|
if status['error'] is not None and status['path']:
|
|
98
|
-
try:
|
|
99
|
-
|
|
90
|
+
try:
|
|
91
|
+
os.remove(status['path'])
|
|
92
|
+
except Exception:
|
|
93
|
+
pass
|
|
100
94
|
|
|
101
95
|
return status['path'], status['stopped']
|
|
102
96
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# 25.07.25
|
|
2
2
|
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
3
7
|
# External libraries
|
|
4
8
|
import httpx
|
|
5
9
|
from rich.console import Console
|
|
@@ -7,6 +11,7 @@ from rich.console import Console
|
|
|
7
11
|
|
|
8
12
|
# Internal utilities
|
|
9
13
|
from StreamingCommunity.Util.config_json import config_manager
|
|
14
|
+
from StreamingCommunity.Util.os import get_wvd_path
|
|
10
15
|
from StreamingCommunity.Util.headers import get_headers
|
|
11
16
|
from StreamingCommunity.Util.table import TVShowManager
|
|
12
17
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
@@ -37,7 +42,18 @@ def title_search(query: str) -> int:
|
|
|
37
42
|
media_search_manager.clear()
|
|
38
43
|
table_show_manager.clear()
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
# Check CDM file before usage
|
|
46
|
+
cdm_device_path = get_wvd_path()
|
|
47
|
+
if not cdm_device_path or not isinstance(cdm_device_path, (str, bytes, os.PathLike)) or not os.path.isfile(cdm_device_path):
|
|
48
|
+
console.print(f"[bold red] CDM file not found or invalid path: {cdm_device_path}[/bold red]")
|
|
49
|
+
sys.exit(0)
|
|
50
|
+
|
|
51
|
+
# Check if beToken is present
|
|
52
|
+
if config_manager.get_dict("SITE_LOGIN", "mediasetinfinity")["beToken"] is None or config_manager.get_dict("SITE_LOGIN", "mediasetinfinity")["beToken"] == "":
|
|
53
|
+
console.print(f"[bold red] beToken is missing or empty.[/bold red]")
|
|
54
|
+
sys.exit(0)
|
|
55
|
+
|
|
56
|
+
search_url = 'https://api-ott-prod-fe.mediaset.net/PROD/play/reco/account/v2.0'
|
|
41
57
|
console.print(f"[cyan]Search url: [yellow]{search_url}")
|
|
42
58
|
|
|
43
59
|
params = {
|
|
@@ -48,11 +64,10 @@ def title_search(query: str) -> int:
|
|
|
48
64
|
'contentId': '',
|
|
49
65
|
'property': 'search',
|
|
50
66
|
'tenant': 'play-prod-v2',
|
|
51
|
-
'userContext': 'iwiAeyJwbGF0Zm9ybSI6IndlYiJ9Aw==',
|
|
52
67
|
'aresContext': '',
|
|
68
|
+
'clientId': 'client_id',
|
|
53
69
|
'page': '1',
|
|
54
70
|
'hitsPerPage': '8',
|
|
55
|
-
'clientId': 'client_id'
|
|
56
71
|
}
|
|
57
72
|
|
|
58
73
|
headers = get_headers()
|