StreamingCommunity 3.3.6__py3-none-any.whl → 3.3.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.
- StreamingCommunity/Api/Site/altadefinizione/film.py +1 -1
- StreamingCommunity/Api/Site/altadefinizione/series.py +1 -1
- StreamingCommunity/Api/Site/animeunity/serie.py +1 -1
- StreamingCommunity/Api/Site/animeworld/film.py +1 -1
- StreamingCommunity/Api/Site/animeworld/serie.py +1 -1
- 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 +149 -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 -129
- 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.7.dist-info}/METADATA +1 -1
- {streamingcommunity-3.3.6.dist-info → streamingcommunity-3.3.7.dist-info}/RECORD +48 -47
- {streamingcommunity-3.3.6.dist-info → streamingcommunity-3.3.7.dist-info}/WHEEL +0 -0
- {streamingcommunity-3.3.6.dist-info → streamingcommunity-3.3.7.dist-info}/entry_points.txt +0 -0
- {streamingcommunity-3.3.6.dist-info → streamingcommunity-3.3.7.dist-info}/licenses/LICENSE +0 -0
- {streamingcommunity-3.3.6.dist-info → streamingcommunity-3.3.7.dist-info}/top_level.txt +0 -0
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
# 16.03.25
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import re
|
|
4
|
+
import uuid
|
|
5
5
|
from urllib.parse import urlencode
|
|
6
6
|
import xml.etree.ElementTree as ET
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
# External library
|
|
10
10
|
import httpx
|
|
11
|
+
from bs4 import BeautifulSoup
|
|
11
12
|
from rich.console import Console
|
|
12
|
-
try:
|
|
13
|
-
from seleniumbase import Driver
|
|
14
|
-
SELENIUMBASE_AVAILABLE = True
|
|
15
|
-
except ImportError:
|
|
16
|
-
SELENIUMBASE_AVAILABLE = False
|
|
17
|
-
Driver = None
|
|
18
13
|
|
|
19
14
|
|
|
20
15
|
# Internal utilities
|
|
@@ -25,192 +20,91 @@ from StreamingCommunity.Util.headers import get_headers, get_userAgent
|
|
|
25
20
|
# Variable
|
|
26
21
|
console = Console()
|
|
27
22
|
MAX_TIMEOUT = config_manager.get_int("REQUESTS", "timeout")
|
|
28
|
-
beToken = None
|
|
29
23
|
network_data = []
|
|
24
|
+
class_mediaset_api = None
|
|
30
25
|
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
response = params.get('response', {})
|
|
40
|
-
url = response.get('url', '')
|
|
27
|
+
class MediasetAPI:
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.client_id = str(uuid.uuid4())
|
|
30
|
+
self.headers = get_headers()
|
|
31
|
+
self.app_name = self.get_app_name()
|
|
32
|
+
self.beToken = self.generate_betoken()
|
|
33
|
+
self.sha256Hash = self.getHash2c()
|
|
41
34
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def generate_betoken(username: str, password: str, sleep_action: float = 1.0) -> str:
|
|
48
|
-
"""Generate beToken using browser automation"""
|
|
49
|
-
|
|
50
|
-
if not SELENIUMBASE_AVAILABLE:
|
|
51
|
-
console.print("[red]Error: seleniumbase is not installed. Cannot perform browser login.")
|
|
52
|
-
console.print("[yellow]Install seleniumbase with: pip install seleniumbase")
|
|
53
|
-
return None
|
|
54
|
-
|
|
55
|
-
if not Driver:
|
|
56
|
-
console.print("[red]Error: seleniumbase Driver is not available.")
|
|
57
|
-
return None
|
|
58
|
-
|
|
59
|
-
driver = Driver(uc=True, uc_cdp_events=True, incognito=True, headless=True)
|
|
60
|
-
|
|
61
|
-
try:
|
|
62
|
-
console.print("[cyan]Launching browser...")
|
|
63
|
-
be_token_holder = {"token": None}
|
|
64
|
-
global network_data
|
|
65
|
-
network_data = [] # Reset network data
|
|
66
|
-
|
|
67
|
-
# Load home page
|
|
68
|
-
console.print("[cyan]Navigating to Mediaset Play...")
|
|
69
|
-
driver.uc_open_with_reconnect("https://www.mediasetplay.mediaset.it", sleep_action)
|
|
35
|
+
def get_app_name(self):
|
|
36
|
+
html = self.fetch_html()
|
|
37
|
+
soup = BeautifulSoup(html, "html.parser")
|
|
38
|
+
meta_tag = soup.find('meta', attrs={'name': 'app-name'})
|
|
70
39
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"Network.responseReceived",
|
|
74
|
-
lambda data: save_network_data(data)
|
|
75
|
-
)
|
|
76
|
-
driver.sleep(sleep_action)
|
|
77
|
-
|
|
78
|
-
# Accept privacy policy if present
|
|
79
|
-
try:
|
|
80
|
-
driver.click("#rti-privacy-accept-btn-screen1-id", timeout=3)
|
|
81
|
-
except Exception:
|
|
82
|
-
pass
|
|
83
|
-
|
|
84
|
-
# Click Login using the specific div structure
|
|
85
|
-
console.print("[cyan]Clicking login button...")
|
|
86
|
-
try:
|
|
87
|
-
driver.click('div.dwv_v span:contains("Login")', timeout=5)
|
|
88
|
-
except Exception as e:
|
|
89
|
-
print(f"Error clicking login: {e}")
|
|
90
|
-
try:
|
|
91
|
-
driver.click('div.dwv_v', timeout=3)
|
|
92
|
-
except Exception:
|
|
93
|
-
pass
|
|
94
|
-
|
|
95
|
-
driver.sleep(sleep_action)
|
|
96
|
-
|
|
97
|
-
# Click "Accedi con email e password" using the specific input
|
|
98
|
-
console.print("[cyan]Clicking email/password login...")
|
|
99
|
-
try:
|
|
100
|
-
driver.click('input.gigya-input-submit[value="Accedi con email e password"]', timeout=5)
|
|
101
|
-
except Exception as e:
|
|
102
|
-
print(f"Error clicking email login: {e}")
|
|
103
|
-
return None
|
|
104
|
-
|
|
105
|
-
driver.sleep(sleep_action)
|
|
106
|
-
|
|
107
|
-
# Fill login credentials using specific IDs
|
|
108
|
-
console.print("[cyan]Filling login credentials...")
|
|
109
|
-
try:
|
|
110
|
-
email_input = 'input[name="username"].gigya-input-text'
|
|
111
|
-
driver.wait_for_element(email_input, timeout=5)
|
|
112
|
-
driver.type(email_input, username)
|
|
113
|
-
|
|
114
|
-
password_input = 'input[name="password"].gigya-input-password'
|
|
115
|
-
driver.wait_for_element(password_input, timeout=5)
|
|
116
|
-
driver.type(password_input, password)
|
|
117
|
-
|
|
118
|
-
except Exception as e:
|
|
119
|
-
print(f"Error filling credentials: {e}")
|
|
120
|
-
return None
|
|
121
|
-
|
|
122
|
-
driver.sleep(sleep_action)
|
|
123
|
-
|
|
124
|
-
# Click Continue/Procedi using the submit button
|
|
125
|
-
console.print("[cyan]Clicking continue button...")
|
|
126
|
-
try:
|
|
127
|
-
driver.click('input.gigya-input-submit[type="submit"][value="Continua"]', timeout=5)
|
|
128
|
-
except Exception as e:
|
|
129
|
-
print(f"Error clicking continue: {e}")
|
|
130
|
-
return None
|
|
131
|
-
|
|
132
|
-
# Wait for login response and parse network data
|
|
133
|
-
console.print("[cyan]Waiting for login response...")
|
|
134
|
-
for attempt in range(30):
|
|
135
|
-
driver.sleep(0.3)
|
|
136
|
-
|
|
137
|
-
# Check network data for beToken - skip preflight requests
|
|
138
|
-
for data in network_data:
|
|
139
|
-
if data.get('method') == 'Network.responseReceived':
|
|
140
|
-
params = data.get('params', {})
|
|
141
|
-
response = params.get('response', {})
|
|
142
|
-
url = response.get('url', '')
|
|
143
|
-
request_type = params.get('type', '')
|
|
144
|
-
|
|
145
|
-
if "persona/login/v2.0" in url and request_type != 'Preflight':
|
|
146
|
-
request_id = params.get('requestId')
|
|
147
|
-
|
|
148
|
-
if request_id:
|
|
149
|
-
try:
|
|
150
|
-
response_body = driver.execute_cdp_cmd(
|
|
151
|
-
'Network.getResponseBody',
|
|
152
|
-
{'requestId': request_id}
|
|
153
|
-
)
|
|
154
|
-
body = response_body.get('body', '')
|
|
155
|
-
|
|
156
|
-
if body:
|
|
157
|
-
response_data = json.loads(body)
|
|
158
|
-
be_token = response_data.get("response", {}).get("beToken")
|
|
159
|
-
|
|
160
|
-
if be_token:
|
|
161
|
-
be_token_holder["token"] = be_token
|
|
162
|
-
console.print("[green]Login successful! BeToken found!")
|
|
163
|
-
return be_token
|
|
164
|
-
|
|
165
|
-
except Exception:
|
|
166
|
-
continue
|
|
167
|
-
|
|
168
|
-
console.print(f"[yellow]Login completed. Total network events captured: {len(network_data)}")
|
|
169
|
-
return be_token_holder["token"]
|
|
40
|
+
if meta_tag:
|
|
41
|
+
return meta_tag.get('content')
|
|
170
42
|
|
|
171
|
-
|
|
172
|
-
|
|
43
|
+
def getHash256(self):
|
|
44
|
+
return self.sha256Hash
|
|
45
|
+
|
|
46
|
+
def getBearerToken(self):
|
|
47
|
+
return self.beToken
|
|
48
|
+
|
|
49
|
+
def generate_betoken(self):
|
|
50
|
+
json_data = {
|
|
51
|
+
'appName': self.app_name,
|
|
52
|
+
'client_id': self.client_id,
|
|
53
|
+
}
|
|
54
|
+
response = httpx.post(
|
|
55
|
+
'https://api-ott-prod-fe.mediaset.net/PROD/play/idm/anonymous/login/v2.0',
|
|
56
|
+
headers=self.headers,
|
|
57
|
+
json=json_data,
|
|
58
|
+
)
|
|
59
|
+
return response.json()['response']['beToken']
|
|
60
|
+
|
|
61
|
+
def fetch_html(self, timeout=10):
|
|
62
|
+
r = httpx.get("https://mediasetinfinity.mediaset.it/", timeout=timeout, headers=self.headers)
|
|
63
|
+
r.raise_for_status()
|
|
64
|
+
return r.text
|
|
65
|
+
|
|
66
|
+
def find_relevant_script(self, html):
|
|
67
|
+
soup = BeautifulSoup(html, "html.parser")
|
|
68
|
+
return [s.get_text() for s in soup.find_all("script") if "imageEngines" in s.get_text()]
|
|
69
|
+
|
|
70
|
+
def extract_pairs_from_scripts(self, scripts):
|
|
71
|
+
# Chi ha inventato questo metodo di offuscare le chiavi merita di essere fustigato in piazza.
|
|
72
|
+
relevant_part = scripts[0].replace('\\"', '').split('...Option')[1].split('imageEngines')[0]
|
|
73
|
+
pairs = {}
|
|
74
|
+
for match in re.finditer(r'([a-f0-9]{64}):\$(\w+)', relevant_part):
|
|
75
|
+
pairs[match.group(1)] = f"${match.group(2)}"
|
|
76
|
+
return pairs
|
|
77
|
+
|
|
78
|
+
def getHash2c(self):
|
|
79
|
+
html = self.fetch_html()
|
|
80
|
+
scripts = self.find_relevant_script(html)[0:1]
|
|
81
|
+
pairs = self.extract_pairs_from_scripts(scripts)
|
|
82
|
+
return next((h for h, k in pairs.items() if k == "$2a"), None)
|
|
83
|
+
|
|
84
|
+
def generate_request_headers(self):
|
|
85
|
+
return {
|
|
86
|
+
'authorization': self.beToken,
|
|
87
|
+
'user-agent': self.headers['user-agent'],
|
|
88
|
+
'x-m-device-id': self.client_id,
|
|
89
|
+
'x-m-platform': 'WEB',
|
|
90
|
+
'x-m-property': 'MPLAY',
|
|
91
|
+
'x-m-sid': self.client_id
|
|
92
|
+
}
|
|
173
93
|
|
|
174
94
|
|
|
175
95
|
def get_bearer_token():
|
|
176
96
|
"""
|
|
177
97
|
Gets the BEARER_TOKEN for authentication.
|
|
178
|
-
|
|
179
|
-
Returns:
|
|
180
|
-
str: The bearer token string.
|
|
98
|
+
Anche i manifestanti per strada dio bellissimo.
|
|
181
99
|
"""
|
|
182
|
-
global
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if beToken is not None and len(beToken) != 0:
|
|
187
|
-
return beToken
|
|
188
|
-
|
|
189
|
-
username = config_manager.get_dict("SITE_LOGIN", "mediasetinfinity").get("username", "")
|
|
190
|
-
password = config_manager.get_dict("SITE_LOGIN", "mediasetinfinity").get("password", "")
|
|
191
|
-
|
|
192
|
-
if username and password:
|
|
193
|
-
if not SELENIUMBASE_AVAILABLE:
|
|
194
|
-
console.print("[yellow]Warning: seleniumbase not available. Cannot perform automatic login.")
|
|
195
|
-
console.print("[yellow]Please manually obtain beToken and set it in config.")
|
|
196
|
-
return config_manager.get_dict("SITE_LOGIN", "mediasetinfinity")["beToken"]
|
|
197
|
-
|
|
198
|
-
beToken = generate_betoken(username, password)
|
|
199
|
-
|
|
200
|
-
if beToken is not None:
|
|
201
|
-
|
|
202
|
-
# Save current beToken
|
|
203
|
-
current_value = config_manager.get("SITE_LOGIN", "mediasetinfinity", dict)
|
|
204
|
-
current_value["beToken"] = beToken
|
|
205
|
-
config_manager.set_key("SITE_LOGIN", "mediasetinfinity", current_value)
|
|
206
|
-
config_manager.save_config()
|
|
207
|
-
|
|
208
|
-
return beToken
|
|
100
|
+
global class_mediaset_api
|
|
101
|
+
if class_mediaset_api is None:
|
|
102
|
+
class_mediaset_api = MediasetAPI()
|
|
103
|
+
return class_mediaset_api
|
|
209
104
|
|
|
210
|
-
return config_manager.get_dict("SITE_LOGIN", "mediasetinfinity")["beToken"]
|
|
211
105
|
|
|
212
106
|
|
|
213
|
-
def get_playback_url(
|
|
107
|
+
def get_playback_url(CONTENT_ID):
|
|
214
108
|
"""
|
|
215
109
|
Gets the playback URL for the specified content.
|
|
216
110
|
|
|
@@ -222,7 +116,7 @@ def get_playback_url(BEARER_TOKEN, CONTENT_ID):
|
|
|
222
116
|
dict: The playback JSON object.
|
|
223
117
|
"""
|
|
224
118
|
headers = get_headers()
|
|
225
|
-
headers['authorization'] = f'Bearer {
|
|
119
|
+
headers['authorization'] = f'Bearer {class_mediaset_api.getBearerToken()}'
|
|
226
120
|
|
|
227
121
|
json_data = {
|
|
228
122
|
'contentId': CONTENT_ID,
|
|
@@ -254,75 +148,101 @@ def get_playback_url(BEARER_TOKEN, CONTENT_ID):
|
|
|
254
148
|
except Exception as e:
|
|
255
149
|
raise RuntimeError(f"Failed to get playback URL: {e}")
|
|
256
150
|
|
|
257
|
-
|
|
258
|
-
def parse_tracking_data(tracking_value):
|
|
151
|
+
def parse_smil_for_media_info(smil_xml):
|
|
259
152
|
"""
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
Args:
|
|
263
|
-
tracking_value (str): The tracking data string.
|
|
264
|
-
|
|
265
|
-
Returns:
|
|
266
|
-
dict: Parsed tracking data.
|
|
267
|
-
"""
|
|
268
|
-
return dict(item.split('=', 1) for item in tracking_value.split('|') if '=' in item)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def parse_smil_for_tracking_and_video(smil_xml):
|
|
272
|
-
"""
|
|
273
|
-
Extracts all video_src and trackingData pairs from the SMIL.
|
|
153
|
+
Extracts video streams with quality info and subtitle streams from SMIL.
|
|
274
154
|
|
|
275
155
|
Args:
|
|
276
156
|
smil_xml (str): The SMIL XML as a string.
|
|
277
157
|
|
|
278
158
|
Returns:
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
159
|
+
dict: {
|
|
160
|
+
'videos': [{'url': str, 'quality': str, 'clipBegin': str, 'clipEnd': str, 'tracking_data': dict}, ...],
|
|
161
|
+
'subtitles': [{'url': str, 'lang': str, 'type': str}, ...]
|
|
162
|
+
}
|
|
163
|
+
"""
|
|
282
164
|
root = ET.fromstring(smil_xml)
|
|
283
165
|
ns = {'smil': root.tag.split('}')[0].strip('{')}
|
|
284
|
-
|
|
285
|
-
|
|
166
|
+
|
|
167
|
+
videos = []
|
|
168
|
+
subtitles_raw = []
|
|
169
|
+
|
|
170
|
+
# Process all <par> elements
|
|
286
171
|
for par in root.findall('.//smil:par', ns):
|
|
287
|
-
video_src = None
|
|
288
|
-
tracking_info = None
|
|
289
|
-
|
|
290
|
-
# Search <video> inside <par>
|
|
291
|
-
video_elem = par.find('.//smil:video', ns)
|
|
292
|
-
if video_elem is not None:
|
|
293
|
-
video_src = video_elem.attrib.get('src')
|
|
294
172
|
|
|
295
|
-
#
|
|
173
|
+
# Extract video information from <ref>
|
|
296
174
|
ref_elem = par.find('.//smil:ref', ns)
|
|
297
175
|
if ref_elem is not None:
|
|
298
|
-
|
|
176
|
+
url = ref_elem.attrib.get('src')
|
|
177
|
+
title = ref_elem.attrib.get('title', '')
|
|
178
|
+
|
|
179
|
+
# Parse tracking data inline
|
|
180
|
+
tracking_data = {}
|
|
299
181
|
for param in ref_elem.findall('.//smil:param', ns):
|
|
300
182
|
if param.attrib.get('name') == 'trackingData':
|
|
301
|
-
tracking_value = param.attrib.get('value')
|
|
302
|
-
if
|
|
303
|
-
tracking_info = parse_tracking_data(tracking_value)
|
|
183
|
+
tracking_value = param.attrib.get('value', '')
|
|
184
|
+
tracking_data = dict(item.split('=', 1) for item in tracking_value.split('|') if '=' in item)
|
|
304
185
|
break
|
|
186
|
+
|
|
187
|
+
if url and url.endswith('.mpd'):
|
|
188
|
+
video_info = {
|
|
189
|
+
'url': url,
|
|
190
|
+
'title': title,
|
|
191
|
+
'tracking_data': tracking_data
|
|
192
|
+
}
|
|
193
|
+
videos.append(video_info)
|
|
194
|
+
|
|
195
|
+
# Extract subtitle information from <textstream>
|
|
196
|
+
for textstream in par.findall('.//smil:textstream', ns):
|
|
197
|
+
sub_url = textstream.attrib.get('src')
|
|
198
|
+
lang = textstream.attrib.get('lang', 'unknown')
|
|
199
|
+
sub_type = textstream.attrib.get('type', 'unknown')
|
|
200
|
+
|
|
201
|
+
if sub_url:
|
|
202
|
+
subtitle_info = {
|
|
203
|
+
'url': sub_url,
|
|
204
|
+
'language': lang,
|
|
205
|
+
'type': sub_type
|
|
206
|
+
}
|
|
207
|
+
subtitles_raw.append(subtitle_info)
|
|
208
|
+
|
|
209
|
+
# Filter subtitles: prefer VTT, fallback to SRT
|
|
210
|
+
subtitles_by_lang = {}
|
|
211
|
+
for sub in subtitles_raw:
|
|
212
|
+
lang = sub['language']
|
|
213
|
+
if lang not in subtitles_by_lang:
|
|
214
|
+
subtitles_by_lang[lang] = []
|
|
215
|
+
subtitles_by_lang[lang].append(sub)
|
|
216
|
+
|
|
217
|
+
subtitles = []
|
|
218
|
+
for lang, subs in subtitles_by_lang.items():
|
|
219
|
+
vtt_subs = [s for s in subs if s['type'] == 'text/vtt']
|
|
220
|
+
if vtt_subs:
|
|
221
|
+
subtitles.append(vtt_subs[0]) # Take first VTT
|
|
222
|
+
|
|
223
|
+
else:
|
|
224
|
+
srt_subs = [s for s in subs if s['type'] == 'text/srt']
|
|
225
|
+
if srt_subs:
|
|
226
|
+
subtitles.append(srt_subs[0]) # Take first SRT
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
'videos': videos,
|
|
230
|
+
'subtitles': subtitles
|
|
231
|
+
}
|
|
305
232
|
|
|
306
|
-
|
|
307
|
-
results.append({'video_src': video_src, 'tracking_info': tracking_info})
|
|
308
|
-
|
|
309
|
-
return results
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
def get_tracking_info(BEARER_TOKEN, PLAYBACK_JSON):
|
|
233
|
+
def get_tracking_info(PLAYBACK_JSON):
|
|
313
234
|
"""
|
|
314
|
-
Retrieves
|
|
235
|
+
Retrieves media information including videos and subtitles from the playback JSON.
|
|
315
236
|
|
|
316
237
|
Args:
|
|
317
|
-
BEARER_TOKEN (str): The authentication token.
|
|
318
238
|
PLAYBACK_JSON (dict): The playback JSON object.
|
|
319
239
|
|
|
320
240
|
Returns:
|
|
321
|
-
|
|
241
|
+
dict or None: {'videos': [...], 'subtitles': [...]}, or None if request fails.
|
|
322
242
|
"""
|
|
323
243
|
params = {
|
|
324
244
|
"format": "SMIL",
|
|
325
|
-
"auth":
|
|
245
|
+
"auth": class_mediaset_api.getBearerToken(),
|
|
326
246
|
"formats": "MPEG-DASH",
|
|
327
247
|
"assetTypes": "HR,browser,widevine,geoIT|geoNo:HR,browser,geoIT|geoNo:SD,browser,widevine,geoIT|geoNo:SD,browser,geoIT|geoNo:SS,browser,widevine,geoIT|geoNo:SS,browser,geoIT|geoNo",
|
|
328
248
|
"balance": "true",
|
|
@@ -344,31 +264,29 @@ def get_tracking_info(BEARER_TOKEN, PLAYBACK_JSON):
|
|
|
344
264
|
)
|
|
345
265
|
response.raise_for_status()
|
|
346
266
|
|
|
347
|
-
|
|
348
|
-
time.sleep(0.2)
|
|
349
|
-
results = parse_smil_for_tracking_and_video(smil_xml)
|
|
267
|
+
results = parse_smil_for_media_info(response.text)
|
|
350
268
|
return results
|
|
351
269
|
|
|
352
|
-
except Exception:
|
|
270
|
+
except Exception as e:
|
|
271
|
+
print(f"Error fetching tracking info: {e}")
|
|
353
272
|
return None
|
|
354
273
|
|
|
355
274
|
|
|
356
|
-
def generate_license_url(
|
|
275
|
+
def generate_license_url(tracking_info):
|
|
357
276
|
"""
|
|
358
277
|
Generates the URL to obtain the Widevine license.
|
|
359
278
|
|
|
360
279
|
Args:
|
|
361
|
-
BEARER_TOKEN (str): The authentication token.
|
|
362
280
|
tracking_info (dict): The tracking info dictionary.
|
|
363
281
|
|
|
364
282
|
Returns:
|
|
365
283
|
str: The full license URL.
|
|
366
284
|
"""
|
|
367
285
|
params = {
|
|
368
|
-
'releasePid': tracking_info['
|
|
369
|
-
'account': f"http://access.auth.theplatform.com/data/Account/{tracking_info['
|
|
286
|
+
'releasePid': tracking_info['tracking_data'].get('pid'),
|
|
287
|
+
'account': f"http://access.auth.theplatform.com/data/Account/{tracking_info['tracking_data'].get('aid')}",
|
|
370
288
|
'schema': '1.0',
|
|
371
|
-
'token':
|
|
289
|
+
'token': class_mediaset_api.getBearerToken(),
|
|
372
290
|
}
|
|
373
291
|
|
|
374
292
|
return f"{'https://widevine.entitlement.theplatform.eu/wv/web/ModularDrm/getRawWidevineLicense'}?{urlencode(params)}"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# 21.05.24
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
import sys
|
|
5
4
|
from typing import Tuple
|
|
6
5
|
|
|
7
6
|
|
|
@@ -44,7 +43,7 @@ def download_film(select_title: MediaItem) -> Tuple[str, bool]:
|
|
|
44
43
|
- bool: Whether download was stopped
|
|
45
44
|
"""
|
|
46
45
|
start_message()
|
|
47
|
-
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
46
|
+
console.print(f"\n[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
48
47
|
|
|
49
48
|
# Extract m3u8 URL from the film's URL
|
|
50
49
|
response = httpx.get(select_title.url + ".json", headers=get_headers(), timeout=10)
|
|
@@ -64,17 +63,10 @@ def download_film(select_title: MediaItem) -> Tuple[str, bool]:
|
|
|
64
63
|
|
|
65
64
|
# MPD
|
|
66
65
|
else:
|
|
67
|
-
|
|
68
|
-
# Check CDM file before usage
|
|
69
|
-
cdm_device_path = get_wvd_path()
|
|
70
|
-
if not cdm_device_path or not isinstance(cdm_device_path, (str, bytes, os.PathLike)) or not os.path.isfile(cdm_device_path):
|
|
71
|
-
console.print(f"[bold red] CDM file not found or invalid path: {cdm_device_path}[/bold red]")
|
|
72
|
-
sys.exit(0)
|
|
73
|
-
|
|
74
66
|
license_url = generate_license_url(select_title.mpd_id)
|
|
75
67
|
|
|
76
68
|
dash_process = DASH_Downloader(
|
|
77
|
-
cdm_device=
|
|
69
|
+
cdm_device=get_wvd_path(),
|
|
78
70
|
license_url=license_url,
|
|
79
71
|
mpd_url=master_playlist,
|
|
80
72
|
output_path=os.path.join(mp4_path, mp4_name),
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# 21.05.24
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
import sys
|
|
5
4
|
from typing import Tuple
|
|
6
5
|
|
|
7
6
|
|
|
@@ -58,7 +57,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
58
57
|
|
|
59
58
|
# Get episode information
|
|
60
59
|
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] → [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
|
+
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")
|
|
62
61
|
|
|
63
62
|
# Define filename and path
|
|
64
63
|
mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4"
|
|
@@ -85,13 +84,6 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
85
84
|
|
|
86
85
|
# MPD
|
|
87
86
|
else:
|
|
88
|
-
|
|
89
|
-
# Check CDM file before usage
|
|
90
|
-
cdm_device_path = get_wvd_path()
|
|
91
|
-
if not cdm_device_path or not isinstance(cdm_device_path, (str, bytes, os.PathLike)) or not os.path.isfile(cdm_device_path):
|
|
92
|
-
console.print(f"[bold red] CDM file not found or invalid path: {cdm_device_path}[/bold red]")
|
|
93
|
-
sys.exit(0)
|
|
94
|
-
|
|
95
87
|
full_license_url = generate_license_url(obj_episode.mpd_id)
|
|
96
88
|
license_headers = {
|
|
97
89
|
'nv-authorizations': full_license_url.split("?")[1].split("=")[1],
|
|
@@ -99,7 +91,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
99
91
|
}
|
|
100
92
|
|
|
101
93
|
dash_process = DASH_Downloader(
|
|
102
|
-
cdm_device=
|
|
94
|
+
cdm_device=get_wvd_path(),
|
|
103
95
|
license_url=full_license_url.split("?")[0],
|
|
104
96
|
mpd_url=master_playlist,
|
|
105
97
|
output_path=os.path.join(mp4_path, mp4_name),
|
|
@@ -40,6 +40,7 @@ def determine_media_type(item):
|
|
|
40
40
|
if not program_name:
|
|
41
41
|
return "film"
|
|
42
42
|
|
|
43
|
+
# Dio stranamente guarda che giro bisogna fare per avere il tipo di media.
|
|
43
44
|
scraper = GetSerieInfo(program_name)
|
|
44
45
|
scraper.collect_info_title()
|
|
45
46
|
return scraper.prog_tipology, scraper.prog_description, scraper.prog_year
|
|
@@ -41,7 +41,12 @@ class GetSerieInfo:
|
|
|
41
41
|
|
|
42
42
|
response.raise_for_status()
|
|
43
43
|
json_data = response.json()
|
|
44
|
-
|
|
44
|
+
|
|
45
|
+
# Dio santissimo ma chi ha fatto le cose cosi di merda.
|
|
46
|
+
type_check_1 = "tv" if json_data.get('program_info', {}).get('layout', 'single') == 'multi' else "film"
|
|
47
|
+
#type_check_2 = "tv" if "tv" in json_data.get('track_info', {}).get('typology', '') else "film"
|
|
48
|
+
|
|
49
|
+
self.prog_tipology = type_check_1
|
|
45
50
|
self.prog_description = json_data.get('program_info', '').get('vanity', '')
|
|
46
51
|
self.prog_year = json_data.get('program_info', '').get('year', '')
|
|
47
52
|
|
|
@@ -82,6 +87,7 @@ class GetSerieInfo:
|
|
|
82
87
|
try:
|
|
83
88
|
season = self.seasons_manager.get_season_by_number(number_season)
|
|
84
89
|
|
|
90
|
+
# Se stai leggendo questo codice spieami perche hai fatto cosi.
|
|
85
91
|
url = f"{self.base_url}/programmi/{self.program_name}/{self.publishing_block_id}/{season.id}/episodes.json"
|
|
86
92
|
response = httpx.get(url=url, headers=get_headers(), timeout=max_timeout)
|
|
87
93
|
response.raise_for_status()
|
|
@@ -52,7 +52,7 @@ def download_film(select_title: MediaItem) -> str:
|
|
|
52
52
|
|
|
53
53
|
# Start message and display film information
|
|
54
54
|
start_message()
|
|
55
|
-
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
55
|
+
console.print(f"\n[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
56
56
|
|
|
57
57
|
# Init class
|
|
58
58
|
video_source = VideoSource(f"{site_constant.FULL_URL}/it", False, select_title.id)
|
|
@@ -55,7 +55,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
55
55
|
|
|
56
56
|
# Get episode information
|
|
57
57
|
obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
|
|
58
|
-
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")
|
|
58
|
+
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")
|
|
59
59
|
|
|
60
60
|
if site_constant.TELEGRAM_BOT:
|
|
61
61
|
bot = get_bot_instance()
|
|
@@ -38,7 +38,7 @@ def download_film(select_title: MediaItem) -> str:
|
|
|
38
38
|
- str: output path
|
|
39
39
|
"""
|
|
40
40
|
start_message()
|
|
41
|
-
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
41
|
+
console.print(f"\n[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
42
42
|
|
|
43
43
|
# Get master playlists
|
|
44
44
|
video_source = VideoSource()
|
|
@@ -53,7 +53,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
53
53
|
|
|
54
54
|
# Get episode information
|
|
55
55
|
obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
|
|
56
|
-
console.print(f"[
|
|
56
|
+
console.print(f"\n[/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
57
|
|
|
58
58
|
# Define filename and path for the downloaded video
|
|
59
59
|
mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4"
|