StreamingCommunity 3.3.6__py3-none-any.whl → 3.3.8__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/film.py +1 -1
- StreamingCommunity/Api/Site/altadefinizione/series.py +1 -1
- StreamingCommunity/Api/Site/animeunity/serie.py +2 -2
- StreamingCommunity/Api/Site/animeworld/film.py +1 -1
- StreamingCommunity/Api/Site/animeworld/serie.py +2 -2
- StreamingCommunity/Api/Site/crunchyroll/film.py +3 -2
- StreamingCommunity/Api/Site/crunchyroll/series.py +3 -2
- StreamingCommunity/Api/Site/crunchyroll/site.py +0 -8
- StreamingCommunity/Api/Site/crunchyroll/util/get_license.py +11 -105
- StreamingCommunity/Api/Site/guardaserie/series.py +1 -1
- StreamingCommunity/Api/Site/mediasetinfinity/film.py +1 -1
- StreamingCommunity/Api/Site/mediasetinfinity/series.py +7 -9
- StreamingCommunity/Api/Site/mediasetinfinity/site.py +29 -66
- StreamingCommunity/Api/Site/mediasetinfinity/util/ScrapeSerie.py +5 -1
- StreamingCommunity/Api/Site/mediasetinfinity/util/get_license.py +151 -233
- StreamingCommunity/Api/Site/raiplay/film.py +2 -10
- StreamingCommunity/Api/Site/raiplay/series.py +2 -10
- StreamingCommunity/Api/Site/raiplay/site.py +1 -0
- StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py +7 -1
- StreamingCommunity/Api/Site/streamingcommunity/film.py +1 -1
- StreamingCommunity/Api/Site/streamingcommunity/series.py +1 -1
- StreamingCommunity/Api/Site/streamingwatch/film.py +1 -1
- StreamingCommunity/Api/Site/streamingwatch/series.py +1 -1
- StreamingCommunity/Api/Template/loader.py +158 -0
- StreamingCommunity/Lib/Downloader/DASH/downloader.py +267 -51
- StreamingCommunity/Lib/Downloader/DASH/segments.py +46 -15
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +51 -36
- StreamingCommunity/Lib/Downloader/HLS/segments.py +105 -25
- StreamingCommunity/Lib/Downloader/MP4/downloader.py +12 -13
- StreamingCommunity/Lib/FFmpeg/command.py +18 -81
- StreamingCommunity/Lib/FFmpeg/util.py +14 -10
- StreamingCommunity/Lib/M3U8/estimator.py +13 -12
- StreamingCommunity/Lib/M3U8/parser.py +16 -16
- StreamingCommunity/Upload/update.py +2 -4
- StreamingCommunity/Upload/version.py +2 -2
- StreamingCommunity/Util/config_json.py +3 -132
- StreamingCommunity/Util/installer/bento4_install.py +21 -31
- StreamingCommunity/Util/installer/device_install.py +0 -1
- StreamingCommunity/Util/installer/ffmpeg_install.py +0 -1
- StreamingCommunity/Util/message.py +8 -9
- StreamingCommunity/Util/os.py +0 -8
- StreamingCommunity/run.py +4 -44
- {streamingcommunity-3.3.6.dist-info → streamingcommunity-3.3.8.dist-info}/METADATA +1 -3
- {streamingcommunity-3.3.6.dist-info → streamingcommunity-3.3.8.dist-info}/RECORD +48 -47
- {streamingcommunity-3.3.6.dist-info → streamingcommunity-3.3.8.dist-info}/WHEEL +0 -0
- {streamingcommunity-3.3.6.dist-info → streamingcommunity-3.3.8.dist-info}/entry_points.txt +0 -0
- {streamingcommunity-3.3.6.dist-info → streamingcommunity-3.3.8.dist-info}/licenses/LICENSE +0 -0
- {streamingcommunity-3.3.6.dist-info → streamingcommunity-3.3.8.dist-info}/top_level.txt +0 -0
|
@@ -56,7 +56,7 @@ def download_film(select_title: MediaItem) -> str:
|
|
|
56
56
|
TelegramSession.updateScriptId(script_id, select_title.name)
|
|
57
57
|
|
|
58
58
|
start_message()
|
|
59
|
-
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
59
|
+
console.print(f"\n[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
60
60
|
|
|
61
61
|
# Extract mostraguarda URL
|
|
62
62
|
try:
|
|
@@ -54,7 +54,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
54
54
|
|
|
55
55
|
# Get episode information
|
|
56
56
|
obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
|
|
57
|
-
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")
|
|
57
|
+
console.print(f"\n[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")
|
|
58
58
|
|
|
59
59
|
# Telegram integration
|
|
60
60
|
if site_constant.TELEGRAM_BOT:
|
|
@@ -48,7 +48,7 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so
|
|
|
48
48
|
|
|
49
49
|
# Get episode information
|
|
50
50
|
obj_episode = scrape_serie.selectEpisode(1, index_select)
|
|
51
|
-
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{scrape_serie.series_name}[/cyan] ([cyan]E{obj_episode.number}[/cyan]) \n")
|
|
51
|
+
console.print(f"\n[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{scrape_serie.series_name}[/cyan] ([cyan]E{obj_episode.number}[/cyan]) \n")
|
|
52
52
|
|
|
53
53
|
if site_constant.TELEGRAM_BOT:
|
|
54
54
|
bot = get_bot_instance()
|
|
@@ -104,7 +104,7 @@ def download_series(select_title: MediaItem, season_selection: str = None, episo
|
|
|
104
104
|
|
|
105
105
|
# Get episode information
|
|
106
106
|
episoded_count = scrape_serie.get_count_episodes()
|
|
107
|
-
console.print(f"[green]Episodes count:[/green] [red]{episoded_count}[/red]")
|
|
107
|
+
console.print(f"\n[green]Episodes count:[/green] [red]{episoded_count}[/red]")
|
|
108
108
|
|
|
109
109
|
# Telegram bot integration
|
|
110
110
|
if episode_selection is None:
|
|
@@ -41,7 +41,7 @@ def download_film(select_title: MediaItem):
|
|
|
41
41
|
|
|
42
42
|
# Get episode information
|
|
43
43
|
episode_data = episodes[0]
|
|
44
|
-
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] ([cyan]{scrape_serie.get_name()}[/cyan]) \n")
|
|
44
|
+
console.print(f"\n[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] ([cyan]{scrape_serie.get_name()}[/cyan]) \n")
|
|
45
45
|
|
|
46
46
|
# Define filename and path for the downloaded video
|
|
47
47
|
mp4_name = f"{scrape_serie.get_name()}.mp4"
|
|
@@ -47,7 +47,7 @@ def download_episode(index_select: int, scrape_serie: ScrapSerie) -> Tuple[str,b
|
|
|
47
47
|
|
|
48
48
|
# Get episode information
|
|
49
49
|
episode_data = scrape_serie.selectEpisode(1, index_select)
|
|
50
|
-
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{scrape_serie.get_name()}[/cyan] ([cyan]E{str(index_select+1)}[/cyan]) \n")
|
|
50
|
+
console.print(f"\n[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{scrape_serie.get_name()}[/cyan] ([cyan]E{str(index_select+1)}[/cyan]) \n")
|
|
51
51
|
|
|
52
52
|
# Define filename and path for the downloaded video
|
|
53
53
|
mp4_name = f"{scrape_serie.get_name()}_EP_{dynamic_format_number(str(index_select+1))}.mp4"
|
|
@@ -84,7 +84,7 @@ def download_series(select_title: MediaItem, episode_selection: str = None):
|
|
|
84
84
|
episodes = scrape_serie.get_episodes()
|
|
85
85
|
|
|
86
86
|
# Get episode count
|
|
87
|
-
console.print(f"[green]Episodes
|
|
87
|
+
console.print(f"\n[green]Episodes count:[/green] [red]{len(episodes)}[/red]")
|
|
88
88
|
|
|
89
89
|
# Display episodes list and get user selection
|
|
90
90
|
if episode_selection is None:
|
|
@@ -40,7 +40,7 @@ def download_film(select_title: MediaItem) -> str:
|
|
|
40
40
|
- str: output path if successful, otherwise None
|
|
41
41
|
"""
|
|
42
42
|
start_message()
|
|
43
|
-
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
43
|
+
console.print(f"\n[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
44
44
|
|
|
45
45
|
# Define filename and path for the downloaded video
|
|
46
46
|
mp4_name = os_manager.get_sanitize_file(select_title.name) + ".mp4"
|
|
@@ -49,7 +49,7 @@ def download_film(select_title: MediaItem) -> str:
|
|
|
49
49
|
# Generate mpd and license URLs
|
|
50
50
|
url_id = select_title.get('url').split('/')[-1]
|
|
51
51
|
device_id = generate_device_id()
|
|
52
|
-
mpd_url, mpd_headers = get_playback_session(get_auth_token(device_id), device_id, url_id)
|
|
52
|
+
mpd_url, mpd_headers, mpd_list_sub = get_playback_session(get_auth_token(device_id), device_id, url_id)
|
|
53
53
|
parsed_url = urlparse(mpd_url)
|
|
54
54
|
query_params = parse_qs(parsed_url.query)
|
|
55
55
|
|
|
@@ -58,6 +58,7 @@ def download_film(select_title: MediaItem) -> str:
|
|
|
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
|
+
mpd_sub_list=mpd_list_sub,
|
|
61
62
|
output_path=os.path.join(mp4_path, mp4_name),
|
|
62
63
|
)
|
|
63
64
|
dash_process.parse_manifest(custom_headers=mpd_headers)
|
|
@@ -56,7 +56,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
56
56
|
# Get episode information
|
|
57
57
|
obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
|
|
58
58
|
|
|
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.get('name')}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
|
|
59
|
+
console.print(f"\n[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{scrape_serie.series_name}[/cyan] \\ [bold magenta]{obj_episode.get('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.get('name'))}.mp4"
|
|
@@ -67,7 +67,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
67
67
|
device_id = generate_device_id()
|
|
68
68
|
token_mpd = get_auth_token(device_id)
|
|
69
69
|
|
|
70
|
-
mpd_url, mpd_headers = get_playback_session(token_mpd, device_id, url_id)
|
|
70
|
+
mpd_url, mpd_headers, mpd_list_sub = get_playback_session(token_mpd, device_id, url_id)
|
|
71
71
|
parsed_url = urlparse(mpd_url)
|
|
72
72
|
query_params = parse_qs(parsed_url.query)
|
|
73
73
|
|
|
@@ -76,6 +76,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
76
76
|
cdm_device=get_wvd_path(),
|
|
77
77
|
license_url='https://www.crunchyroll.com/license/v1/license/widevine',
|
|
78
78
|
mpd_url=mpd_url,
|
|
79
|
+
mpd_sub_list=mpd_list_sub,
|
|
79
80
|
output_path=os.path.join(mp4_path, mp4_name),
|
|
80
81
|
)
|
|
81
82
|
dash_process.parse_manifest(custom_headers=mpd_headers)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# 16.03.25
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
3
|
import sys
|
|
5
4
|
|
|
6
5
|
|
|
@@ -11,7 +10,6 @@ from rich.console import Console
|
|
|
11
10
|
|
|
12
11
|
# Internal utilities
|
|
13
12
|
from StreamingCommunity.Util.config_json import config_manager
|
|
14
|
-
from StreamingCommunity.Util.os import get_wvd_path
|
|
15
13
|
from StreamingCommunity.Util.headers import get_headers
|
|
16
14
|
from StreamingCommunity.Util.table import TVShowManager
|
|
17
15
|
|
|
@@ -42,12 +40,6 @@ def title_search(query: str) -> int:
|
|
|
42
40
|
media_search_manager.clear()
|
|
43
41
|
table_show_manager.clear()
|
|
44
42
|
|
|
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
43
|
# Check if x_cr_tab_id or etp_rt is present
|
|
52
44
|
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
45
|
console.print("[bold red] x_cr_tab_id or etp_rt is missing or empty.[/bold red]")
|
|
@@ -11,7 +11,7 @@ from curl_cffi.requests import Session
|
|
|
11
11
|
|
|
12
12
|
# Internal utilities
|
|
13
13
|
from StreamingCommunity.Util.config_json import config_manager
|
|
14
|
-
from StreamingCommunity.Util.headers import get_userAgent
|
|
14
|
+
from StreamingCommunity.Util.headers import get_userAgent
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
# Variable
|
|
@@ -34,22 +34,6 @@ class Token:
|
|
|
34
34
|
extra: Dict[str, Any] = field(default_factory=dict)
|
|
35
35
|
|
|
36
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
37
|
|
|
54
38
|
def generate_device_id():
|
|
55
39
|
global device_id
|
|
@@ -102,95 +86,9 @@ def get_auth_token(device_id):
|
|
|
102
86
|
)
|
|
103
87
|
|
|
104
88
|
|
|
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
89
|
def get_playback_session(token: Token, device_id: str, url_id: str):
|
|
192
90
|
"""
|
|
193
|
-
Crea una sessione per ottenere i dati di playback da Crunchyroll.
|
|
91
|
+
Crea una sessione per ottenere i dati di playback e sottotitoli da Crunchyroll.
|
|
194
92
|
"""
|
|
195
93
|
cookies = {
|
|
196
94
|
'device_id': device_id,
|
|
@@ -224,4 +122,12 @@ def get_playback_session(token: Token, device_id: str, url_id: str):
|
|
|
224
122
|
raise Exception("Playback is Rejected: Premium required")
|
|
225
123
|
|
|
226
124
|
url = data.get('url')
|
|
227
|
-
|
|
125
|
+
|
|
126
|
+
subtitles = []
|
|
127
|
+
if 'subtitles' in data:
|
|
128
|
+
subtitles = [
|
|
129
|
+
{'language': lang, 'url': info['url'], 'format': info.get('format')}
|
|
130
|
+
for lang, info in data['subtitles'].items()
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
return url, headers, subtitles
|
|
@@ -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] → [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")
|
|
58
|
+
console.print(f"\n[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"
|
|
@@ -40,7 +40,7 @@ def download_film(select_title: MediaItem) -> Tuple[str, bool]:
|
|
|
40
40
|
- str: output path if successful, otherwise None
|
|
41
41
|
"""
|
|
42
42
|
start_message()
|
|
43
|
-
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
43
|
+
console.print(f"\n[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
44
44
|
|
|
45
45
|
# Define the filename and path for the downloaded film
|
|
46
46
|
title_name = os_manager.get_sanitize_file(select_title.name) + ".mp4"
|
|
@@ -31,7 +31,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
|
31
31
|
# Player
|
|
32
32
|
from .util.fix_mpd import get_manifest
|
|
33
33
|
from StreamingCommunity import DASH_Downloader
|
|
34
|
-
from .util.get_license import
|
|
34
|
+
from .util.get_license import get_playback_url, get_tracking_info, generate_license_url
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
# Variable
|
|
@@ -56,26 +56,24 @@ 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] → [cyan]{scrape_serie.series_name}[/cyan] \\ [bold magenta]{obj_episode.name}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
|
|
59
|
+
console.print(f"\n[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"
|
|
63
63
|
mp4_path = os_manager.get_sanitize_path(os.path.join(site_constant.SERIES_FOLDER, scrape_serie.series_name, f"S{index_season_selected}"))
|
|
64
64
|
|
|
65
65
|
# Generate mpd and license URLs
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
license_url = generate_license_url(bearer, tracking_info)
|
|
72
|
-
mpd_url = get_manifest(tracking_info['video_src'])
|
|
66
|
+
playback_json = get_playback_url(obj_episode.id)
|
|
67
|
+
tracking_info = get_tracking_info(playback_json)
|
|
68
|
+
license_url = generate_license_url(tracking_info['videos'][0])
|
|
69
|
+
mpd_url = get_manifest(tracking_info['videos'][0]['url'])
|
|
73
70
|
|
|
74
71
|
# Download the episode
|
|
75
72
|
dash_process = DASH_Downloader(
|
|
76
73
|
cdm_device=get_wvd_path(),
|
|
77
74
|
license_url=license_url,
|
|
78
75
|
mpd_url=mpd_url,
|
|
76
|
+
mpd_sub_list=tracking_info['subtitles'],
|
|
79
77
|
output_path=os.path.join(mp4_path, mp4_name),
|
|
80
78
|
)
|
|
81
79
|
dash_process.parse_manifest(custom_headers=get_headers())
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# 25.07.25
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
|
-
import sys
|
|
5
3
|
from datetime import datetime
|
|
6
4
|
|
|
7
5
|
|
|
@@ -12,8 +10,6 @@ from rich.console import Console
|
|
|
12
10
|
|
|
13
11
|
# Internal utilities
|
|
14
12
|
from StreamingCommunity.Util.config_json import config_manager
|
|
15
|
-
from StreamingCommunity.Util.os import get_wvd_path
|
|
16
|
-
from StreamingCommunity.Util.headers import get_headers
|
|
17
13
|
from StreamingCommunity.Util.table import TVShowManager
|
|
18
14
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
19
15
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
|
|
@@ -42,44 +38,18 @@ def title_search(query: str) -> int:
|
|
|
42
38
|
"""
|
|
43
39
|
media_search_manager.clear()
|
|
44
40
|
table_show_manager.clear()
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
cdm_device_path = get_wvd_path()
|
|
48
|
-
if not cdm_device_path or not isinstance(cdm_device_path, (str, bytes, os.PathLike)) or not os.path.isfile(cdm_device_path):
|
|
49
|
-
console.print(f"[bold red] CDM file not found or invalid path: {cdm_device_path}[/bold red]")
|
|
50
|
-
sys.exit(0)
|
|
51
|
-
|
|
52
|
-
# Check if beToken is present
|
|
53
|
-
if (config_manager.get_dict("SITE_LOGIN", "mediasetinfinity")["beToken"] is None or config_manager.get_dict("SITE_LOGIN", "mediasetinfinity")["beToken"] == "") and \
|
|
54
|
-
(config_manager.get_dict("SITE_LOGIN", "mediasetinfinity").get("username") is None or config_manager.get_dict("SITE_LOGIN", "mediasetinfinity").get("username") == "" \
|
|
55
|
-
or config_manager.get_dict("SITE_LOGIN", "mediasetinfinity").get("password") is None or config_manager.get_dict("SITE_LOGIN", "mediasetinfinity").get("password") == ""):
|
|
56
|
-
console.print("[bold red] beToken or credentials are missing or empty.[/bold red]")
|
|
57
|
-
sys.exit(0)
|
|
58
|
-
|
|
59
|
-
search_url = 'https://api-ott-prod-fe.mediaset.net/PROD/play/reco/account/v2.0'
|
|
41
|
+
class_mediaset_api = get_bearer_token()
|
|
42
|
+
search_url = 'https://mediasetplay.api-graph.mediaset.it/'
|
|
60
43
|
console.print(f"[cyan]Search url: [yellow]{search_url}")
|
|
61
44
|
|
|
62
45
|
params = {
|
|
63
|
-
'
|
|
64
|
-
'
|
|
65
|
-
'query': query.strip(),
|
|
66
|
-
'params': 'channel≈;variant≈',
|
|
67
|
-
'contentId': '',
|
|
68
|
-
'property': 'search',
|
|
69
|
-
'tenant': 'play-prod-v2',
|
|
70
|
-
'aresContext': '',
|
|
71
|
-
'clientId': 'client_id',
|
|
72
|
-
'page': '1',
|
|
73
|
-
'hitsPerPage': '8',
|
|
46
|
+
'extensions': f'{{"persistedQuery":{{"version":1,"sha256Hash":"{class_mediaset_api.getHash256()}"}}}}',
|
|
47
|
+
'variables': f'{{"first":10,"property":"search","query":"{query}","uxReference":"filteredSearch"}}',
|
|
74
48
|
}
|
|
75
|
-
|
|
76
|
-
headers = get_headers()
|
|
77
|
-
headers['authorization'] = f'Bearer {get_bearer_token()}'
|
|
78
|
-
|
|
79
49
|
try:
|
|
80
50
|
response = httpx.get(
|
|
81
51
|
search_url,
|
|
82
|
-
headers=
|
|
52
|
+
headers=class_mediaset_api.generate_request_headers(),
|
|
83
53
|
params=params,
|
|
84
54
|
timeout=max_timeout,
|
|
85
55
|
follow_redirects=True
|
|
@@ -92,32 +62,25 @@ def title_search(query: str) -> int:
|
|
|
92
62
|
|
|
93
63
|
# Parse response
|
|
94
64
|
resp_json = response.json()
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
for block in blocks:
|
|
98
|
-
if 'items' in block:
|
|
99
|
-
items.extend(block['items'])
|
|
100
|
-
elif 'results' in block and 'items' in block['results']:
|
|
101
|
-
items.extend(block['results']['items'])
|
|
102
|
-
|
|
65
|
+
items = resp_json.get("data", {}).get("getSearchPage", {}).get("areaContainersConnection", {}).get("areaContainers", [])[0].get("areas", [])[0].get("sections", [])[0].get("collections", [])[0].get("itemsConnection", {}).get("items", [])
|
|
66
|
+
|
|
103
67
|
# Process items
|
|
104
68
|
for item in items:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
page_url = f"https:{page_url}"
|
|
69
|
+
item_type = "tv" if item.get("__typename") == "SeriesItem" else "film"
|
|
70
|
+
|
|
71
|
+
# Bastava un campo data ma no ...
|
|
72
|
+
date = item.get("year")
|
|
73
|
+
if not date:
|
|
74
|
+
updated = item.get("updated")
|
|
75
|
+
if updated:
|
|
76
|
+
try:
|
|
77
|
+
date = datetime.fromisoformat(updated.replace("Z", "+00:00")).year
|
|
78
|
+
except Exception:
|
|
79
|
+
try:
|
|
80
|
+
timestamp_ms = int(updated)
|
|
81
|
+
date = datetime.fromtimestamp(timestamp_ms / 1000).year
|
|
82
|
+
except Exception:
|
|
83
|
+
date = ""
|
|
121
84
|
|
|
122
85
|
date = item.get('year', '')
|
|
123
86
|
if not date and item.get('updated'):
|
|
@@ -129,12 +92,12 @@ def title_search(query: str) -> int:
|
|
|
129
92
|
date = ''
|
|
130
93
|
|
|
131
94
|
media_search_manager.add_media({
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
95
|
+
"url": item.get("cardLink", "").get("value", ""),
|
|
96
|
+
"id": item.get("guid", ""),
|
|
97
|
+
"name": item.get("cardTitle", "No Title"),
|
|
98
|
+
"type": item_type,
|
|
99
|
+
"image": None,
|
|
100
|
+
"date": date,
|
|
138
101
|
})
|
|
139
102
|
|
|
140
|
-
|
|
103
|
+
return media_search_manager.get_length()
|
|
@@ -112,8 +112,8 @@ class GetSerieInfo:
|
|
|
112
112
|
season['page_url'],
|
|
113
113
|
headers={'User-Agent': get_userAgent()}
|
|
114
114
|
)
|
|
115
|
+
|
|
115
116
|
print("Response for _extract_season_sb_ids:", response_page.status_code, " season index:", season['tvSeasonNumber'])
|
|
116
|
-
|
|
117
117
|
soup = BeautifulSoup(response_page.text, 'html.parser')
|
|
118
118
|
|
|
119
119
|
# Try first with 'Episodi', then with 'Puntate intere'
|
|
@@ -121,10 +121,14 @@ class GetSerieInfo:
|
|
|
121
121
|
if not link:
|
|
122
122
|
#print("Using word: Puntate intere")
|
|
123
123
|
link = soup.find('a', string='Puntate intere')
|
|
124
|
+
|
|
125
|
+
if link is None:
|
|
126
|
+
link = soup.find('a', class_ = 'titleCarousel')
|
|
124
127
|
|
|
125
128
|
if link and link.has_attr('href'):
|
|
126
129
|
if not link.string == 'Puntate intere':
|
|
127
130
|
print("Using word: Episodi")
|
|
131
|
+
|
|
128
132
|
season['sb'] = link['href'].split(',')[-1]
|
|
129
133
|
else:
|
|
130
134
|
logging.warning(f"Link 'Episodi' or 'Puntate intere' not found for season {season['tvSeasonNumber']}")
|