StreamingCommunity 3.3.8__py3-none-any.whl → 3.4.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/hdplayer.py +0 -5
- StreamingCommunity/Api/Player/mediapolisvod.py +4 -13
- StreamingCommunity/Api/Player/supervideo.py +3 -8
- StreamingCommunity/Api/Player/sweetpixel.py +1 -9
- StreamingCommunity/Api/Player/vixcloud.py +5 -16
- StreamingCommunity/Api/Site/altadefinizione/film.py +4 -15
- StreamingCommunity/Api/Site/altadefinizione/site.py +2 -7
- StreamingCommunity/Api/Site/altadefinizione/util/ScrapeSerie.py +2 -7
- StreamingCommunity/Api/Site/animeunity/site.py +9 -24
- StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py +11 -27
- StreamingCommunity/Api/Site/animeworld/film.py +4 -2
- StreamingCommunity/Api/Site/animeworld/site.py +3 -11
- StreamingCommunity/Api/Site/animeworld/util/ScrapeSerie.py +1 -4
- StreamingCommunity/Api/Site/crunchyroll/film.py +17 -8
- StreamingCommunity/Api/Site/crunchyroll/series.py +8 -9
- StreamingCommunity/Api/Site/crunchyroll/site.py +14 -16
- StreamingCommunity/Api/Site/crunchyroll/util/ScrapeSerie.py +18 -65
- StreamingCommunity/Api/Site/crunchyroll/util/get_license.py +97 -106
- StreamingCommunity/Api/Site/guardaserie/site.py +4 -12
- StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +3 -10
- StreamingCommunity/Api/Site/mediasetinfinity/film.py +11 -12
- StreamingCommunity/Api/Site/mediasetinfinity/series.py +1 -2
- StreamingCommunity/Api/Site/mediasetinfinity/site.py +3 -11
- StreamingCommunity/Api/Site/mediasetinfinity/util/ScrapeSerie.py +39 -50
- StreamingCommunity/Api/Site/mediasetinfinity/util/fix_mpd.py +3 -3
- StreamingCommunity/Api/Site/mediasetinfinity/util/get_license.py +8 -26
- StreamingCommunity/Api/Site/raiplay/film.py +6 -7
- StreamingCommunity/Api/Site/raiplay/series.py +1 -12
- StreamingCommunity/Api/Site/raiplay/site.py +8 -24
- StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py +15 -22
- StreamingCommunity/Api/Site/raiplay/util/get_license.py +3 -12
- StreamingCommunity/Api/Site/streamingcommunity/film.py +5 -16
- StreamingCommunity/Api/Site/streamingcommunity/site.py +3 -22
- StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +11 -26
- StreamingCommunity/Api/Site/streamingwatch/__init__.py +1 -0
- StreamingCommunity/Api/Site/streamingwatch/film.py +4 -2
- StreamingCommunity/Api/Site/streamingwatch/series.py +1 -1
- StreamingCommunity/Api/Site/streamingwatch/site.py +4 -18
- StreamingCommunity/Api/Site/streamingwatch/util/ScrapeSerie.py +0 -3
- StreamingCommunity/Api/Template/config_loader.py +0 -7
- StreamingCommunity/Lib/Downloader/DASH/cdm_helpher.py +8 -3
- StreamingCommunity/Lib/Downloader/DASH/decrypt.py +55 -1
- StreamingCommunity/Lib/Downloader/DASH/downloader.py +139 -55
- StreamingCommunity/Lib/Downloader/DASH/parser.py +458 -101
- StreamingCommunity/Lib/Downloader/DASH/segments.py +131 -74
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +31 -50
- StreamingCommunity/Lib/Downloader/HLS/segments.py +266 -365
- StreamingCommunity/Lib/Downloader/MP4/downloader.py +1 -1
- StreamingCommunity/Lib/FFmpeg/capture.py +37 -5
- StreamingCommunity/Lib/FFmpeg/command.py +35 -93
- StreamingCommunity/Lib/M3U8/estimator.py +0 -1
- StreamingCommunity/Lib/TMBD/tmdb.py +2 -4
- StreamingCommunity/TelegramHelp/config.json +0 -1
- StreamingCommunity/Upload/version.py +1 -1
- StreamingCommunity/Util/config_json.py +28 -21
- StreamingCommunity/Util/http_client.py +28 -0
- StreamingCommunity/Util/os.py +16 -6
- {streamingcommunity-3.3.8.dist-info → streamingcommunity-3.4.0.dist-info}/METADATA +1 -3
- streamingcommunity-3.4.0.dist-info/RECORD +111 -0
- streamingcommunity-3.3.8.dist-info/RECORD +0 -111
- {streamingcommunity-3.3.8.dist-info → streamingcommunity-3.4.0.dist-info}/WHEEL +0 -0
- {streamingcommunity-3.3.8.dist-info → streamingcommunity-3.4.0.dist-info}/entry_points.txt +0 -0
- {streamingcommunity-3.3.8.dist-info → streamingcommunity-3.4.0.dist-info}/licenses/LICENSE +0 -0
- {streamingcommunity-3.3.8.dist-info → streamingcommunity-3.4.0.dist-info}/top_level.txt +0 -0
|
@@ -4,27 +4,25 @@ import sys
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
# External libraries
|
|
7
|
-
from curl_cffi import requests
|
|
8
7
|
from rich.console import Console
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
# Internal utilities
|
|
12
11
|
from StreamingCommunity.Util.config_json import config_manager
|
|
13
|
-
from StreamingCommunity.Util.
|
|
12
|
+
from StreamingCommunity.Util.http_client import create_client_curl
|
|
14
13
|
from StreamingCommunity.Util.table import TVShowManager
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
# Logic class
|
|
18
17
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
19
18
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
|
|
20
|
-
from .util.get_license import
|
|
19
|
+
from .util.get_license import CrunchyrollClient
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
# Variable
|
|
24
23
|
console = Console()
|
|
25
24
|
media_search_manager = MediaManager()
|
|
26
25
|
table_show_manager = TVShowManager()
|
|
27
|
-
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|
28
26
|
|
|
29
27
|
|
|
30
28
|
def title_search(query: str) -> int:
|
|
@@ -40,9 +38,16 @@ def title_search(query: str) -> int:
|
|
|
40
38
|
media_search_manager.clear()
|
|
41
39
|
table_show_manager.clear()
|
|
42
40
|
|
|
43
|
-
# Check if
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
# Check if device_id or etp_rt is present
|
|
42
|
+
config = config_manager.get_dict("SITE_LOGIN", "crunchyroll")
|
|
43
|
+
if not config.get('device_id') or not config.get('etp_rt'):
|
|
44
|
+
console.print("[bold red] device_id or etp_rt is missing or empty in config.json.[/bold red]")
|
|
45
|
+
sys.exit(0)
|
|
46
|
+
|
|
47
|
+
# Initialize Crunchyroll client
|
|
48
|
+
client = CrunchyrollClient()
|
|
49
|
+
if not client.start():
|
|
50
|
+
console.print("[bold red] Failed to authenticate with Crunchyroll.[/bold red]")
|
|
46
51
|
sys.exit(0)
|
|
47
52
|
|
|
48
53
|
# Build new Crunchyroll API search URL
|
|
@@ -57,19 +62,12 @@ def title_search(query: str) -> int:
|
|
|
57
62
|
"locale": "it-IT"
|
|
58
63
|
}
|
|
59
64
|
|
|
60
|
-
headers =
|
|
61
|
-
headers['authorization'] = f"Bearer {get_auth_token(generate_device_id()).access_token}"
|
|
65
|
+
headers = client._get_headers()
|
|
62
66
|
|
|
63
67
|
console.print(f"[cyan]Search url: [yellow]{api_url}")
|
|
64
68
|
|
|
65
69
|
try:
|
|
66
|
-
response =
|
|
67
|
-
api_url,
|
|
68
|
-
params=params,
|
|
69
|
-
headers=headers,
|
|
70
|
-
timeout=max_timeout,
|
|
71
|
-
impersonate="chrome110"
|
|
72
|
-
)
|
|
70
|
+
response = create_client_curl(headers=headers).get(api_url, params=params)
|
|
73
71
|
response.raise_for_status()
|
|
74
72
|
|
|
75
73
|
except Exception as e:
|
|
@@ -3,19 +3,10 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
# External libraries
|
|
7
|
-
from curl_cffi import requests
|
|
8
|
-
|
|
9
|
-
|
|
10
6
|
# Internal utilities
|
|
11
|
-
from StreamingCommunity.Util.headers import get_headers
|
|
12
|
-
from StreamingCommunity.Util.config_json import config_manager
|
|
13
7
|
from StreamingCommunity.Api.Player.Helper.Vixcloud.util import SeasonManager
|
|
14
|
-
from .
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
# Variable
|
|
18
|
-
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|
8
|
+
from StreamingCommunity.Util.http_client import create_client_curl
|
|
9
|
+
from .get_license import CrunchyrollClient
|
|
19
10
|
|
|
20
11
|
|
|
21
12
|
def get_series_seasons(series_id, headers, params):
|
|
@@ -23,12 +14,7 @@ def get_series_seasons(series_id, headers, params):
|
|
|
23
14
|
Fetches the seasons for a given series ID from Crunchyroll.
|
|
24
15
|
"""
|
|
25
16
|
url = f'https://www.crunchyroll.com/content/v2/cms/series/{series_id}/seasons'
|
|
26
|
-
response =
|
|
27
|
-
url,
|
|
28
|
-
params=params,
|
|
29
|
-
headers=headers,
|
|
30
|
-
impersonate="chrome110"
|
|
31
|
-
)
|
|
17
|
+
response = create_client_curl(headers=headers).get(url, params=params)
|
|
32
18
|
return response
|
|
33
19
|
|
|
34
20
|
|
|
@@ -37,34 +23,9 @@ def get_season_episodes(season_id, headers, params):
|
|
|
37
23
|
Fetches the episodes for a given season ID from Crunchyroll.
|
|
38
24
|
"""
|
|
39
25
|
url = f'https://www.crunchyroll.com/content/v2/cms/seasons/{season_id}/episodes'
|
|
40
|
-
response =
|
|
41
|
-
url,
|
|
42
|
-
params=params,
|
|
43
|
-
headers=headers,
|
|
44
|
-
impersonate="chrome110"
|
|
45
|
-
)
|
|
26
|
+
response = create_client_curl(headers=headers).get(url, params=params)
|
|
46
27
|
return response
|
|
47
28
|
|
|
48
|
-
def delete_stream_episode(episode_id, stream_id, headers):
|
|
49
|
-
"""
|
|
50
|
-
Deletes a specific stream episode by episode ID and stream ID.
|
|
51
|
-
"""
|
|
52
|
-
url = f'https://www.crunchyroll.com/playback/v1/token/{episode_id}/{stream_id}'
|
|
53
|
-
headers = get_headers()
|
|
54
|
-
|
|
55
|
-
response = requests.delete(
|
|
56
|
-
url,
|
|
57
|
-
headers=headers,
|
|
58
|
-
impersonate="chrome110"
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
if response.status_code == 204:
|
|
62
|
-
return True
|
|
63
|
-
|
|
64
|
-
else:
|
|
65
|
-
logging.error(f"Failed to delete stream episode: {response.status_code} - {response.text}")
|
|
66
|
-
return False
|
|
67
|
-
|
|
68
29
|
|
|
69
30
|
class GetSerieInfo:
|
|
70
31
|
def __init__(self, series_id):
|
|
@@ -76,8 +37,13 @@ class GetSerieInfo:
|
|
|
76
37
|
"""
|
|
77
38
|
self.series_id = series_id
|
|
78
39
|
self.seasons_manager = SeasonManager()
|
|
79
|
-
|
|
80
|
-
|
|
40
|
+
|
|
41
|
+
# Initialize Crunchyroll client
|
|
42
|
+
self.client = CrunchyrollClient()
|
|
43
|
+
if not self.client.start():
|
|
44
|
+
raise Exception("Failed to authenticate with Crunchyroll")
|
|
45
|
+
|
|
46
|
+
self.headers = self.client._get_headers()
|
|
81
47
|
self.params = {
|
|
82
48
|
'force_locale': '',
|
|
83
49
|
'preferred_audio_language': 'it-IT',
|
|
@@ -104,13 +70,10 @@ class GetSerieInfo:
|
|
|
104
70
|
if seasons:
|
|
105
71
|
self.series_name = seasons[0].get("series_title") or seasons[0].get("title")
|
|
106
72
|
|
|
107
|
-
for season in seasons:
|
|
108
|
-
season_num = season.get("season_number", 0)
|
|
109
|
-
season_name = season.get("title", f"Season {season_num}")
|
|
110
|
-
|
|
73
|
+
for i, season in enumerate(seasons, start=1):
|
|
111
74
|
self.seasons_manager.add_season({
|
|
112
|
-
'number':
|
|
113
|
-
'name':
|
|
75
|
+
'number': i,
|
|
76
|
+
'name': season.get("title", f"Season {i}"),
|
|
114
77
|
'id': season.get('id')
|
|
115
78
|
})
|
|
116
79
|
|
|
@@ -119,15 +82,10 @@ class GetSerieInfo:
|
|
|
119
82
|
Fetch and cache episodes for a specific season number.
|
|
120
83
|
"""
|
|
121
84
|
season = self.seasons_manager.get_season_by_number(season_number)
|
|
85
|
+
ep_response = get_season_episodes(season.id, self.headers, self.params)
|
|
122
86
|
|
|
123
|
-
if not season or getattr(season, 'id', None) is None:
|
|
124
|
-
logging.error(f"Season {season_number} not found or missing id.")
|
|
125
|
-
return []
|
|
126
|
-
|
|
127
|
-
season_id = season.id
|
|
128
|
-
ep_response = get_season_episodes(season_id, self.headers, self.params)
|
|
129
87
|
if ep_response.status_code != 200:
|
|
130
|
-
logging.error(f"Failed to fetch episodes for season {
|
|
88
|
+
logging.error(f"Failed to fetch episodes for season {season.id}")
|
|
131
89
|
return []
|
|
132
90
|
|
|
133
91
|
ep_data = ep_response.json()
|
|
@@ -161,12 +119,8 @@ class GetSerieInfo:
|
|
|
161
119
|
'ratings': 'true',
|
|
162
120
|
'locale': 'it-IT',
|
|
163
121
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
params=params,
|
|
167
|
-
headers=headers,
|
|
168
|
-
impersonate="chrome110"
|
|
169
|
-
)
|
|
122
|
+
|
|
123
|
+
response = create_client_curl(headers=headers).get(url, params=params)
|
|
170
124
|
|
|
171
125
|
if response.status_code != 200:
|
|
172
126
|
logging.warning(f"Failed to fetch audio locales for episode {episode_id}")
|
|
@@ -186,7 +140,6 @@ class GetSerieInfo:
|
|
|
186
140
|
if locale and guid:
|
|
187
141
|
audio_locales.append(locale)
|
|
188
142
|
urls_by_locale[locale] = f"https://www.crunchyroll.com/it/watch/{guid}"
|
|
189
|
-
#print(f"Locale: {locale}, URL: {urls_by_locale[locale]}")
|
|
190
143
|
|
|
191
144
|
return audio_locales, urls_by_locale
|
|
192
145
|
|
|
@@ -1,133 +1,124 @@
|
|
|
1
1
|
# 28.07.25
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Optional, Dict, Any
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# External library
|
|
9
|
-
from curl_cffi.requests import Session
|
|
3
|
+
from typing import Tuple, List, Dict
|
|
10
4
|
|
|
11
5
|
|
|
12
6
|
# Internal utilities
|
|
13
7
|
from StreamingCommunity.Util.config_json import config_manager
|
|
8
|
+
from StreamingCommunity.Util.http_client import create_client_curl
|
|
14
9
|
from StreamingCommunity.Util.headers import get_userAgent
|
|
15
10
|
|
|
16
11
|
|
|
17
12
|
# Variable
|
|
18
|
-
|
|
19
|
-
auth_basic = 'bm9haWhkZXZtXzZpeWcwYThsMHE6'
|
|
20
|
-
etp_rt = config_manager.get_dict("SITE_LOGIN", "crunchyroll")['etp_rt']
|
|
21
|
-
x_cr_tab_id = config_manager.get_dict("SITE_LOGIN", "crunchyroll")['x_cr_tab_id']
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@dataclass
|
|
25
|
-
class Token:
|
|
26
|
-
access_token: Optional[str] = None
|
|
27
|
-
refresh_token: Optional[str] = None
|
|
28
|
-
expires_in: Optional[int] = None
|
|
29
|
-
token_type: Optional[str] = None
|
|
30
|
-
scope: Optional[str] = None
|
|
31
|
-
country: Optional[str] = None
|
|
32
|
-
account_id: Optional[str] = None
|
|
33
|
-
profile_id: Optional[str] = None
|
|
34
|
-
extra: Dict[str, Any] = field(default_factory=dict)
|
|
35
|
-
|
|
13
|
+
PUBLIC_TOKEN = "bm9haWhkZXZtXzZpeWcwYThsMHE6"
|
|
36
14
|
|
|
37
15
|
|
|
38
|
-
|
|
39
|
-
|
|
16
|
+
class CrunchyrollClient:
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
config = config_manager.get_dict("SITE_LOGIN", "crunchyroll")
|
|
19
|
+
self.device_id = config.get('device_id')
|
|
20
|
+
self.etp_rt = config.get('etp_rt')
|
|
21
|
+
self.locale = "it-IT"
|
|
22
|
+
|
|
23
|
+
self.access_token = None
|
|
24
|
+
self.refresh_token = None
|
|
25
|
+
self.account_id = None
|
|
40
26
|
|
|
41
|
-
|
|
42
|
-
|
|
27
|
+
def _get_headers(self) -> Dict:
|
|
28
|
+
headers = {
|
|
29
|
+
'user-agent': get_userAgent(),
|
|
30
|
+
}
|
|
31
|
+
if self.access_token:
|
|
32
|
+
headers['authorization'] = f'Bearer {self.access_token}'
|
|
33
|
+
return headers
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
35
|
+
def _get_cookies(self) -> Dict:
|
|
36
|
+
cookies = {'device_id': self.device_id}
|
|
37
|
+
if self.etp_rt:
|
|
38
|
+
cookies['etp_rt'] = self.etp_rt
|
|
39
|
+
return cookies
|
|
40
|
+
|
|
41
|
+
def start(self) -> bool:
|
|
42
|
+
"""Autorizza il client"""
|
|
43
|
+
headers = self._get_headers()
|
|
44
|
+
headers['authorization'] = f'Basic {PUBLIC_TOKEN}'
|
|
45
|
+
headers['content-type'] = 'application/x-www-form-urlencoded'
|
|
46
|
+
|
|
47
|
+
data = {
|
|
48
|
+
'device_id': self.device_id,
|
|
49
|
+
'device_type': 'Chrome on Windows',
|
|
50
|
+
'grant_type': 'etp_rt_cookie',
|
|
52
51
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
'authorization': f'Basic {auth_basic}',
|
|
57
|
-
'user-agent': get_userAgent(),
|
|
58
|
-
},
|
|
59
|
-
data={
|
|
60
|
-
'device_id': device_id,
|
|
61
|
-
'device_type': 'Chrome on Windows',
|
|
62
|
-
'grant_type': 'etp_rt_cookie',
|
|
63
|
-
},
|
|
64
|
-
cookies=cookies
|
|
65
|
-
)
|
|
52
|
+
|
|
53
|
+
response = create_client_curl(headers=headers).post('https://www.crunchyroll.com/auth/v1/token', cookies=self._get_cookies(), data=data)
|
|
54
|
+
|
|
66
55
|
if response.status_code == 400:
|
|
67
56
|
print("Error 400: Please enter a correct 'etp_rt' value in config.json. You can find the value in the request headers.")
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
result = response.json()
|
|
60
|
+
self.access_token = result.get('access_token')
|
|
61
|
+
self.refresh_token = result.get('refresh_token')
|
|
62
|
+
self.account_id = result.get('account_id')
|
|
63
|
+
|
|
64
|
+
return True
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
extra = {k: v for k, v in data.items() if k not in known}
|
|
76
|
-
return Token(
|
|
77
|
-
access_token=data.get('access_token'),
|
|
78
|
-
refresh_token=data.get('refresh_token'),
|
|
79
|
-
expires_in=data.get('expires_in'),
|
|
80
|
-
token_type=data.get('token_type'),
|
|
81
|
-
scope=data.get('scope'),
|
|
82
|
-
country=data.get('country'),
|
|
83
|
-
account_id=data.get('account_id'),
|
|
84
|
-
profile_id=data.get('profile_id'),
|
|
85
|
-
extra=extra
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def get_playback_session(token: Token, device_id: str, url_id: str):
|
|
90
|
-
"""
|
|
91
|
-
Crea una sessione per ottenere i dati di playback e sottotitoli da Crunchyroll.
|
|
92
|
-
"""
|
|
93
|
-
cookies = {
|
|
94
|
-
'device_id': device_id,
|
|
95
|
-
'etp_rt': etp_rt
|
|
96
|
-
}
|
|
97
|
-
headers = {
|
|
98
|
-
'authorization': f'Bearer {token.access_token}',
|
|
99
|
-
'user-agent': get_userAgent(),
|
|
100
|
-
'x-cr-tab-id': x_cr_tab_id
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
with Session(impersonate="chrome110") as session:
|
|
104
|
-
response = session.get(
|
|
105
|
-
f'https://www.crunchyroll.com/playback/v3/{url_id}/web/chrome/play',
|
|
106
|
-
cookies=cookies,
|
|
107
|
-
headers=headers
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
if (response.status_code == 403):
|
|
66
|
+
def get_streams(self, media_id: str) -> Dict:
|
|
67
|
+
"""Ottieni gli stream disponibili"""
|
|
68
|
+
response = create_client_curl(headers=self._get_headers()).get(f'https://www.crunchyroll.com/playback/v3/{media_id}/web/chrome/play', cookies=self._get_cookies(), params={'locale': self.locale})
|
|
69
|
+
|
|
70
|
+
if response.status_code == 403:
|
|
111
71
|
raise Exception("Playback is Rejected: The current subscription does not have access to this content")
|
|
112
72
|
|
|
113
|
-
if
|
|
73
|
+
if response.status_code == 420:
|
|
114
74
|
raise Exception("TOO_MANY_ACTIVE_STREAMS. Wait a few minutes and try again.")
|
|
115
|
-
|
|
75
|
+
|
|
116
76
|
response.raise_for_status()
|
|
117
|
-
|
|
118
|
-
# Get the JSON response
|
|
77
|
+
|
|
119
78
|
data = response.json()
|
|
120
79
|
|
|
121
80
|
if data.get('error') == 'Playback is Rejected':
|
|
122
81
|
raise Exception("Playback is Rejected: Premium required")
|
|
123
82
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
83
|
+
return data
|
|
84
|
+
|
|
85
|
+
def delete_active_stream(self, media_id: str, token: str) -> bool:
|
|
86
|
+
"""Elimina uno stream attivo"""
|
|
87
|
+
response = create_client_curl(headers=self._get_headers()).delete(f'https://www.crunchyroll.com/playback/v1/token/{media_id}/{token}', cookies=self._get_cookies())
|
|
88
|
+
response.raise_for_status()
|
|
89
|
+
return response.status_code in [200, 204]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_playback_session(client: CrunchyrollClient, url_id: str) -> Tuple[str, Dict, List[Dict]]:
|
|
93
|
+
"""
|
|
94
|
+
Return the playback session details including the MPD URL, headers, and subtitles.
|
|
95
|
+
|
|
96
|
+
Parameters:
|
|
97
|
+
- client: Instance of CrunchyrollClient
|
|
98
|
+
- url_id: ID of the media to fetch
|
|
99
|
+
"""
|
|
100
|
+
data = client.get_streams(url_id)
|
|
101
|
+
url = data.get('url')
|
|
102
|
+
|
|
103
|
+
# Collect subtitles if available
|
|
104
|
+
subtitles = []
|
|
105
|
+
if 'subtitles' in data:
|
|
106
|
+
collected = []
|
|
107
|
+
for lang, info in data['subtitles'].items():
|
|
108
|
+
sub_url = info.get('url')
|
|
109
|
+
|
|
110
|
+
if not sub_url:
|
|
111
|
+
continue
|
|
112
|
+
|
|
113
|
+
collected.append({
|
|
114
|
+
'language': lang,
|
|
115
|
+
'url': sub_url,
|
|
116
|
+
'format': info.get('format')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
if collected:
|
|
120
|
+
subtitles = collected
|
|
121
|
+
|
|
122
|
+
# Return the MPD URL, headers, and subtitles
|
|
123
|
+
headers = client._get_headers()
|
|
124
|
+
return url, headers, subtitles
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# 09.06.24
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
# External libraries
|
|
4
|
-
import httpx
|
|
5
5
|
from bs4 import BeautifulSoup
|
|
6
6
|
from rich.console import Console
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
# Internal utilities
|
|
9
|
-
from StreamingCommunity.Util.config_json import config_manager
|
|
10
10
|
from StreamingCommunity.Util.headers import get_userAgent
|
|
11
|
+
from StreamingCommunity.Util.http_client import create_client
|
|
11
12
|
from StreamingCommunity.Util.table import TVShowManager
|
|
12
13
|
|
|
13
14
|
|
|
@@ -20,8 +21,6 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
|
|
|
20
21
|
console = Console()
|
|
21
22
|
media_search_manager = MediaManager()
|
|
22
23
|
table_show_manager = TVShowManager()
|
|
23
|
-
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|
24
|
-
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
def title_search(query: str) -> int:
|
|
@@ -41,15 +40,8 @@ def title_search(query: str) -> int:
|
|
|
41
40
|
console.print(f"[cyan]Search url: [yellow]{search_url}")
|
|
42
41
|
|
|
43
42
|
try:
|
|
44
|
-
response =
|
|
45
|
-
search_url,
|
|
46
|
-
headers={'user-agent': get_userAgent()},
|
|
47
|
-
timeout=max_timeout,
|
|
48
|
-
follow_redirects=True,
|
|
49
|
-
verify=False
|
|
50
|
-
)
|
|
43
|
+
response = create_client(headers={'user-agent': get_userAgent()}).get(search_url)
|
|
51
44
|
response.raise_for_status()
|
|
52
|
-
|
|
53
45
|
except Exception as e:
|
|
54
46
|
console.print(f"[red]Site: {site_constant.SITE_NAME}, request search error: {e}")
|
|
55
47
|
return 0
|
|
@@ -5,22 +5,18 @@ from typing import List, Dict
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
# External libraries
|
|
8
|
-
import httpx
|
|
9
8
|
from bs4 import BeautifulSoup
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
# Internal utilities
|
|
13
12
|
from StreamingCommunity.Util.headers import get_userAgent
|
|
14
|
-
from StreamingCommunity.Util.
|
|
13
|
+
from StreamingCommunity.Util.http_client import create_client
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
# Logic class
|
|
18
17
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
19
18
|
|
|
20
19
|
|
|
21
|
-
# Variable
|
|
22
|
-
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|
23
|
-
|
|
24
20
|
|
|
25
21
|
class GetSerieInfo:
|
|
26
22
|
def __init__(self, dict_serie: MediaItem) -> None:
|
|
@@ -43,8 +39,7 @@ class GetSerieInfo:
|
|
|
43
39
|
int: Number of seasons of the TV series. Returns -1 if parsing fails.
|
|
44
40
|
"""
|
|
45
41
|
try:
|
|
46
|
-
|
|
47
|
-
response = httpx.get(self.url, headers=self.headers, timeout=max_timeout, follow_redirects=True)
|
|
42
|
+
response = create_client(headers=self.headers).get(self.url)
|
|
48
43
|
response.raise_for_status()
|
|
49
44
|
|
|
50
45
|
# Find the seasons container
|
|
@@ -72,9 +67,7 @@ class GetSerieInfo:
|
|
|
72
67
|
List[Dict[str, str]]: List of dictionaries containing episode information.
|
|
73
68
|
"""
|
|
74
69
|
try:
|
|
75
|
-
|
|
76
|
-
# Make an HTTP request to the series URL
|
|
77
|
-
response = httpx.get(self.url, headers=self.headers, timeout=max_timeout, follow_redirects=True)
|
|
70
|
+
response = create_client(headers=self.headers).get(self.url)
|
|
78
71
|
response.raise_for_status()
|
|
79
72
|
|
|
80
73
|
# Parse HTML content of the page
|
|
@@ -9,7 +9,8 @@ from rich.console import Console
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
# Internal utilities
|
|
12
|
-
from StreamingCommunity.Util.
|
|
12
|
+
from StreamingCommunity.Util.config_json import config_manager
|
|
13
|
+
from StreamingCommunity.Util.os import os_manager
|
|
13
14
|
from StreamingCommunity.Util.message import start_message
|
|
14
15
|
from StreamingCommunity.Util.headers import get_headers
|
|
15
16
|
|
|
@@ -22,11 +23,12 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
|
22
23
|
# Player
|
|
23
24
|
from .util.fix_mpd import get_manifest
|
|
24
25
|
from StreamingCommunity import DASH_Downloader
|
|
25
|
-
from .util.get_license import
|
|
26
|
+
from .util.get_license import get_playback_url, get_tracking_info, generate_license_url
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
# Variable
|
|
29
30
|
console = Console()
|
|
31
|
+
extension_output = config_manager.get("M3U8_CONVERSION", "extension")
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
def download_film(select_title: MediaItem) -> Tuple[str, bool]:
|
|
@@ -43,21 +45,18 @@ def download_film(select_title: MediaItem) -> Tuple[str, bool]:
|
|
|
43
45
|
console.print(f"\n[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
44
46
|
|
|
45
47
|
# Define the filename and path for the downloaded film
|
|
46
|
-
title_name = os_manager.get_sanitize_file(select_title.name) +
|
|
47
|
-
mp4_path = os.path.join(site_constant.MOVIE_FOLDER, title_name.replace(
|
|
48
|
+
title_name = os_manager.get_sanitize_file(select_title.name, select_title.date) + extension_output
|
|
49
|
+
mp4_path = os.path.join(site_constant.MOVIE_FOLDER, title_name.replace(extension_output, ""))
|
|
48
50
|
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
+
# Get playback URL and tracking info
|
|
52
|
+
playback_json = get_playback_url(select_title.id)
|
|
53
|
+
tracking_info = get_tracking_info(playback_json)['videos'][0]
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
license_url = generate_license_url(bearer, tracking_info)
|
|
56
|
-
mpd_url = get_manifest(tracking_info['video_src'])
|
|
55
|
+
license_url = generate_license_url(tracking_info)
|
|
56
|
+
mpd_url = get_manifest(tracking_info['url'])
|
|
57
57
|
|
|
58
58
|
# Download the episode
|
|
59
59
|
dash_process = DASH_Downloader(
|
|
60
|
-
cdm_device=get_wvd_path(),
|
|
61
60
|
license_url=license_url,
|
|
62
61
|
mpd_url=mpd_url,
|
|
63
62
|
output_path=os.path.join(mp4_path, title_name),
|
|
@@ -12,7 +12,7 @@ from rich.prompt import Prompt
|
|
|
12
12
|
# Internal utilities
|
|
13
13
|
from StreamingCommunity.Util.headers import get_headers
|
|
14
14
|
from StreamingCommunity.Util.message import start_message
|
|
15
|
-
from StreamingCommunity.Util.os import os_manager
|
|
15
|
+
from StreamingCommunity.Util.os import os_manager
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
# Logic class
|
|
@@ -70,7 +70,6 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
70
70
|
|
|
71
71
|
# Download the episode
|
|
72
72
|
dash_process = DASH_Downloader(
|
|
73
|
-
cdm_device=get_wvd_path(),
|
|
74
73
|
license_url=license_url,
|
|
75
74
|
mpd_url=mpd_url,
|
|
76
75
|
mpd_sub_list=tracking_info['subtitles'],
|
|
@@ -4,12 +4,11 @@ from datetime import datetime
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
# External libraries
|
|
7
|
-
import httpx
|
|
8
7
|
from rich.console import Console
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
# Internal utilities
|
|
12
|
-
from StreamingCommunity.Util.
|
|
11
|
+
from StreamingCommunity.Util.http_client import create_client
|
|
13
12
|
from StreamingCommunity.Util.table import TVShowManager
|
|
14
13
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
15
14
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
|
|
@@ -23,7 +22,6 @@ from .util.get_license import get_bearer_token
|
|
|
23
22
|
console = Console()
|
|
24
23
|
media_search_manager = MediaManager()
|
|
25
24
|
table_show_manager = TVShowManager()
|
|
26
|
-
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|
27
25
|
|
|
28
26
|
|
|
29
27
|
def title_search(query: str) -> int:
|
|
@@ -46,15 +44,9 @@ def title_search(query: str) -> int:
|
|
|
46
44
|
'extensions': f'{{"persistedQuery":{{"version":1,"sha256Hash":"{class_mediaset_api.getHash256()}"}}}}',
|
|
47
45
|
'variables': f'{{"first":10,"property":"search","query":"{query}","uxReference":"filteredSearch"}}',
|
|
48
46
|
}
|
|
47
|
+
|
|
49
48
|
try:
|
|
50
|
-
response =
|
|
51
|
-
search_url,
|
|
52
|
-
headers=class_mediaset_api.generate_request_headers(),
|
|
53
|
-
params=params,
|
|
54
|
-
timeout=max_timeout,
|
|
55
|
-
follow_redirects=True
|
|
56
|
-
)
|
|
57
|
-
|
|
49
|
+
response = create_client(headers=class_mediaset_api.generate_request_headers()).get(search_url, params=params)
|
|
58
50
|
response.raise_for_status()
|
|
59
51
|
except Exception as e:
|
|
60
52
|
console.print(f"[red]Site: {site_constant.SITE_NAME}, request search error: {e}")
|