StreamingCommunity 3.2.8__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 +4 -9
- 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.8.dist-info → streamingcommunity-3.2.9.dist-info}/METADATA +143 -45
- streamingcommunity-3.2.9.dist-info/RECORD +113 -0
- streamingcommunity-3.2.8.dist-info/RECORD +0 -111
- {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.2.9.dist-info}/WHEEL +0 -0
- {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.2.9.dist-info}/entry_points.txt +0 -0
- {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.2.9.dist-info}/licenses/LICENSE +0 -0
- {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.2.9.dist-info}/top_level.txt +0 -0
|
@@ -10,11 +10,15 @@ from rich.prompt import Prompt
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
# Internal utilities
|
|
13
|
+
from StreamingCommunity.Util.headers import get_headers
|
|
14
|
+
from StreamingCommunity.Util.os import get_wvd_path
|
|
13
15
|
from StreamingCommunity.Util.message import start_message
|
|
14
16
|
|
|
15
17
|
|
|
18
|
+
|
|
16
19
|
# Logic class
|
|
17
20
|
from .util.ScrapeSerie import GetSerieInfo
|
|
21
|
+
from .util.get_license import generate_license_url
|
|
18
22
|
from StreamingCommunity.Api.Template.Util import (
|
|
19
23
|
manage_selection,
|
|
20
24
|
map_episode_title,
|
|
@@ -27,7 +31,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
|
27
31
|
|
|
28
32
|
|
|
29
33
|
# Player
|
|
30
|
-
from StreamingCommunity import HLS_Downloader
|
|
34
|
+
from StreamingCommunity import HLS_Downloader, DASH_Downloader
|
|
31
35
|
from StreamingCommunity.Api.Player.mediapolisvod import VideoSource
|
|
32
36
|
|
|
33
37
|
|
|
@@ -53,24 +57,52 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
53
57
|
|
|
54
58
|
# Get episode information
|
|
55
59
|
obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
|
|
56
|
-
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.name}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
|
|
57
|
-
|
|
58
|
-
# Get streaming URL
|
|
59
|
-
master_playlist = VideoSource.extract_m3u8_url(obj_episode.url)
|
|
60
|
+
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
61
|
|
|
61
62
|
# Define filename and path
|
|
62
63
|
mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4"
|
|
63
64
|
mp4_path = os.path.join(site_constant.SERIES_FOLDER, scrape_serie.series_name, f"S{index_season_selected}")
|
|
64
65
|
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
# Get streaming URL
|
|
67
|
+
master_playlist = VideoSource.extract_m3u8_url(obj_episode.url)
|
|
68
|
+
|
|
69
|
+
# HLS
|
|
70
|
+
if ".mpd" not in master_playlist:
|
|
71
|
+
r_proc = HLS_Downloader(
|
|
72
|
+
m3u8_url=master_playlist,
|
|
73
|
+
output_path=os.path.join(mp4_path, mp4_name)
|
|
74
|
+
).start()
|
|
75
|
+
|
|
76
|
+
# MPD
|
|
77
|
+
else:
|
|
78
|
+
|
|
79
|
+
# Check CDM file before usage
|
|
80
|
+
cdm_device_path = get_wvd_path()
|
|
81
|
+
if not cdm_device_path or not isinstance(cdm_device_path, (str, bytes, os.PathLike)) or not os.path.isfile(cdm_device_path):
|
|
82
|
+
console.print(f"[bold red] CDM file not found or invalid path: {cdm_device_path}[/bold red]")
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
license_url = generate_license_url(obj_episode.mpd_id)
|
|
86
|
+
|
|
87
|
+
dash_process = DASH_Downloader(
|
|
88
|
+
cdm_device=cdm_device_path,
|
|
89
|
+
license_url=license_url,
|
|
90
|
+
mpd_url=master_playlist,
|
|
91
|
+
output_path=os.path.join(mp4_path, mp4_name),
|
|
92
|
+
)
|
|
93
|
+
dash_process.parse_manifest(custom_headers=get_headers())
|
|
94
|
+
|
|
95
|
+
if dash_process.download_and_decrypt():
|
|
96
|
+
dash_process.finalize_output()
|
|
97
|
+
|
|
98
|
+
# Get final output path and status
|
|
99
|
+
r_proc = dash_process.get_status()
|
|
70
100
|
|
|
71
101
|
if r_proc['error'] is not None:
|
|
72
|
-
try:
|
|
73
|
-
|
|
102
|
+
try:
|
|
103
|
+
os.remove(r_proc['path'])
|
|
104
|
+
except Exception:
|
|
105
|
+
pass
|
|
74
106
|
|
|
75
107
|
return r_proc['path'], r_proc['stopped']
|
|
76
108
|
|
|
@@ -11,6 +11,9 @@ from StreamingCommunity.Util.headers import get_userAgent
|
|
|
11
11
|
from StreamingCommunity.Util.table import TVShowManager
|
|
12
12
|
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
13
13
|
from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Logic Import
|
|
14
17
|
from .util.ScrapeSerie import GetSerieInfo
|
|
15
18
|
|
|
16
19
|
|
|
@@ -59,7 +62,7 @@ def title_search(query: str) -> int:
|
|
|
59
62
|
media_search_manager.clear()
|
|
60
63
|
table_show_manager.clear()
|
|
61
64
|
|
|
62
|
-
search_url =
|
|
65
|
+
search_url = "https://www.raiplay.it/atomatic/raiplay-search-service/api/v1/msearch"
|
|
63
66
|
console.print(f"[cyan]Search url: [yellow]{search_url}")
|
|
64
67
|
|
|
65
68
|
json_data = {
|
|
@@ -101,7 +101,8 @@ class GetSerieInfo:
|
|
|
101
101
|
'number': ep.get('episode', ''),
|
|
102
102
|
'name': ep.get('episode_title', '') or ep.get('toptitle', ''),
|
|
103
103
|
'duration': ep.get('duration', ''),
|
|
104
|
-
'url': f"{self.base_url}{ep.get('weblink', '')}" if 'weblink' in ep else f"{self.base_url}{ep.get('url', '')}"
|
|
104
|
+
'url': f"{self.base_url}{ep.get('weblink', '')}" if 'weblink' in ep else f"{self.base_url}{ep.get('url', '')}",
|
|
105
|
+
'mpd_id': ep.get('video_url').split("=")[1].strip()
|
|
105
106
|
}
|
|
106
107
|
season.episodes.add(episode)
|
|
107
108
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# 16.03.25
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# External library
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Internal utilities
|
|
9
|
+
from StreamingCommunity.Util.config_json import config_manager
|
|
10
|
+
from StreamingCommunity.Util.headers import get_headers
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Variable
|
|
14
|
+
MAX_TIMEOUT = config_manager.get_int("REQUESTS", "timeout")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def generate_license_url(mpd_id: str):
|
|
19
|
+
"""
|
|
20
|
+
Generates the URL to obtain the Widevine license.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
mpd_id (str): The ID of the MPD (Media Presentation Description) file.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
str: The full license URL.
|
|
27
|
+
"""
|
|
28
|
+
params = {
|
|
29
|
+
'cont': mpd_id,
|
|
30
|
+
'output': '62',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
response = httpx.get('https://mediapolisvod.rai.it/relinker/relinkerServlet.htm', params=params, headers=get_headers(), timeout=MAX_TIMEOUT)
|
|
34
|
+
response.raise_for_status()
|
|
35
|
+
|
|
36
|
+
# Extract the license URL from the response in two lines
|
|
37
|
+
json_data = response.json()
|
|
38
|
+
license_url = json_data.get('licence_server_map').get('drmLicenseUrlValues')[0].get('licenceUrl')
|
|
39
|
+
|
|
40
|
+
return license_url
|
|
@@ -71,7 +71,7 @@ def download_film(select_title: MediaItem) -> str:
|
|
|
71
71
|
mp4_path = os.path.join(site_constant.MOVIE_FOLDER, title_name.replace(".mp4", ""))
|
|
72
72
|
|
|
73
73
|
# Download the film using the m3u8 playlist, and output filename
|
|
74
|
-
|
|
74
|
+
hls_process = HLS_Downloader(
|
|
75
75
|
m3u8_url=master_playlist,
|
|
76
76
|
output_path=os.path.join(mp4_path, title_name)
|
|
77
77
|
).start()
|
|
@@ -83,8 +83,10 @@ def download_film(select_title: MediaItem) -> str:
|
|
|
83
83
|
if script_id != "unknown":
|
|
84
84
|
TelegramSession.deleteScriptId(script_id)
|
|
85
85
|
|
|
86
|
-
if
|
|
87
|
-
try:
|
|
88
|
-
|
|
86
|
+
if hls_process['error'] is not None:
|
|
87
|
+
try:
|
|
88
|
+
os.remove(hls_process['path'])
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
89
91
|
|
|
90
|
-
return
|
|
92
|
+
return hls_process['path']
|
|
@@ -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] → [bold magenta]{obj_episode.name}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
|
|
58
|
+
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{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()
|
|
@@ -81,16 +81,18 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
81
81
|
master_playlist = video_source.get_playlist()
|
|
82
82
|
|
|
83
83
|
# Download the episode
|
|
84
|
-
|
|
84
|
+
hls_process = HLS_Downloader(
|
|
85
85
|
m3u8_url=master_playlist,
|
|
86
86
|
output_path=os.path.join(mp4_path, mp4_name)
|
|
87
87
|
).start()
|
|
88
88
|
|
|
89
|
-
if
|
|
90
|
-
try:
|
|
91
|
-
|
|
89
|
+
if hls_process['error'] is not None:
|
|
90
|
+
try:
|
|
91
|
+
os.remove(hls_process['path'])
|
|
92
|
+
except Exception:
|
|
93
|
+
pass
|
|
92
94
|
|
|
93
|
-
return
|
|
95
|
+
return hls_process['path'], hls_process['stopped']
|
|
94
96
|
|
|
95
97
|
|
|
96
98
|
def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, video_source: VideoSource, download_all: bool = False, episode_selection: str = None) -> None:
|
|
@@ -214,7 +216,7 @@ def download_series(select_season: MediaItem, season_selection: str = None, epis
|
|
|
214
216
|
download_episode(season_number, scrape_serie, video_source, download_all=False, episode_selection=episode_selection)
|
|
215
217
|
|
|
216
218
|
if site_constant.TELEGRAM_BOT:
|
|
217
|
-
bot.send_message(
|
|
219
|
+
bot.send_message("Finito di scaricare tutte le serie e episodi", None)
|
|
218
220
|
|
|
219
221
|
# Get script_id
|
|
220
222
|
script_id = TelegramSession.get_session()
|
|
@@ -57,7 +57,9 @@ def title_search(query: str) -> int:
|
|
|
57
57
|
version = json.loads(soup.find('div', {'id': "app"}).get("data-page"))['version']
|
|
58
58
|
|
|
59
59
|
except Exception as e:
|
|
60
|
-
if "WinError" in str(e) or "Errno" in str(e):
|
|
60
|
+
if "WinError" in str(e) or "Errno" in str(e):
|
|
61
|
+
console.print("\n[bold yellow]Please make sure you have enabled and configured a valid proxy.[/bold yellow]")
|
|
62
|
+
|
|
61
63
|
console.print(f"[red]Site: {site_constant.SITE_NAME} version, request error: {e}")
|
|
62
64
|
return 0
|
|
63
65
|
|
|
@@ -116,7 +118,7 @@ def title_search(query: str) -> int:
|
|
|
116
118
|
|
|
117
119
|
if site_constant.TELEGRAM_BOT:
|
|
118
120
|
if choices:
|
|
119
|
-
bot.send_message(
|
|
121
|
+
bot.send_message("Lista dei risultati:", choices)
|
|
120
122
|
|
|
121
123
|
# Return the number of titles found
|
|
122
124
|
return media_search_manager.get_length()
|
|
@@ -49,13 +49,15 @@ def download_film(select_title: MediaItem) -> str:
|
|
|
49
49
|
mp4_path = os.path.join(site_constant.MOVIE_FOLDER, title_name.replace(".mp4", ""))
|
|
50
50
|
|
|
51
51
|
# Download the film using the m3u8 playlist, and output filename
|
|
52
|
-
|
|
52
|
+
hls_process = HLS_Downloader(
|
|
53
53
|
m3u8_url=master_playlist,
|
|
54
54
|
output_path=os.path.join(mp4_path, title_name)
|
|
55
55
|
).start()
|
|
56
56
|
|
|
57
|
-
if
|
|
58
|
-
try:
|
|
59
|
-
|
|
57
|
+
if hls_process['error'] is not None:
|
|
58
|
+
try:
|
|
59
|
+
os.remove(hls_process['path'])
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
60
62
|
|
|
61
|
-
return
|
|
63
|
+
return hls_process['path']
|
|
@@ -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"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.name}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
|
|
56
|
+
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
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"
|
|
@@ -64,16 +64,18 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
|
|
|
64
64
|
master_playlist = video_source.get_m3u8_url(obj_episode.url)
|
|
65
65
|
|
|
66
66
|
# Download the episode
|
|
67
|
-
|
|
67
|
+
hls_process = HLS_Downloader(
|
|
68
68
|
m3u8_url=master_playlist,
|
|
69
69
|
output_path=os.path.join(mp4_path, mp4_name)
|
|
70
70
|
).start()
|
|
71
71
|
|
|
72
|
-
if
|
|
73
|
-
try:
|
|
74
|
-
|
|
72
|
+
if hls_process['error'] is not None:
|
|
73
|
+
try:
|
|
74
|
+
os.remove(hls_process['path'])
|
|
75
|
+
except Exception:
|
|
76
|
+
pass
|
|
75
77
|
|
|
76
|
-
return
|
|
78
|
+
return hls_process['path'], hls_process['stopped']
|
|
77
79
|
|
|
78
80
|
|
|
79
81
|
def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, download_all: bool = False, episode_selection: str = None) -> None:
|
|
@@ -86,7 +86,9 @@ def title_search(query: str) -> int:
|
|
|
86
86
|
soup = BeautifulSoup(response.text, 'html.parser')
|
|
87
87
|
|
|
88
88
|
except Exception as e:
|
|
89
|
-
if "WinError" in str(e) or "Errno" in str(e):
|
|
89
|
+
if "WinError" in str(e) or "Errno" in str(e):
|
|
90
|
+
console.print("\n[bold yellow]Please make sure you have enabled and configured a valid proxy.[/bold yellow]")
|
|
91
|
+
|
|
90
92
|
console.print(f"[red]Site: {site_constant.SITE_NAME}, request search error: {e}")
|
|
91
93
|
return 0
|
|
92
94
|
|
|
@@ -5,11 +5,12 @@ import logging
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
# External libraries
|
|
8
|
-
import httpx
|
|
9
8
|
from bs4 import BeautifulSoup
|
|
10
9
|
|
|
10
|
+
|
|
11
11
|
# Internal utilities
|
|
12
12
|
from StreamingCommunity.Util.headers import get_userAgent
|
|
13
|
+
from StreamingCommunity.Util.http_client import create_client
|
|
13
14
|
from StreamingCommunity.Util.config_json import config_manager
|
|
14
15
|
from StreamingCommunity.Api.Player.Helper.Vixcloud.util import SeasonManager, Episode
|
|
15
16
|
|
|
@@ -24,8 +25,7 @@ class GetSerieInfo:
|
|
|
24
25
|
self.url = url
|
|
25
26
|
self.seasons_manager = SeasonManager()
|
|
26
27
|
self.series_name = None
|
|
27
|
-
|
|
28
|
-
self.client = httpx.Client(headers=self.headers, timeout=max_timeout)
|
|
28
|
+
self.client = create_client(headers=self.headers)
|
|
29
29
|
|
|
30
30
|
def collect_info_season(self) -> None:
|
|
31
31
|
"""
|
|
@@ -7,4 +7,13 @@ from .manage_ep import (
|
|
|
7
7
|
validate_selection,
|
|
8
8
|
dynamic_format_number,
|
|
9
9
|
display_episodes_list
|
|
10
|
-
)
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"manage_selection",
|
|
14
|
+
"map_episode_title",
|
|
15
|
+
"validate_episode_selection",
|
|
16
|
+
"validate_selection",
|
|
17
|
+
"dynamic_format_number",
|
|
18
|
+
"display_episodes_list"
|
|
19
|
+
]
|
|
@@ -125,20 +125,20 @@ def map_episode_title(tv_name: str, number_season: int, episode_number: int, epi
|
|
|
125
125
|
"""
|
|
126
126
|
map_episode_temp = MAP_EPISODE
|
|
127
127
|
|
|
128
|
-
if tv_name
|
|
128
|
+
if tv_name is not None:
|
|
129
129
|
map_episode_temp = map_episode_temp.replace("%(tv_name)", os_manager.get_sanitize_file(tv_name))
|
|
130
130
|
|
|
131
|
-
if number_season
|
|
131
|
+
if number_season is not None:
|
|
132
132
|
map_episode_temp = map_episode_temp.replace("%(season)", str(number_season))
|
|
133
133
|
else:
|
|
134
134
|
map_episode_temp = map_episode_temp.replace("%(season)", dynamic_format_number(str(0)))
|
|
135
135
|
|
|
136
|
-
if episode_number
|
|
136
|
+
if episode_number is not None:
|
|
137
137
|
map_episode_temp = map_episode_temp.replace("%(episode)", dynamic_format_number(str(episode_number)))
|
|
138
138
|
else:
|
|
139
139
|
map_episode_temp = map_episode_temp.replace("%(episode)", dynamic_format_number(str(0)))
|
|
140
140
|
|
|
141
|
-
if episode_name
|
|
141
|
+
if episode_name is not None:
|
|
142
142
|
map_episode_temp = map_episode_temp.replace("%(episode_name)", os_manager.get_sanitize_file(episode_name))
|
|
143
143
|
|
|
144
144
|
logging.info(f"Map episode string return: {map_episode_temp}")
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# 19.06.24
|
|
2
2
|
|
|
3
|
-
import sys
|
|
4
|
-
|
|
5
|
-
|
|
6
3
|
# External library
|
|
7
4
|
from rich.console import Console
|
|
8
5
|
|
|
@@ -83,12 +80,19 @@ def get_select_title(table_show_manager, media_search_manager, num_results_avail
|
|
|
83
80
|
|
|
84
81
|
color_index = 1
|
|
85
82
|
for key in first_media_item.__dict__.keys():
|
|
83
|
+
|
|
86
84
|
if key.capitalize() in column_to_hide:
|
|
87
85
|
continue
|
|
86
|
+
|
|
88
87
|
if key in ('id', 'type', 'name', 'score'):
|
|
89
|
-
if key == 'type':
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
if key == 'type':
|
|
89
|
+
column_info["Type"] = {'color': 'yellow'}
|
|
90
|
+
|
|
91
|
+
elif key == 'name':
|
|
92
|
+
column_info["Name"] = {'color': 'magenta'}
|
|
93
|
+
elif key == 'score':
|
|
94
|
+
column_info["Score"] = {'color': 'cyan'}
|
|
95
|
+
|
|
92
96
|
else:
|
|
93
97
|
column_info[key.capitalize()] = {'color': available_colors[color_index % len(available_colors)]}
|
|
94
98
|
color_index += 1
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import base64
|
|
4
4
|
import logging
|
|
5
|
-
import os
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
# External libraries
|
|
@@ -31,16 +30,11 @@ def get_widevine_keys(pssh, license_url, cdm_device_path, headers=None, payload=
|
|
|
31
30
|
list: List of dicts {'kid': ..., 'key': ...} (only CONTENT keys) or None if error.
|
|
32
31
|
"""
|
|
33
32
|
|
|
34
|
-
# Check if CDM file exists
|
|
35
|
-
if not os.path.isfile(cdm_device_path):
|
|
36
|
-
console.print(f"[bold red] CDM file not found: {cdm_device_path}[/bold red]")
|
|
37
|
-
return None
|
|
38
|
-
|
|
39
33
|
# Check if PSSH is a valid base64 string
|
|
40
34
|
try:
|
|
41
35
|
base64.b64decode(pssh)
|
|
42
36
|
except Exception:
|
|
43
|
-
console.print(
|
|
37
|
+
console.print("[bold red] Invalid PSSH base64 string.[/bold red]")
|
|
44
38
|
return None
|
|
45
39
|
|
|
46
40
|
try:
|
|
@@ -67,7 +61,7 @@ def get_widevine_keys(pssh, license_url, cdm_device_path, headers=None, payload=
|
|
|
67
61
|
response = httpx.post(license_url, data=challenge, headers=req_headers, content=payload)
|
|
68
62
|
|
|
69
63
|
if response.status_code != 200:
|
|
70
|
-
console.print(f"[bold red]License error:[/bold red] {response.status_code}")
|
|
64
|
+
console.print(f"[bold red]License error:[/bold red] {response.status_code}, {response.text}")
|
|
71
65
|
return None
|
|
72
66
|
|
|
73
67
|
# Handle (JSON) or classic (binary) license response
|
|
@@ -77,18 +71,17 @@ def get_widevine_keys(pssh, license_url, cdm_device_path, headers=None, payload=
|
|
|
77
71
|
|
|
78
72
|
# Check if license_data is empty
|
|
79
73
|
if not license_data:
|
|
80
|
-
console.print(
|
|
74
|
+
console.print("[bold red]License response is empty.[/bold red]")
|
|
81
75
|
return None
|
|
82
76
|
|
|
83
77
|
if "application/json" in content_type:
|
|
84
78
|
try:
|
|
85
79
|
|
|
86
80
|
# Try to decode as JSON only if plausible
|
|
87
|
-
text = response.text
|
|
88
81
|
data = None
|
|
89
82
|
try:
|
|
90
83
|
data = response.json()
|
|
91
|
-
except Exception
|
|
84
|
+
except Exception:
|
|
92
85
|
data = None
|
|
93
86
|
|
|
94
87
|
if data and "license" in data:
|
|
@@ -118,7 +111,7 @@ def get_widevine_keys(pssh, license_url, cdm_device_path, headers=None, payload=
|
|
|
118
111
|
|
|
119
112
|
# Check if content_keys list is empty
|
|
120
113
|
if not content_keys:
|
|
121
|
-
console.print(
|
|
114
|
+
console.print("[bold yellow]⚠️ No CONTENT keys found in license.[/bold yellow]")
|
|
122
115
|
return None
|
|
123
116
|
|
|
124
117
|
return content_keys
|
|
@@ -38,7 +38,7 @@ def decrypt_with_mp4decrypt(encrypted_path, kid, key, output_path=None, cleanup=
|
|
|
38
38
|
bytes.fromhex(kid)
|
|
39
39
|
bytes.fromhex(key)
|
|
40
40
|
except Exception:
|
|
41
|
-
console.print(
|
|
41
|
+
console.print("[bold red] Invalid KID or KEY (not hex).[/bold red]")
|
|
42
42
|
return None
|
|
43
43
|
|
|
44
44
|
if not output_path:
|
|
@@ -22,7 +22,7 @@ from .cdm_helpher import get_widevine_keys
|
|
|
22
22
|
|
|
23
23
|
# Config
|
|
24
24
|
DOWNLOAD_SPECIFIC_AUDIO = config_manager.get_list('M3U8_DOWNLOAD', 'specific_list_audio')
|
|
25
|
-
FILTER_CUSTOM_REOLUTION = str(config_manager.get('
|
|
25
|
+
FILTER_CUSTOM_REOLUTION = str(config_manager.get('M3U8_CONVERSION', 'force_resolution')).strip().lower()
|
|
26
26
|
CLEANUP_TMP = config_manager.get_bool('M3U8_DOWNLOAD', 'cleanup_tmp_folder')
|
|
27
27
|
|
|
28
28
|
|
|
@@ -61,7 +61,7 @@ class MPDParser:
|
|
|
61
61
|
self.base_url = mpd_url.rsplit('/', 1)[0] + '/'
|
|
62
62
|
|
|
63
63
|
def parse(self, custom_headers):
|
|
64
|
-
response = httpx.get(self.mpd_url, headers=custom_headers, timeout=max_timeout)
|
|
64
|
+
response = httpx.get(self.mpd_url, headers=custom_headers, timeout=max_timeout, follow_redirects=True)
|
|
65
65
|
response.raise_for_status()
|
|
66
66
|
|
|
67
67
|
root = ET.fromstring(response.content)
|
|
@@ -134,7 +134,7 @@ class MPD_Segments:
|
|
|
134
134
|
|
|
135
135
|
try:
|
|
136
136
|
headers = {'User-Agent': get_userAgent()}
|
|
137
|
-
response = await client.get(init_url, headers=headers)
|
|
137
|
+
response = await client.get(init_url, headers=headers, follow_redirects=True)
|
|
138
138
|
|
|
139
139
|
with open(concat_path, 'wb') as outfile:
|
|
140
140
|
if response.status_code == 200:
|
|
@@ -160,7 +160,8 @@ class MPD_Segments:
|
|
|
160
160
|
headers = {'User-Agent': get_userAgent()}
|
|
161
161
|
for attempt in range(max_retry):
|
|
162
162
|
try:
|
|
163
|
-
resp = await client.get(url, headers=headers)
|
|
163
|
+
resp = await client.get(url, headers=headers, follow_redirects=True)
|
|
164
|
+
|
|
164
165
|
if resp.status_code == 200:
|
|
165
166
|
return idx, resp.content, attempt
|
|
166
167
|
else:
|
|
@@ -214,7 +215,7 @@ class MPD_Segments:
|
|
|
214
215
|
for attempt in range(max_retry):
|
|
215
216
|
try:
|
|
216
217
|
resp = await client.get(url, headers=headers)
|
|
217
|
-
|
|
218
|
+
|
|
218
219
|
if resp.status_code == 200:
|
|
219
220
|
return idx, resp.content, attempt
|
|
220
221
|
else:
|
|
@@ -17,6 +17,7 @@ from rich.panel import Panel
|
|
|
17
17
|
# Internal utilities
|
|
18
18
|
from StreamingCommunity.Util.config_json import config_manager
|
|
19
19
|
from StreamingCommunity.Util.headers import get_userAgent
|
|
20
|
+
from StreamingCommunity.Util.http_client import create_client
|
|
20
21
|
from StreamingCommunity.Util.os import os_manager, internet_manager
|
|
21
22
|
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
|
|
22
23
|
|
|
@@ -38,7 +39,7 @@ DOWNLOAD_SPECIFIC_AUDIO = config_manager.get_list('M3U8_DOWNLOAD', 'specific_lis
|
|
|
38
39
|
DOWNLOAD_SPECIFIC_SUBTITLE = config_manager.get_list('M3U8_DOWNLOAD', 'specific_list_subtitles')
|
|
39
40
|
MERGE_SUBTITLE = config_manager.get_bool('M3U8_DOWNLOAD', 'merge_subs')
|
|
40
41
|
CLEANUP_TMP = config_manager.get_bool('M3U8_DOWNLOAD', 'cleanup_tmp_folder')
|
|
41
|
-
|
|
42
|
+
FILTER_CUSTOM_RESOLUTION = str(config_manager.get('M3U8_CONVERSION', 'force_resolution')).strip().lower()
|
|
42
43
|
RETRY_LIMIT = config_manager.get_int('REQUESTS', 'max_retry')
|
|
43
44
|
MAX_TIMEOUT = config_manager.get_int("REQUESTS", "timeout")
|
|
44
45
|
TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
|
|
@@ -62,7 +63,8 @@ class HLSClient:
|
|
|
62
63
|
Returns:
|
|
63
64
|
Response content/text or None if all retries fail
|
|
64
65
|
"""
|
|
65
|
-
client
|
|
66
|
+
# Use unified HTTP client (inherits timeout/verify/proxy from config)
|
|
67
|
+
client = create_client(headers=self.headers)
|
|
66
68
|
|
|
67
69
|
for attempt in range(RETRY_LIMIT):
|
|
68
70
|
try:
|
|
@@ -154,12 +156,12 @@ class M3U8Manager:
|
|
|
154
156
|
self.sub_streams = []
|
|
155
157
|
|
|
156
158
|
else:
|
|
157
|
-
if str(
|
|
159
|
+
if str(FILTER_CUSTOM_RESOLUTION) == "best":
|
|
158
160
|
self.video_url, self.video_res = self.parser._video.get_best_uri()
|
|
159
|
-
elif str(
|
|
161
|
+
elif str(FILTER_CUSTOM_RESOLUTION) == "worst":
|
|
160
162
|
self.video_url, self.video_res = self.parser._video.get_worst_uri()
|
|
161
|
-
elif str(
|
|
162
|
-
resolution_value = int(str(
|
|
163
|
+
elif str(FILTER_CUSTOM_RESOLUTION).replace("p", "").replace("px", "").isdigit():
|
|
164
|
+
resolution_value = int(str(FILTER_CUSTOM_RESOLUTION).replace("p", "").replace("px", ""))
|
|
163
165
|
self.video_url, self.video_res = self.parser._video.get_custom_uri(resolution_value)
|
|
164
166
|
else:
|
|
165
167
|
logging.error("Resolution not recognized.")
|
|
@@ -188,7 +190,7 @@ class M3U8Manager:
|
|
|
188
190
|
|
|
189
191
|
console.print(
|
|
190
192
|
f"[cyan bold]Video [/cyan bold] [green]Available:[/green] [purple]{', '.join(list_available_resolution)}[/purple] | "
|
|
191
|
-
f"[red]Set:[/red] [purple]{
|
|
193
|
+
f"[red]Set:[/red] [purple]{FILTER_CUSTOM_RESOLUTION}[/purple] | "
|
|
192
194
|
f"[yellow]Downloadable:[/yellow] [purple]{self.video_res[0]}x{self.video_res[1]}[/purple]"
|
|
193
195
|
)
|
|
194
196
|
|
|
@@ -418,7 +420,7 @@ class HLS_Downloader:
|
|
|
418
420
|
- is_master: Whether the M3U8 was a master playlist
|
|
419
421
|
Or raises an exception if there's an error
|
|
420
422
|
"""
|
|
421
|
-
console.print(
|
|
423
|
+
console.print("[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
|
|
422
424
|
|
|
423
425
|
if TELEGRAM_BOT:
|
|
424
426
|
bot = get_bot_instance()
|
|
@@ -435,7 +437,7 @@ class HLS_Downloader:
|
|
|
435
437
|
'stopped': False
|
|
436
438
|
}
|
|
437
439
|
if TELEGRAM_BOT:
|
|
438
|
-
bot.send_message(
|
|
440
|
+
bot.send_message("Contenuto già scaricato!", None)
|
|
439
441
|
return response
|
|
440
442
|
|
|
441
443
|
self.path_manager.setup_directories()
|