StreamingCommunity 3.2.1__py3-none-any.whl → 3.2.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/Player/Helper/Vixcloud/util.py +4 -0
- StreamingCommunity/Api/Player/hdplayer.py +2 -2
- StreamingCommunity/Api/Player/mixdrop.py +1 -1
- StreamingCommunity/Api/Player/vixcloud.py +4 -5
- StreamingCommunity/Api/Site/altadefinizione/film.py +2 -2
- 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 -2
- StreamingCommunity/Api/Site/cb01new/film.py +1 -1
- StreamingCommunity/Api/Site/crunchyroll/__init__.py +103 -0
- StreamingCommunity/Api/Site/crunchyroll/film.py +82 -0
- StreamingCommunity/Api/Site/crunchyroll/series.py +186 -0
- StreamingCommunity/Api/Site/crunchyroll/site.py +113 -0
- StreamingCommunity/Api/Site/crunchyroll/util/ScrapeSerie.py +238 -0
- StreamingCommunity/Api/Site/crunchyroll/util/get_license.py +227 -0
- StreamingCommunity/Api/Site/guardaserie/series.py +1 -2
- StreamingCommunity/Api/Site/guardaserie/site.py +1 -2
- StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +9 -8
- StreamingCommunity/Api/Site/mediasetinfinity/__init__.py +96 -0
- StreamingCommunity/Api/Site/mediasetinfinity/film.py +85 -0
- StreamingCommunity/Api/Site/mediasetinfinity/series.py +185 -0
- StreamingCommunity/Api/Site/mediasetinfinity/site.py +112 -0
- StreamingCommunity/Api/Site/mediasetinfinity/util/ScrapeSerie.py +259 -0
- StreamingCommunity/Api/Site/mediasetinfinity/util/fix_mpd.py +64 -0
- StreamingCommunity/Api/Site/mediasetinfinity/util/get_license.py +214 -0
- StreamingCommunity/Api/Site/raiplay/film.py +2 -2
- StreamingCommunity/Api/Site/raiplay/series.py +2 -1
- StreamingCommunity/Api/Site/streamingcommunity/__init__.py +6 -17
- StreamingCommunity/Api/Site/streamingcommunity/film.py +3 -3
- StreamingCommunity/Api/Site/streamingcommunity/series.py +11 -11
- StreamingCommunity/Api/Site/streamingcommunity/site.py +2 -4
- StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +3 -6
- StreamingCommunity/Api/Site/streamingwatch/__init__.py +6 -14
- StreamingCommunity/Api/Site/streamingwatch/film.py +3 -3
- StreamingCommunity/Api/Site/streamingwatch/series.py +9 -9
- StreamingCommunity/Api/Site/streamingwatch/site.py +5 -7
- StreamingCommunity/Api/Site/streamingwatch/util/ScrapeSerie.py +2 -2
- StreamingCommunity/Lib/Downloader/DASH/cdm_helpher.py +131 -0
- StreamingCommunity/Lib/Downloader/DASH/decrypt.py +79 -0
- StreamingCommunity/Lib/Downloader/DASH/downloader.py +218 -0
- StreamingCommunity/Lib/Downloader/DASH/parser.py +249 -0
- StreamingCommunity/Lib/Downloader/DASH/segments.py +332 -0
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +10 -30
- StreamingCommunity/Lib/Downloader/HLS/segments.py +146 -263
- StreamingCommunity/Lib/Downloader/MP4/downloader.py +0 -5
- StreamingCommunity/Lib/FFmpeg/capture.py +3 -3
- StreamingCommunity/Lib/FFmpeg/command.py +1 -1
- StreamingCommunity/TelegramHelp/config.json +3 -7
- StreamingCommunity/Upload/version.py +1 -1
- StreamingCommunity/Util/bento4_installer.py +191 -0
- StreamingCommunity/Util/config_json.py +1 -1
- StreamingCommunity/Util/headers.py +0 -3
- StreamingCommunity/Util/os.py +36 -46
- StreamingCommunity/__init__.py +2 -1
- StreamingCommunity/run.py +11 -10
- {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/METADATA +7 -9
- streamingcommunity-3.2.7.dist-info/RECORD +111 -0
- StreamingCommunity/Api/Site/1337xx/__init__.py +0 -72
- StreamingCommunity/Api/Site/1337xx/site.py +0 -82
- StreamingCommunity/Api/Site/1337xx/title.py +0 -61
- StreamingCommunity/Lib/Proxies/proxy.py +0 -72
- streamingcommunity-3.2.1.dist-info/RECORD +0 -96
- {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/WHEEL +0 -0
- {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/entry_points.txt +0 -0
- {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/licenses/LICENSE +0 -0
- {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# 21.05.24
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# External library
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Internal utilities
|
|
12
|
+
from StreamingCommunity.Util.os import os_manager, get_wvd_path
|
|
13
|
+
from StreamingCommunity.Util.message import start_message
|
|
14
|
+
from StreamingCommunity.Util.headers import get_headers
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Logic class
|
|
18
|
+
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
19
|
+
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Player
|
|
23
|
+
from .util.fix_mpd import get_manifest
|
|
24
|
+
from StreamingCommunity import DASH_Downloader
|
|
25
|
+
from .util.get_license import get_bearer_token, get_playback_url, get_tracking_info, generate_license_url
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Variable
|
|
29
|
+
console = Console()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def download_film(select_title: MediaItem) -> Tuple[str, bool]:
|
|
33
|
+
"""
|
|
34
|
+
Downloads a film using the provided film ID, title name, and domain.
|
|
35
|
+
|
|
36
|
+
Parameters:
|
|
37
|
+
- select_title (MediaItem): The selected media item.
|
|
38
|
+
|
|
39
|
+
Return:
|
|
40
|
+
- str: output path if successful, otherwise None
|
|
41
|
+
"""
|
|
42
|
+
start_message()
|
|
43
|
+
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
44
|
+
|
|
45
|
+
# Define the filename and path for the downloaded film
|
|
46
|
+
title_name = os_manager.get_sanitize_file(select_title.name) + ".mp4"
|
|
47
|
+
mp4_path = os.path.join(site_constant.MOVIE_FOLDER, title_name.replace(".mp4", ""))
|
|
48
|
+
|
|
49
|
+
# Generate mpd and license URLs
|
|
50
|
+
bearer = get_bearer_token()
|
|
51
|
+
|
|
52
|
+
# Extract ID from the episode URL
|
|
53
|
+
episode_id = select_title.url.split('_')[-1]
|
|
54
|
+
if "http" in episode_id:
|
|
55
|
+
try: episode_id = select_title.url.split('/')[-1]
|
|
56
|
+
except Exception:
|
|
57
|
+
console.print(f"[red]Error:[/red] Failed to parse episode ID from URL: {select_title.url}")
|
|
58
|
+
return None, True
|
|
59
|
+
|
|
60
|
+
playback_json = get_playback_url(bearer, episode_id)
|
|
61
|
+
tracking_info = get_tracking_info(bearer, playback_json)[0]
|
|
62
|
+
|
|
63
|
+
license_url = generate_license_url(bearer, tracking_info)
|
|
64
|
+
mpd_url = get_manifest(tracking_info['video_src'])
|
|
65
|
+
|
|
66
|
+
# Download the episode
|
|
67
|
+
r_proc = DASH_Downloader(
|
|
68
|
+
cdm_device=get_wvd_path(),
|
|
69
|
+
license_url=license_url,
|
|
70
|
+
mpd_url=mpd_url,
|
|
71
|
+
output_path=mp4_path,
|
|
72
|
+
)
|
|
73
|
+
r_proc.parse_manifest(custom_headers=get_headers())
|
|
74
|
+
|
|
75
|
+
if r_proc.download_and_decrypt():
|
|
76
|
+
r_proc.finalize_output()
|
|
77
|
+
|
|
78
|
+
# Get final output path and status
|
|
79
|
+
status = r_proc.get_status()
|
|
80
|
+
|
|
81
|
+
if status['error'] is not None and status['path']:
|
|
82
|
+
try: os.remove(status['path'])
|
|
83
|
+
except Exception: pass
|
|
84
|
+
|
|
85
|
+
return status['path'], status['stopped']
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# 16.03.25
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# External library
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.prompt import Prompt
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Internal utilities
|
|
13
|
+
from StreamingCommunity.Util.headers import get_headers
|
|
14
|
+
from StreamingCommunity.Util.message import start_message
|
|
15
|
+
from StreamingCommunity.Util.os import os_manager, get_wvd_path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Logic class
|
|
19
|
+
from .util.ScrapeSerie import GetSerieInfo
|
|
20
|
+
from StreamingCommunity.Api.Template.Util import (
|
|
21
|
+
manage_selection,
|
|
22
|
+
map_episode_title,
|
|
23
|
+
validate_selection,
|
|
24
|
+
validate_episode_selection,
|
|
25
|
+
display_episodes_list
|
|
26
|
+
)
|
|
27
|
+
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
28
|
+
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Player
|
|
32
|
+
from .util.fix_mpd import get_manifest
|
|
33
|
+
from StreamingCommunity import DASH_Downloader
|
|
34
|
+
from .util.get_license import get_bearer_token, get_playback_url, get_tracking_info, generate_license_url
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Variable
|
|
38
|
+
msg = Prompt()
|
|
39
|
+
console = Console()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: GetSerieInfo) -> Tuple[str,bool]:
|
|
43
|
+
"""
|
|
44
|
+
Downloads a specific episode from a specified season.
|
|
45
|
+
|
|
46
|
+
Parameters:
|
|
47
|
+
- index_season_selected (int): Season number
|
|
48
|
+
- index_episode_selected (int): Episode index
|
|
49
|
+
- scrape_serie (GetSerieInfo): Scraper object with series information
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
- str: Path to downloaded file
|
|
53
|
+
- bool: Whether download was stopped
|
|
54
|
+
"""
|
|
55
|
+
start_message()
|
|
56
|
+
|
|
57
|
+
# Get episode information
|
|
58
|
+
obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
|
|
59
|
+
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.name}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
|
|
60
|
+
|
|
61
|
+
# Define filename and path for the downloaded video
|
|
62
|
+
mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4"
|
|
63
|
+
mp4_path = os_manager.get_sanitize_path(os.path.join(site_constant.SERIES_FOLDER, scrape_serie.series_name, f"S{index_season_selected}"))
|
|
64
|
+
|
|
65
|
+
# Generate mpd and license URLs
|
|
66
|
+
bearer = get_bearer_token()
|
|
67
|
+
|
|
68
|
+
# Extract ID from the episode URL
|
|
69
|
+
episode_id = obj_episode.url.split('_')[-1]
|
|
70
|
+
if "http" in episode_id:
|
|
71
|
+
try: episode_id = obj_episode.url.split('/')[-1]
|
|
72
|
+
except Exception:
|
|
73
|
+
console.print(f"[red]Error:[/red] Failed to parse episode ID from URL: {obj_episode.url}")
|
|
74
|
+
return None, True
|
|
75
|
+
|
|
76
|
+
playback_json = get_playback_url(bearer, episode_id)
|
|
77
|
+
tracking_info = get_tracking_info(bearer, playback_json)[0]
|
|
78
|
+
|
|
79
|
+
license_url = generate_license_url(bearer, tracking_info)
|
|
80
|
+
mpd_url = get_manifest(tracking_info['video_src'])
|
|
81
|
+
|
|
82
|
+
# Download the episode
|
|
83
|
+
r_proc = DASH_Downloader(
|
|
84
|
+
cdm_device=get_wvd_path(),
|
|
85
|
+
license_url=license_url,
|
|
86
|
+
mpd_url=mpd_url,
|
|
87
|
+
output_path=os.path.join(mp4_path, mp4_name),
|
|
88
|
+
)
|
|
89
|
+
r_proc.parse_manifest(custom_headers=get_headers())
|
|
90
|
+
|
|
91
|
+
if r_proc.download_and_decrypt():
|
|
92
|
+
r_proc.finalize_output()
|
|
93
|
+
|
|
94
|
+
# Get final output path and status
|
|
95
|
+
status = r_proc.get_status()
|
|
96
|
+
|
|
97
|
+
if status['error'] is not None and status['path']:
|
|
98
|
+
try: os.remove(status['path'])
|
|
99
|
+
except Exception: pass
|
|
100
|
+
|
|
101
|
+
return status['path'], status['stopped']
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, download_all: bool = False, episode_selection: str = None) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Handle downloading episodes for a specific season.
|
|
107
|
+
|
|
108
|
+
Parameters:
|
|
109
|
+
- index_season_selected (int): Season number
|
|
110
|
+
- scrape_serie (GetSerieInfo): Scraper object with series information
|
|
111
|
+
- download_all (bool): Whether to download all episodes
|
|
112
|
+
- episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
|
|
113
|
+
"""
|
|
114
|
+
# Get episodes for the selected season
|
|
115
|
+
episodes = scrape_serie.getEpisodeSeasons(index_season_selected)
|
|
116
|
+
episodes_count = len(episodes)
|
|
117
|
+
|
|
118
|
+
if download_all:
|
|
119
|
+
for i_episode in range(1, episodes_count + 1):
|
|
120
|
+
path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
|
|
121
|
+
|
|
122
|
+
if stopped:
|
|
123
|
+
break
|
|
124
|
+
|
|
125
|
+
console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.")
|
|
126
|
+
|
|
127
|
+
else:
|
|
128
|
+
if episode_selection is not None:
|
|
129
|
+
last_command = episode_selection
|
|
130
|
+
console.print(f"\n[cyan]Using provided episode selection: [yellow]{episode_selection}")
|
|
131
|
+
|
|
132
|
+
else:
|
|
133
|
+
last_command = display_episodes_list(episodes)
|
|
134
|
+
|
|
135
|
+
# Prompt user for episode selection
|
|
136
|
+
list_episode_select = manage_selection(last_command, episodes_count)
|
|
137
|
+
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
|
138
|
+
|
|
139
|
+
# Download selected episodes if not stopped
|
|
140
|
+
for i_episode in list_episode_select:
|
|
141
|
+
path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
|
|
142
|
+
|
|
143
|
+
if stopped:
|
|
144
|
+
break
|
|
145
|
+
|
|
146
|
+
def download_series(select_season: MediaItem, season_selection: str = None, episode_selection: str = None) -> None:
|
|
147
|
+
"""
|
|
148
|
+
Handle downloading a complete series.
|
|
149
|
+
|
|
150
|
+
Parameters:
|
|
151
|
+
- select_season (MediaItem): Series metadata from search
|
|
152
|
+
- season_selection (str, optional): Pre-defined season selection that bypasses manual input
|
|
153
|
+
- episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
|
|
154
|
+
"""
|
|
155
|
+
scrape_serie = GetSerieInfo(select_season.url)
|
|
156
|
+
|
|
157
|
+
# Get total number of seasons
|
|
158
|
+
seasons_count = scrape_serie.getNumberSeason()
|
|
159
|
+
|
|
160
|
+
# Prompt user for season selection and download episodes
|
|
161
|
+
console.print(f"\n[green]Seasons found: [red]{seasons_count}")
|
|
162
|
+
|
|
163
|
+
# If season_selection is provided, use it instead of asking for input
|
|
164
|
+
if season_selection is None:
|
|
165
|
+
index_season_selected = msg.ask(
|
|
166
|
+
"\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
|
|
167
|
+
"[yellow](e.g., 1-2) [cyan]for a range of seasons, or [yellow](e.g., 3-*) [cyan]to download from a specific season to the end"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
else:
|
|
171
|
+
index_season_selected = season_selection
|
|
172
|
+
console.print(f"\n[cyan]Using provided season selection: [yellow]{season_selection}")
|
|
173
|
+
|
|
174
|
+
# Validate the selection
|
|
175
|
+
list_season_select = manage_selection(index_season_selected, seasons_count)
|
|
176
|
+
list_season_select = validate_selection(list_season_select, seasons_count)
|
|
177
|
+
|
|
178
|
+
# Loop through the selected seasons and download episodes
|
|
179
|
+
for i_season in list_season_select:
|
|
180
|
+
if len(list_season_select) > 1 or index_season_selected == "*":
|
|
181
|
+
# Download all episodes if multiple seasons are selected or if '*' is used
|
|
182
|
+
download_episode(i_season, scrape_serie, download_all=True)
|
|
183
|
+
else:
|
|
184
|
+
# Otherwise, let the user select specific episodes for the single season
|
|
185
|
+
download_episode(i_season, scrape_serie, download_all=False, episode_selection=episode_selection)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# 25.07.25
|
|
2
|
+
|
|
3
|
+
# External libraries
|
|
4
|
+
import httpx
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Internal utilities
|
|
9
|
+
from StreamingCommunity.Util.config_json import config_manager
|
|
10
|
+
from StreamingCommunity.Util.headers import get_headers
|
|
11
|
+
from StreamingCommunity.Util.table import TVShowManager
|
|
12
|
+
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
13
|
+
from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Logic class
|
|
17
|
+
from .util.get_license import get_bearer_token
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Variable
|
|
21
|
+
console = Console()
|
|
22
|
+
media_search_manager = MediaManager()
|
|
23
|
+
table_show_manager = TVShowManager()
|
|
24
|
+
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def title_search(query: str) -> int:
|
|
28
|
+
"""
|
|
29
|
+
Search for titles based on a search query.
|
|
30
|
+
|
|
31
|
+
Parameters:
|
|
32
|
+
- query (str): The query to search for.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
int: The number of titles found.
|
|
36
|
+
"""
|
|
37
|
+
media_search_manager.clear()
|
|
38
|
+
table_show_manager.clear()
|
|
39
|
+
|
|
40
|
+
search_url = f'https://api-ott-prod-fe.mediaset.net/PROD/play/reco/anonymous/v2.0'
|
|
41
|
+
console.print(f"[cyan]Search url: [yellow]{search_url}")
|
|
42
|
+
|
|
43
|
+
params = {
|
|
44
|
+
'uxReference': 'filteredSearch',
|
|
45
|
+
'shortId': '',
|
|
46
|
+
'query': query.strip(),
|
|
47
|
+
'params': 'channel≈;variant≈',
|
|
48
|
+
'contentId': '',
|
|
49
|
+
'property': 'search',
|
|
50
|
+
'tenant': 'play-prod-v2',
|
|
51
|
+
'userContext': 'iwiAeyJwbGF0Zm9ybSI6IndlYiJ9Aw==',
|
|
52
|
+
'aresContext': '',
|
|
53
|
+
'page': '1',
|
|
54
|
+
'hitsPerPage': '8',
|
|
55
|
+
'clientId': 'client_id'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
headers = get_headers()
|
|
59
|
+
headers['authorization'] = f'Bearer {get_bearer_token()}'
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
response = httpx.get(
|
|
63
|
+
search_url,
|
|
64
|
+
headers=headers,
|
|
65
|
+
params=params,
|
|
66
|
+
timeout=max_timeout,
|
|
67
|
+
follow_redirects=True
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
response.raise_for_status()
|
|
71
|
+
except Exception as e:
|
|
72
|
+
console.print(f"[red]Site: {site_constant.SITE_NAME}, request search error: {e}")
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
# Parse response
|
|
76
|
+
resp_json = response.json()
|
|
77
|
+
blocks = resp_json.get('response', {}).get('blocks', [])
|
|
78
|
+
items = []
|
|
79
|
+
for block in blocks:
|
|
80
|
+
if 'items' in block:
|
|
81
|
+
items.extend(block['items'])
|
|
82
|
+
elif 'results' in block and 'items' in block['results']:
|
|
83
|
+
items.extend(block['results']['items'])
|
|
84
|
+
|
|
85
|
+
# Process items
|
|
86
|
+
for item in items:
|
|
87
|
+
|
|
88
|
+
# Get the media type
|
|
89
|
+
program_type = item.get('programType', '') or item.get('programtype', '')
|
|
90
|
+
program_type = program_type.lower()
|
|
91
|
+
|
|
92
|
+
if program_type in ('movie', 'film'):
|
|
93
|
+
media_type = 'film'
|
|
94
|
+
page_url = item.get('mediasetprogram$videoPageUrl', '')
|
|
95
|
+
elif program_type in ('series', 'serie'):
|
|
96
|
+
media_type = 'tv'
|
|
97
|
+
page_url = item.get('mediasetprogram$pageUrl', '')
|
|
98
|
+
else:
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
if page_url and page_url.startswith('//'):
|
|
102
|
+
page_url = f"https:{page_url}"
|
|
103
|
+
|
|
104
|
+
media_search_manager.add_media({
|
|
105
|
+
'id': item.get('guid', '') or item.get('_id', ''),
|
|
106
|
+
'name': item.get('title', ''),
|
|
107
|
+
'type': media_type,
|
|
108
|
+
'url': page_url,
|
|
109
|
+
'image': None,
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
return media_search_manager.get_length()
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# 16.03.25
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# External libraries
|
|
8
|
+
import httpx
|
|
9
|
+
from bs4 import BeautifulSoup
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Internal utilities
|
|
13
|
+
from StreamingCommunity.Util.headers import get_headers, get_userAgent
|
|
14
|
+
from StreamingCommunity.Util.config_json import config_manager
|
|
15
|
+
from StreamingCommunity.Api.Player.Helper.Vixcloud.util import SeasonManager
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Logic class
|
|
19
|
+
from .get_license import get_bearer_token, get_playback_url
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Variable
|
|
23
|
+
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GetSerieInfo:
|
|
27
|
+
def __init__(self, url):
|
|
28
|
+
"""
|
|
29
|
+
Initialize the GetSerieInfo class for scraping TV series information.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
- url (str): The URL of the streaming site.
|
|
33
|
+
"""
|
|
34
|
+
self.headers = get_headers()
|
|
35
|
+
self.url = url
|
|
36
|
+
self.seasons_manager = SeasonManager()
|
|
37
|
+
self.subBrandId = None
|
|
38
|
+
self.id_media = None
|
|
39
|
+
self.current_url = None
|
|
40
|
+
|
|
41
|
+
def _extract_subbrand_id(self, soup):
|
|
42
|
+
"""
|
|
43
|
+
Extract subBrandId from the chapter link in the main page.
|
|
44
|
+
Searches all <a> tags to see if one has 'capitoli_' in the href.
|
|
45
|
+
"""
|
|
46
|
+
for a_tag in soup.find_all("a", href=True):
|
|
47
|
+
href = a_tag["href"]
|
|
48
|
+
|
|
49
|
+
if "capitoli_" in href:
|
|
50
|
+
match = re.search(r"sb(\d+)", href)
|
|
51
|
+
if match:
|
|
52
|
+
return match.group(1)
|
|
53
|
+
match = re.search(r",sb(\d+)", href)
|
|
54
|
+
if match:
|
|
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
|
+
})
|
|
98
|
+
|
|
99
|
+
for idx, entry in enumerate(entries, 1):
|
|
100
|
+
title = entry.get("title", "")
|
|
101
|
+
video_page_url = entry.get("mediasetprogram$videoPageUrl", "")
|
|
102
|
+
|
|
103
|
+
if video_page_url.startswith("//"):
|
|
104
|
+
episode_url = "https:" + video_page_url
|
|
105
|
+
else:
|
|
106
|
+
episode_url = video_page_url
|
|
107
|
+
|
|
108
|
+
if current_season:
|
|
109
|
+
current_season.episodes.add({
|
|
110
|
+
'number': idx,
|
|
111
|
+
'name': title,
|
|
112
|
+
'url': episode_url,
|
|
113
|
+
'duration': int(entry.get("mediasetprogram$duration", 0) / 60)
|
|
114
|
+
})
|
|
115
|
+
else:
|
|
116
|
+
seasons_dict = {}
|
|
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
|
|
133
|
+
|
|
134
|
+
elif len(numbers) == 1:
|
|
135
|
+
# If only one, use it as episode
|
|
136
|
+
ep_num = numbers[0]
|
|
137
|
+
|
|
138
|
+
if season_num is None or ep_num is None:
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
season_name = entry.get("mediasetprogram$brandTitle") or f"Stagione {season_num}"
|
|
142
|
+
|
|
143
|
+
if season_num not in seasons_dict:
|
|
144
|
+
current_season = self.seasons_manager.add_season({
|
|
145
|
+
'number': season_num,
|
|
146
|
+
'name': season_name
|
|
147
|
+
})
|
|
148
|
+
seasons_dict[season_num] = current_season
|
|
149
|
+
|
|
150
|
+
else:
|
|
151
|
+
current_season = seasons_dict[season_num]
|
|
152
|
+
|
|
153
|
+
video_page_url = entry.get("mediasetprogram$videoPageUrl", "")
|
|
154
|
+
if video_page_url.startswith("//"):
|
|
155
|
+
episode_url = "https:" + video_page_url
|
|
156
|
+
else:
|
|
157
|
+
episode_url = video_page_url
|
|
158
|
+
|
|
159
|
+
if current_season:
|
|
160
|
+
current_season.episodes.add({
|
|
161
|
+
'number': ep_num,
|
|
162
|
+
'name': entry.get("title", ""),
|
|
163
|
+
'url': episode_url,
|
|
164
|
+
'duration': entry.get("mediasetprogram$duration")
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
def collect_season(self) -> None:
|
|
168
|
+
"""
|
|
169
|
+
Retrieve all episodes for all seasons using the Mediaset Infinity API.
|
|
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
|
|
195
|
+
|
|
196
|
+
else:
|
|
197
|
+
puntate_link = None
|
|
198
|
+
for a_tag in soup.find_all("a", href=True):
|
|
199
|
+
href = a_tag["href"]
|
|
200
|
+
if "puntateintere" in href and "sb" in href:
|
|
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
|
|
214
|
+
params = {
|
|
215
|
+
'byCustomValue': "{subBrandId}{" + str(self.subBrandId) + "}",
|
|
216
|
+
'sort': ':publishInfo_lastPublished|asc,tvSeasonEpisodeNumber|asc',
|
|
217
|
+
'range': '0-100',
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
json_url = f'https://feed.entertainment.tv.theplatform.eu/f/{self.id_media}/mediaset-prod-all-programs-v2'
|
|
221
|
+
json_resp = httpx.get(json_url, headers={'user-agent': get_userAgent()}, params=params, timeout=max_timeout, follow_redirects=True)
|
|
222
|
+
|
|
223
|
+
data = json_resp.json()
|
|
224
|
+
entries = data.get("entries", [])
|
|
225
|
+
|
|
226
|
+
# Use the unified parsing function
|
|
227
|
+
self._parse_entries(entries, single_season=single_season)
|
|
228
|
+
|
|
229
|
+
# ------------- FOR GUI -------------
|
|
230
|
+
def getNumberSeason(self) -> int:
|
|
231
|
+
"""
|
|
232
|
+
Get the total number of seasons available for the series.
|
|
233
|
+
"""
|
|
234
|
+
if not self.seasons_manager.seasons:
|
|
235
|
+
self.collect_season()
|
|
236
|
+
|
|
237
|
+
return len(self.seasons_manager.seasons)
|
|
238
|
+
|
|
239
|
+
def getEpisodeSeasons(self, season_number: int) -> list:
|
|
240
|
+
"""
|
|
241
|
+
Get all episodes for a specific season.
|
|
242
|
+
"""
|
|
243
|
+
if not self.seasons_manager.seasons:
|
|
244
|
+
self.collect_season()
|
|
245
|
+
|
|
246
|
+
# Get season directly by its number
|
|
247
|
+
season = self.seasons_manager.get_season_by_number(season_number)
|
|
248
|
+
return season.episodes.episodes if season else []
|
|
249
|
+
|
|
250
|
+
def selectEpisode(self, season_number: int, episode_index: int) -> dict:
|
|
251
|
+
"""
|
|
252
|
+
Get information for a specific episode in a specific season.
|
|
253
|
+
"""
|
|
254
|
+
episodes = self.getEpisodeSeasons(season_number)
|
|
255
|
+
if not episodes or episode_index < 0 or episode_index >= len(episodes):
|
|
256
|
+
logging.error(f"Episode index {episode_index} is out of range for season {season_number}")
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
return episodes[episode_index]
|