StreamingCommunity 3.2.7__py3-none-any.whl → 3.2.9__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 +2 -2
- 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 +2 -2
- StreamingCommunity/Api/Site/animeunity/serie.py +2 -2
- StreamingCommunity/Api/Site/animeworld/site.py +3 -5
- StreamingCommunity/Api/Site/animeworld/util/ScrapeSerie.py +8 -10
- StreamingCommunity/Api/Site/cb01new/film.py +7 -5
- StreamingCommunity/Api/Site/crunchyroll/__init__.py +1 -3
- StreamingCommunity/Api/Site/crunchyroll/film.py +9 -7
- StreamingCommunity/Api/Site/crunchyroll/series.py +9 -7
- StreamingCommunity/Api/Site/crunchyroll/site.py +10 -1
- 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 +1 -1
- StreamingCommunity/Api/Site/mediasetinfinity/film.py +10 -16
- StreamingCommunity/Api/Site/mediasetinfinity/series.py +12 -18
- StreamingCommunity/Api/Site/mediasetinfinity/site.py +11 -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 +1 -1
- StreamingCommunity/Api/Site/raiplay/film.py +41 -10
- StreamingCommunity/Api/Site/raiplay/series.py +44 -12
- 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 +0 -1
- StreamingCommunity/Api/Site/streamingcommunity/film.py +7 -5
- StreamingCommunity/Api/Site/streamingcommunity/series.py +9 -7
- StreamingCommunity/Api/Site/streamingcommunity/site.py +4 -2
- 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 +5 -12
- StreamingCommunity/Lib/Downloader/DASH/decrypt.py +1 -1
- StreamingCommunity/Lib/Downloader/DASH/downloader.py +1 -1
- 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 +253 -144
- StreamingCommunity/Lib/Downloader/MP4/downloader.py +4 -3
- 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/version.py +2 -2
- StreamingCommunity/Util/config_json.py +139 -59
- StreamingCommunity/Util/http_client.py +201 -0
- StreamingCommunity/Util/message.py +1 -1
- StreamingCommunity/Util/os.py +5 -5
- StreamingCommunity/Util/table.py +3 -3
- StreamingCommunity/__init__.py +9 -1
- StreamingCommunity/run.py +396 -260
- {streamingcommunity-3.2.7.dist-info → streamingcommunity-3.2.9.dist-info}/METADATA +143 -45
- streamingcommunity-3.2.9.dist-info/RECORD +113 -0
- streamingcommunity-3.2.7.dist-info/RECORD +0 -111
- {streamingcommunity-3.2.7.dist-info → streamingcommunity-3.2.9.dist-info}/WHEEL +0 -0
- {streamingcommunity-3.2.7.dist-info → streamingcommunity-3.2.9.dist-info}/entry_points.txt +0 -0
- {streamingcommunity-3.2.7.dist-info → streamingcommunity-3.2.9.dist-info}/licenses/LICENSE +0 -0
- {streamingcommunity-3.2.7.dist-info → streamingcommunity-3.2.9.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# 25.07.25
|
|
2
2
|
|
|
3
|
+
import os
|
|
4
|
+
|
|
3
5
|
# External libraries
|
|
4
6
|
import httpx
|
|
5
7
|
from rich.console import Console
|
|
@@ -7,6 +9,7 @@ from rich.console import Console
|
|
|
7
9
|
|
|
8
10
|
# Internal utilities
|
|
9
11
|
from StreamingCommunity.Util.config_json import config_manager
|
|
12
|
+
from StreamingCommunity.Util.os import get_wvd_path
|
|
10
13
|
from StreamingCommunity.Util.headers import get_headers
|
|
11
14
|
from StreamingCommunity.Util.table import TVShowManager
|
|
12
15
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
@@ -37,7 +40,13 @@ def title_search(query: str) -> int:
|
|
|
37
40
|
media_search_manager.clear()
|
|
38
41
|
table_show_manager.clear()
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
# Check CDM file before usage
|
|
44
|
+
cdm_device_path = get_wvd_path()
|
|
45
|
+
if not cdm_device_path or not isinstance(cdm_device_path, (str, bytes, os.PathLike)) or not os.path.isfile(cdm_device_path):
|
|
46
|
+
console.print(f"[bold red] CDM file not found or invalid path: {cdm_device_path}[/bold red]")
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
search_url = 'https://api-ott-prod-fe.mediaset.net/PROD/play/reco/account/v2.0'
|
|
41
50
|
console.print(f"[cyan]Search url: [yellow]{search_url}")
|
|
42
51
|
|
|
43
52
|
params = {
|
|
@@ -48,11 +57,10 @@ def title_search(query: str) -> int:
|
|
|
48
57
|
'contentId': '',
|
|
49
58
|
'property': 'search',
|
|
50
59
|
'tenant': 'play-prod-v2',
|
|
51
|
-
'userContext': 'iwiAeyJwbGF0Zm9ybSI6IndlYiJ9Aw==',
|
|
52
60
|
'aresContext': '',
|
|
61
|
+
'clientId': 'client_id',
|
|
53
62
|
'page': '1',
|
|
54
63
|
'hitsPerPage': '8',
|
|
55
|
-
'clientId': 'client_id'
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
headers = get_headers()
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# 16.03.25
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
import logging
|
|
5
|
+
from urllib.parse import urlparse
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
# External libraries
|
|
8
|
-
import
|
|
9
|
+
from curl_cffi import requests
|
|
9
10
|
from bs4 import BeautifulSoup
|
|
10
11
|
|
|
11
12
|
|
|
@@ -16,7 +17,7 @@ from StreamingCommunity.Api.Player.Helper.Vixcloud.util import SeasonManager
|
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
# Logic class
|
|
19
|
-
from .get_license import get_bearer_token
|
|
20
|
+
from .get_license import get_bearer_token
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
# Variable
|
|
@@ -34,197 +35,230 @@ class GetSerieInfo:
|
|
|
34
35
|
self.headers = get_headers()
|
|
35
36
|
self.url = url
|
|
36
37
|
self.seasons_manager = SeasonManager()
|
|
37
|
-
self.
|
|
38
|
-
self.
|
|
39
|
-
self.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
""
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return match.group(1)
|
|
56
|
-
|
|
57
|
-
return None
|
|
58
|
-
|
|
59
|
-
def _find_video_href_and_id(self, soup):
|
|
60
|
-
"""
|
|
61
|
-
Search for the first <a> with href containing '/video/' and return (current_url, id_media).
|
|
62
|
-
Always builds the absolute URL.
|
|
63
|
-
"""
|
|
64
|
-
for a_tag in soup.find_all("a", href=True):
|
|
65
|
-
href = a_tag["href"]
|
|
66
|
-
if "/video/" in href:
|
|
67
|
-
if href.startswith("http"):
|
|
68
|
-
current_url = href
|
|
69
|
-
else:
|
|
70
|
-
current_url = "https://mediasetinfinity.mediaset.it" + href
|
|
71
|
-
|
|
72
|
-
bearer = get_bearer_token()
|
|
73
|
-
playback_json = get_playback_url(bearer, current_url.split('_')[-1])
|
|
74
|
-
id_media = str(playback_json['url']).split("/s/")[1].split("/")[0]
|
|
75
|
-
|
|
76
|
-
return current_url, id_media
|
|
77
|
-
return None, None
|
|
78
|
-
|
|
79
|
-
def _parse_entries(self, entries, single_season=False):
|
|
80
|
-
"""
|
|
81
|
-
Populate seasons and episodes from the JSON entries.
|
|
82
|
-
If single_season=True, creates only one season and adds all episodes there.
|
|
83
|
-
"""
|
|
84
|
-
if not entries:
|
|
85
|
-
self.series_name = ""
|
|
86
|
-
return
|
|
87
|
-
|
|
88
|
-
self.series_name = entries[0].get("mediasetprogram$auditelBrandName", "")
|
|
89
|
-
|
|
90
|
-
if single_season:
|
|
91
|
-
logging.info("Single season mode enabled.")
|
|
92
|
-
season_num = 1
|
|
93
|
-
season_name = "Stagione 1"
|
|
94
|
-
current_season = self.seasons_manager.add_season({
|
|
95
|
-
'number': season_num,
|
|
96
|
-
'name': season_name
|
|
97
|
-
})
|
|
38
|
+
self.serie_id = None
|
|
39
|
+
self.public_id = None
|
|
40
|
+
self.series_name = ""
|
|
41
|
+
self.stagioni_disponibili = []
|
|
42
|
+
|
|
43
|
+
def _extract_serie_id(self):
|
|
44
|
+
"""Estrae l'ID della serie dall'URL di partenza"""
|
|
45
|
+
self.serie_id = f"SE{self.url.split('SE')[1]}"
|
|
46
|
+
print(f"Serie ID: {self.serie_id}")
|
|
47
|
+
return self.serie_id
|
|
48
|
+
|
|
49
|
+
def _get_public_id(self):
|
|
50
|
+
"""Ottiene il public ID tramite l'API watchlist"""
|
|
51
|
+
bearer_token = get_bearer_token()
|
|
52
|
+
headers = {
|
|
53
|
+
'authorization': f'Bearer {bearer_token}',
|
|
54
|
+
'user-agent': get_userAgent(),
|
|
55
|
+
}
|
|
98
56
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
57
|
+
response = requests.get(
|
|
58
|
+
'https://api-ott-prod-fe.mediaset.net/PROD/play/userlist/watchlist/v2.0',
|
|
59
|
+
headers=headers,
|
|
60
|
+
impersonate="chrome",
|
|
61
|
+
allow_redirects=True
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if response.status_code == 401:
|
|
65
|
+
print("Token scaduto, rinnovare il token")
|
|
66
|
+
|
|
67
|
+
if response.status_code == 200:
|
|
68
|
+
data = response.json()
|
|
69
|
+
self.public_id = data['response']['entries'][0]['media'][0]['publicUrl'].split("/")[4]
|
|
70
|
+
print(f"Public id: {self.public_id}")
|
|
71
|
+
return self.public_id
|
|
72
|
+
|
|
115
73
|
else:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
logging.info("Multi season mode")
|
|
119
|
-
for entry in entries:
|
|
120
|
-
|
|
121
|
-
# Use JSON fields directly instead of regex
|
|
122
|
-
season_num = entry.get("tvSeasonNumber")
|
|
123
|
-
ep_num = entry.get("tvSeasonEpisodeNumber")
|
|
124
|
-
|
|
125
|
-
# Extract numbers from title if season_num or ep_num are None
|
|
126
|
-
if season_num is None or ep_num is None:
|
|
127
|
-
title = entry.get("title", "")
|
|
128
|
-
|
|
129
|
-
# Find all numbers in the title
|
|
130
|
-
numbers = [int(n) for n in re.findall(r"\d+", title)]
|
|
131
|
-
if len(numbers) == 2:
|
|
132
|
-
season_num, ep_num = numbers
|
|
74
|
+
logging.error(f"Failed to get public ID: {response.status_code}")
|
|
75
|
+
return None
|
|
133
76
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
77
|
+
def _get_series_data(self):
|
|
78
|
+
"""Ottiene i dati della serie tramite l'API"""
|
|
79
|
+
headers = {
|
|
80
|
+
'User-Agent': get_userAgent(),
|
|
81
|
+
}
|
|
82
|
+
params = {'byGuid': self.serie_id}
|
|
83
|
+
|
|
84
|
+
response = requests.get(
|
|
85
|
+
f'https://feed.entertainment.tv.theplatform.eu/f/{self.public_id}/mediaset-prod-all-series-v2',
|
|
86
|
+
params=params,
|
|
87
|
+
headers=headers,
|
|
88
|
+
impersonate="chrome",
|
|
89
|
+
allow_redirects=True
|
|
90
|
+
)
|
|
91
|
+
print("Risposta per _get_series_data:", response.status_code)
|
|
92
|
+
|
|
93
|
+
if response.status_code == 200:
|
|
94
|
+
return response.json()
|
|
95
|
+
else:
|
|
96
|
+
logging.error(f"Failed to get series data: {response.status_code}")
|
|
97
|
+
return None
|
|
137
98
|
|
|
138
|
-
|
|
139
|
-
|
|
99
|
+
def _process_available_seasons(self, data):
|
|
100
|
+
"""Processa le stagioni disponibili dai dati della serie"""
|
|
101
|
+
if not data or not data.get('entries'):
|
|
102
|
+
logging.error("No series data found")
|
|
103
|
+
return []
|
|
140
104
|
|
|
141
|
-
|
|
105
|
+
entry = data['entries'][0]
|
|
106
|
+
self.series_name = entry.get('title', '')
|
|
107
|
+
|
|
108
|
+
seriesTvSeasons = entry.get('seriesTvSeasons', [])
|
|
109
|
+
availableTvSeasonIds = entry.get('availableTvSeasonIds', [])
|
|
110
|
+
|
|
111
|
+
stagioni_disponibili = []
|
|
112
|
+
|
|
113
|
+
for url in availableTvSeasonIds:
|
|
114
|
+
season = next((s for s in seriesTvSeasons if s['id'] == url), None)
|
|
115
|
+
if season:
|
|
116
|
+
stagioni_disponibili.append({
|
|
117
|
+
'tvSeasonNumber': season['tvSeasonNumber'],
|
|
118
|
+
'url': url,
|
|
119
|
+
'id': str(url).split("/")[-1],
|
|
120
|
+
'guid': season['guid']
|
|
121
|
+
})
|
|
122
|
+
else:
|
|
123
|
+
logging.warning(f"Season URL not found: {url}")
|
|
142
124
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
125
|
+
# Ordina le stagioni dalla più vecchia alla più nuova
|
|
126
|
+
stagioni_disponibili.sort(key=lambda s: s['tvSeasonNumber'])
|
|
127
|
+
|
|
128
|
+
return stagioni_disponibili
|
|
129
|
+
|
|
130
|
+
def _build_season_page_urls(self, stagioni_disponibili):
|
|
131
|
+
"""Costruisce gli URL delle pagine delle stagioni"""
|
|
132
|
+
parsed_url = urlparse(self.url)
|
|
133
|
+
base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
|
|
134
|
+
series_slug = parsed_url.path.strip('/').split('/')[-1].split('_')[0]
|
|
135
|
+
|
|
136
|
+
for season in stagioni_disponibili:
|
|
137
|
+
page_url = f"{base_url}/fiction/{series_slug}/{series_slug}{season['tvSeasonNumber']}_{self.serie_id},{season['guid']}"
|
|
138
|
+
season['page_url'] = page_url
|
|
139
|
+
|
|
140
|
+
def _extract_season_sb_ids(self, stagioni_disponibili):
|
|
141
|
+
"""Estrae gli ID sb dalle pagine delle stagioni"""
|
|
142
|
+
for season in stagioni_disponibili:
|
|
143
|
+
response_page = requests.get(
|
|
144
|
+
season['page_url'],
|
|
145
|
+
headers={'User-Agent': get_userAgent()},
|
|
146
|
+
impersonate="chrome",
|
|
147
|
+
allow_redirects=True
|
|
148
|
+
)
|
|
149
|
+
print("Risposta per _extract_season_sb_ids:", response_page.status_code)
|
|
150
|
+
|
|
151
|
+
soup = BeautifulSoup(response_page.text, 'html.parser')
|
|
152
|
+
|
|
153
|
+
# Prova prima con 'Episodi', poi con 'Puntate intere'
|
|
154
|
+
link = soup.find('a', string='Episodi')
|
|
155
|
+
if not link:
|
|
156
|
+
print("Using word: Puntate intere")
|
|
157
|
+
link = soup.find('a', string='Puntate intere')
|
|
158
|
+
|
|
159
|
+
if link and link.has_attr('href'):
|
|
160
|
+
if not link.string == 'Puntate intere':
|
|
161
|
+
print("Using word: Episodi")
|
|
162
|
+
season['sb'] = link['href'].split(',')[-1]
|
|
163
|
+
else:
|
|
164
|
+
logging.warning(f"Link 'Episodi' o 'Puntate intere' non trovato per stagione {season['tvSeasonNumber']}")
|
|
166
165
|
|
|
167
|
-
def
|
|
168
|
-
"""
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
response = httpx.get(self.url, headers=self.headers, follow_redirects=True, timeout=max_timeout)
|
|
172
|
-
soup = BeautifulSoup(response.text, "html.parser")
|
|
173
|
-
|
|
174
|
-
# Find current_url and id_media from the first <a> with /video/
|
|
175
|
-
self.current_url, found_id_media = self._find_video_href_and_id(soup)
|
|
176
|
-
if found_id_media:
|
|
177
|
-
self.id_media = found_id_media
|
|
178
|
-
|
|
179
|
-
self.subBrandId = self._extract_subbrand_id(soup)
|
|
180
|
-
single_season = False
|
|
181
|
-
if self.subBrandId is None:
|
|
182
|
-
episodi_link = None
|
|
183
|
-
for h2_tag in soup.find_all("h2", class_=True):
|
|
184
|
-
a_tag = h2_tag.find("a", href=True)
|
|
185
|
-
if a_tag and "/episodi_" in a_tag["href"]:
|
|
186
|
-
episodi_link = a_tag["href"]
|
|
187
|
-
break
|
|
188
|
-
|
|
189
|
-
if episodi_link:
|
|
190
|
-
match = re.search(r"sb(\d+)", episodi_link)
|
|
191
|
-
if match:
|
|
192
|
-
self.subBrandId = match.group(1)
|
|
193
|
-
|
|
194
|
-
single_season = True
|
|
166
|
+
def _get_season_episodes(self, season):
|
|
167
|
+
"""Ottiene gli episodi per una stagione specifica"""
|
|
168
|
+
if not season.get('sb'):
|
|
169
|
+
return
|
|
195
170
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
puntate_link = href
|
|
202
|
-
break
|
|
203
|
-
|
|
204
|
-
if puntate_link:
|
|
205
|
-
match = re.search(r"sb(\d+)", puntate_link)
|
|
206
|
-
if match:
|
|
207
|
-
self.subBrandId = match.group(1)
|
|
208
|
-
|
|
209
|
-
single_season = True
|
|
210
|
-
else:
|
|
211
|
-
print("No /episodi_ or puntateintere link found.")
|
|
212
|
-
|
|
213
|
-
# Step 2: JSON request
|
|
171
|
+
episode_headers = {
|
|
172
|
+
'origin': 'https://mediasetinfinity.mediaset.it',
|
|
173
|
+
'referer': 'https://mediasetinfinity.mediaset.it/',
|
|
174
|
+
'user-agent': get_userAgent(),
|
|
175
|
+
}
|
|
214
176
|
params = {
|
|
215
|
-
'byCustomValue': "{subBrandId}{" + str(
|
|
177
|
+
'byCustomValue': "{subBrandId}{" + str(season["sb"].replace('sb', '')) + "}",
|
|
216
178
|
'sort': ':publishInfo_lastPublished|asc,tvSeasonEpisodeNumber|asc',
|
|
217
179
|
'range': '0-100',
|
|
218
180
|
}
|
|
181
|
+
episode_url = f"https://feed.entertainment.tv.theplatform.eu/f/{self.public_id}/mediaset-prod-all-programs-v2"
|
|
219
182
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
183
|
+
episode_response = requests.get(episode_url, headers=episode_headers, params=params, impersonate="chrome"
|
|
184
|
+
, allow_redirects=True)
|
|
185
|
+
print("Risposta per _get_season_episodes:", episode_response.status_code)
|
|
186
|
+
|
|
187
|
+
if episode_response.status_code == 200:
|
|
188
|
+
episode_data = episode_response.json()
|
|
189
|
+
season['episodes'] = []
|
|
190
|
+
|
|
191
|
+
for entry in episode_data.get('entries', []):
|
|
192
|
+
episode_info = {
|
|
193
|
+
'id': entry.get('guid'),
|
|
194
|
+
'title': entry.get('title'),
|
|
195
|
+
'duration': int(entry.get('mediasetprogram$duration', 0) / 60) if entry.get('mediasetprogram$duration') else 0,
|
|
196
|
+
'url': entry.get('media', [{}])[0].get('publicUrl') if entry.get('media') else None
|
|
197
|
+
}
|
|
198
|
+
season['episodes'].append(episode_info)
|
|
199
|
+
|
|
200
|
+
print(f"Found {len(season['episodes'])} episodes for season {season['tvSeasonNumber']}")
|
|
201
|
+
else:
|
|
202
|
+
logging.error(f"Failed to get episodes for season {season['tvSeasonNumber']}: {episode_response.status_code}")
|
|
225
203
|
|
|
226
|
-
|
|
227
|
-
|
|
204
|
+
def collect_season(self) -> None:
|
|
205
|
+
"""
|
|
206
|
+
Retrieve all episodes for all seasons using the new Mediaset Infinity API.
|
|
207
|
+
"""
|
|
208
|
+
try:
|
|
209
|
+
# Step 1: Extract serie ID from URL
|
|
210
|
+
self._extract_serie_id()
|
|
211
|
+
|
|
212
|
+
# Step 2: Get public ID
|
|
213
|
+
if not self._get_public_id():
|
|
214
|
+
logging.error("Failed to get public ID")
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
# Step 3: Get series data
|
|
218
|
+
data = self._get_series_data()
|
|
219
|
+
if not data:
|
|
220
|
+
logging.error("Failed to get series data")
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
# Step 4: Process available seasons
|
|
224
|
+
self.stagioni_disponibili = self._process_available_seasons(data)
|
|
225
|
+
if not self.stagioni_disponibili:
|
|
226
|
+
logging.error("No seasons found")
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
# Step 5: Build season page URLs
|
|
230
|
+
self._build_season_page_urls(self.stagioni_disponibili)
|
|
231
|
+
|
|
232
|
+
# Step 6: Extract sb IDs from season pages
|
|
233
|
+
self._extract_season_sb_ids(self.stagioni_disponibili)
|
|
234
|
+
|
|
235
|
+
# Step 7: Get episodes for each season
|
|
236
|
+
for season in self.stagioni_disponibili:
|
|
237
|
+
self._get_season_episodes(season)
|
|
238
|
+
|
|
239
|
+
# Step 8: Populate seasons manager
|
|
240
|
+
self._populate_seasons_manager()
|
|
241
|
+
|
|
242
|
+
except Exception as e:
|
|
243
|
+
logging.error(f"Error in collect_season: {str(e)}")
|
|
244
|
+
|
|
245
|
+
def _populate_seasons_manager(self):
|
|
246
|
+
"""Popola il seasons_manager con i dati raccolti"""
|
|
247
|
+
for season_data in self.stagioni_disponibili:
|
|
248
|
+
season_obj = self.seasons_manager.add_season({
|
|
249
|
+
'number': season_data['tvSeasonNumber'],
|
|
250
|
+
'name': f"Stagione {season_data['tvSeasonNumber']}"
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
if season_obj and season_data.get('episodes'):
|
|
254
|
+
for idx, episode in enumerate(season_data['episodes'], 1):
|
|
255
|
+
season_obj.episodes.add({
|
|
256
|
+
'id': episode['id'],
|
|
257
|
+
'number': idx,
|
|
258
|
+
'name': episode['title'],
|
|
259
|
+
'url': episode['url'],
|
|
260
|
+
'duration': episode['duration']
|
|
261
|
+
})
|
|
228
262
|
|
|
229
263
|
# ------------- FOR GUI -------------
|
|
230
264
|
def getNumberSeason(self) -> int:
|
|
@@ -15,8 +15,6 @@ from StreamingCommunity.Util.headers import get_headers, get_userAgent
|
|
|
15
15
|
|
|
16
16
|
# Variable
|
|
17
17
|
MAX_TIMEOUT = config_manager.get_int("REQUESTS", "timeout")
|
|
18
|
-
bearer_token = None
|
|
19
|
-
playback_json = None
|
|
20
18
|
|
|
21
19
|
|
|
22
20
|
def get_bearer_token():
|
|
@@ -26,29 +24,7 @@ def get_bearer_token():
|
|
|
26
24
|
Returns:
|
|
27
25
|
str: The bearer token string.
|
|
28
26
|
"""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if bearer_token:
|
|
32
|
-
return bearer_token
|
|
33
|
-
|
|
34
|
-
LOGIN_URL = "https://api-ott-prod-fe.mediaset.net/PROD/play/idm/anonymous/login/v2.0"
|
|
35
|
-
|
|
36
|
-
try:
|
|
37
|
-
response = httpx.post(
|
|
38
|
-
LOGIN_URL,
|
|
39
|
-
json={'client_id': 'client_id', 'appName': 'embed//mediasetplay-embed'},
|
|
40
|
-
follow_redirects=True,
|
|
41
|
-
timeout=MAX_TIMEOUT
|
|
42
|
-
)
|
|
43
|
-
response.raise_for_status()
|
|
44
|
-
|
|
45
|
-
# Extract the bearer token from the response
|
|
46
|
-
data = response.json()
|
|
47
|
-
bearer_token = data["response"]["beToken"]
|
|
48
|
-
return bearer_token
|
|
49
|
-
|
|
50
|
-
except Exception as e:
|
|
51
|
-
raise RuntimeError(f"Failed to get bearer token: {e}")
|
|
27
|
+
return config_manager.get_dict("SITE_LOGIN", "mediasetinfinity")["beToken"]
|
|
52
28
|
|
|
53
29
|
def get_playback_url(BEARER_TOKEN, CONTENT_ID):
|
|
54
30
|
"""
|
|
@@ -61,11 +37,6 @@ def get_playback_url(BEARER_TOKEN, CONTENT_ID):
|
|
|
61
37
|
Returns:
|
|
62
38
|
dict: The playback JSON object.
|
|
63
39
|
"""
|
|
64
|
-
global playback_json
|
|
65
|
-
|
|
66
|
-
if playback_json is not None:
|
|
67
|
-
return playback_json
|
|
68
|
-
|
|
69
40
|
headers = get_headers()
|
|
70
41
|
headers['authorization'] = f'Bearer {BEARER_TOKEN}'
|
|
71
42
|
|
|
@@ -190,7 +161,7 @@ def get_tracking_info(BEARER_TOKEN, PLAYBACK_JSON):
|
|
|
190
161
|
results = parse_smil_for_tracking_and_video(smil_xml)
|
|
191
162
|
return results
|
|
192
163
|
|
|
193
|
-
except Exception
|
|
164
|
+
except Exception:
|
|
194
165
|
return None
|
|
195
166
|
|
|
196
167
|
def generate_license_url(BEARER_TOKEN, tracking_info):
|
|
@@ -12,16 +12,18 @@ from rich.console import Console
|
|
|
12
12
|
# Internal utilities
|
|
13
13
|
from StreamingCommunity.Util.os import os_manager
|
|
14
14
|
from StreamingCommunity.Util.headers import get_headers
|
|
15
|
+
from StreamingCommunity.Util.os import get_wvd_path
|
|
15
16
|
from StreamingCommunity.Util.message import start_message
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
# Logic class
|
|
20
|
+
from .util.get_license import generate_license_url
|
|
19
21
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
20
22
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
# Player
|
|
24
|
-
from StreamingCommunity import HLS_Downloader
|
|
26
|
+
from StreamingCommunity import HLS_Downloader, DASH_Downloader
|
|
25
27
|
from StreamingCommunity.Api.Player.mediapolisvod import VideoSource
|
|
26
28
|
|
|
27
29
|
|
|
@@ -49,17 +51,46 @@ def download_film(select_title: MediaItem) -> Tuple[str, bool]:
|
|
|
49
51
|
master_playlist = VideoSource.extract_m3u8_url(first_item_path)
|
|
50
52
|
|
|
51
53
|
# Define the filename and path for the downloaded film
|
|
52
|
-
|
|
53
|
-
mp4_path = os.path.join(site_constant.MOVIE_FOLDER,
|
|
54
|
+
mp4_name = os_manager.get_sanitize_file(select_title.name) + ".mp4"
|
|
55
|
+
mp4_path = os.path.join(site_constant.MOVIE_FOLDER, mp4_name.replace(".mp4", ""))
|
|
56
|
+
|
|
57
|
+
# HLS
|
|
58
|
+
if ".mpd" not in master_playlist:
|
|
59
|
+
r_proc = HLS_Downloader(
|
|
60
|
+
m3u8_url=master_playlist,
|
|
61
|
+
output_path=os.path.join(mp4_path, mp4_name)
|
|
62
|
+
).start()
|
|
63
|
+
|
|
64
|
+
# MPD
|
|
65
|
+
else:
|
|
66
|
+
|
|
67
|
+
# Check CDM file before usage
|
|
68
|
+
cdm_device_path = get_wvd_path()
|
|
69
|
+
if not cdm_device_path or not isinstance(cdm_device_path, (str, bytes, os.PathLike)) or not os.path.isfile(cdm_device_path):
|
|
70
|
+
console.print(f"[bold red] CDM file not found or invalid path: {cdm_device_path}[/bold red]")
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
license_url = generate_license_url(select_title.mpd_id)
|
|
74
|
+
|
|
75
|
+
dash_process = DASH_Downloader(
|
|
76
|
+
cdm_device=cdm_device_path,
|
|
77
|
+
license_url=license_url,
|
|
78
|
+
mpd_url=master_playlist,
|
|
79
|
+
output_path=os.path.join(mp4_path, mp4_name),
|
|
80
|
+
)
|
|
81
|
+
dash_process.parse_manifest(custom_headers=get_headers())
|
|
82
|
+
|
|
83
|
+
if dash_process.download_and_decrypt():
|
|
84
|
+
dash_process.finalize_output()
|
|
85
|
+
|
|
86
|
+
# Get final output path and status
|
|
87
|
+
r_proc = dash_process.get_status()
|
|
54
88
|
|
|
55
|
-
# Download the film using the m3u8 playlist, and output filename
|
|
56
|
-
r_proc = HLS_Downloader(
|
|
57
|
-
m3u8_url=master_playlist,
|
|
58
|
-
output_path=os.path.join(mp4_path, title_name)
|
|
59
|
-
).start()
|
|
60
89
|
|
|
61
90
|
if r_proc['error'] is not None:
|
|
62
|
-
try:
|
|
63
|
-
|
|
91
|
+
try:
|
|
92
|
+
os.remove(r_proc['path'])
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
64
95
|
|
|
65
96
|
return r_proc['path'], r_proc['stopped']
|