Skip to content

Commit 4b45e49

Browse files
UrloMythusArrowar
andauthored
Discovery EU addition (#471)
* Fix a typo in the function naming * Adds a new site: DiscoveryEU * Removes Free check * Extra check to filter out wrong results * Update series.py * Update ScrapeSerie.py * Update series.py * Add handling for shows and movies in site.py --------- Co-authored-by: Arrow <[email protected]>
1 parent 17863b2 commit 4b45e49

File tree

6 files changed

+743
-1
lines changed

6 files changed

+743
-1
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# 22.12.25
2+
3+
# External library
4+
from rich.console import Console
5+
from rich.prompt import Prompt
6+
7+
8+
# Internal utilities
9+
from StreamingCommunity.Api.Template import site_constants, MediaItem, get_select_title
10+
11+
12+
# Logic
13+
from .site import title_search, table_show_manager, media_search_manager
14+
from .series import download_series
15+
16+
17+
# Variables
18+
indice = 15
19+
_useFor = "Film_&_Serie"
20+
_region = "EU"
21+
_deprecate = False
22+
_stream_type = "DASH"
23+
_maxResolution = "1080p"
24+
_drm = True
25+
26+
27+
msg = Prompt()
28+
console = Console()
29+
30+
31+
def process_search_result(select_title, selections=None):
32+
"""
33+
Handles the search result and initiates download for film or series
34+
35+
Parameters:
36+
select_title (MediaItem): The selected media item
37+
selections (dict, optional): Dictionary containing selection inputs
38+
{'season': season_selection, 'episode': episode_selection}
39+
40+
Returns:
41+
bool: True if processing was successful, False otherwise
42+
"""
43+
if not select_title:
44+
console.print("[yellow]No title selected or selection cancelled.")
45+
return False
46+
47+
if select_title.type == 'tv':
48+
season_selection = None
49+
episode_selection = None
50+
51+
if selections:
52+
season_selection = selections.get('season')
53+
episode_selection = selections.get('episode')
54+
55+
download_series(select_title, season_selection, episode_selection)
56+
media_search_manager.clear()
57+
table_show_manager.clear()
58+
return True
59+
60+
61+
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
62+
"""
63+
Main function for searching and downloading content
64+
65+
Parameters:
66+
string_to_search (str, optional): Search query string
67+
get_onlyDatabase (bool, optional): If True, return only the database object
68+
direct_item (dict, optional): Direct item to process (bypass search)
69+
selections (dict, optional): Dictionary containing selection inputs
70+
{'season': season_selection, 'episode': episode_selection}
71+
"""
72+
if direct_item:
73+
select_title = MediaItem(**direct_item)
74+
result = process_search_result(select_title, selections)
75+
return result
76+
77+
# Get search query from user
78+
actual_search_query = None
79+
if string_to_search is not None:
80+
actual_search_query = string_to_search.strip()
81+
else:
82+
actual_search_query = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constants.SITE_NAME}").strip()
83+
84+
# Handle empty input
85+
if not actual_search_query:
86+
return False
87+
88+
# Search on database
89+
len_database = title_search(actual_search_query)
90+
91+
# If only database is needed, return the manager
92+
if get_onlyDatabase:
93+
return media_search_manager
94+
95+
if len_database > 0:
96+
select_title = get_select_title(table_show_manager, media_search_manager, len_database)
97+
result = process_search_result(select_title, selections)
98+
return result
99+
100+
else:
101+
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{actual_search_query}")
102+
return False
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# 22.12.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 import os_manager, config_manager, start_message
14+
from StreamingCommunity.Api.Template import site_constants, MediaItem
15+
from StreamingCommunity.Api.Template.episode_manager import (
16+
manage_selection,
17+
map_episode_title,
18+
validate_selection,
19+
validate_episode_selection,
20+
display_episodes_list,
21+
display_seasons_list
22+
)
23+
from StreamingCommunity.Lib.DASH.downloader import DASH_Downloader
24+
from StreamingCommunity.Lib.HLS import HLS_Downloader
25+
26+
27+
28+
# Logic
29+
from .util.ScrapeSerie import GetSerieInfo
30+
from .util.get_license import get_playback_info, generate_license_headers,DiscoveryEUAPI
31+
32+
33+
# Variables
34+
msg = Prompt()
35+
console = Console()
36+
extension_output = config_manager.config.get("M3U8_CONVERSION", "extension")
37+
38+
39+
def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: GetSerieInfo) -> Tuple[str, bool]:
40+
"""
41+
Download a specific episode
42+
43+
Parameters:
44+
index_season_selected (int): Season number
45+
index_episode_selected (int): Episode index
46+
scrape_serie (GetSerieInfo): Series scraper instance
47+
48+
Returns:
49+
Tuple[str, bool]: (output_path, stopped_status)
50+
"""
51+
start_message()
52+
53+
# Get episode information
54+
obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected - 1)
55+
56+
# Get the real season number. Due to some seasons not having free episodes there's a mismatch between seasons and their index number.
57+
index_season_selected = scrape_serie.getRealNumberSeason(index_season_selected)
58+
59+
console.print(f"\n[yellow]Download: [red]{site_constants.SITE_NAME} → [cyan]{scrape_serie.series_name} \\ [magenta]{obj_episode.name} ([cyan]S{index_season_selected}E{index_episode_selected}) \n")
60+
61+
# Define output path
62+
mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.name)}.{extension_output}"
63+
mp4_path = os_manager.get_sanitize_path(
64+
os.path.join(site_constants.SERIES_FOLDER, scrape_serie.series_name, f"S{index_season_selected}")
65+
)
66+
67+
# Get playback information using video_id
68+
playback_info = get_playback_info(obj_episode.video_id)
69+
70+
if (str(playback_info['type']).strip().lower() == 'dash' and playback_info['license_url'] is None) or (str(playback_info['type']).strip().lower() != 'hls' and str(playback_info['type']).strip().lower() != 'dash' ):
71+
console.print(f"[red]Unsupported streaming type. Playbackk info: {playback_info}")
72+
return None, False
73+
74+
# Check the type of stream
75+
status = None
76+
if playback_info['type'] == 'dash':
77+
license_headers = generate_license_headers(playback_info['license_token'])
78+
79+
# Download the episode
80+
dash_process = DASH_Downloader(
81+
license_url=playback_info['license_url'],
82+
mpd_url=playback_info['mpd_url'],
83+
output_path=os.path.join(mp4_path, mp4_name),
84+
)
85+
86+
dash_process.parse_manifest(custom_headers=license_headers)
87+
88+
if dash_process.download_and_decrypt(custom_headers=license_headers):
89+
dash_process.finalize_output()
90+
91+
# Get final status
92+
status = dash_process.get_status()
93+
94+
elif playback_info['type'] == 'hls':
95+
96+
api = DiscoveryEUAPI()
97+
headers = api.get_request_headers()
98+
99+
# Download the episode
100+
status = HLS_Downloader(
101+
m3u8_url=playback_info['mpd_url'], #mpd_url is just a typo: it is a hls
102+
headers=headers,
103+
output_path=os.path.join(mp4_path, mp4_name),
104+
).start()
105+
106+
if status['error'] is not None and status['path']:
107+
try:
108+
os.remove(status['path'])
109+
except Exception:
110+
pass
111+
112+
return status['path'], status['stopped']
113+
114+
115+
def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, download_all: bool = False, episode_selection: str = None) -> None:
116+
"""
117+
Handle downloading episodes for a specific season
118+
119+
Parameters:
120+
index_season_selected (int): Season number
121+
scrape_serie (GetSerieInfo): Series scraper instance
122+
download_all (bool): Whether to download all episodes
123+
episode_selection (str, optional): Pre-defined episode selection
124+
"""
125+
# Get episodes for the selected season
126+
episodes = scrape_serie.getEpisodeSeasons(index_season_selected)
127+
episodes_count = len(episodes)
128+
129+
if episodes_count == 0:
130+
console.print(f"[red]No episodes found for season {index_season_selected}")
131+
return
132+
133+
if download_all:
134+
for i_episode in range(1, episodes_count + 1):
135+
path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
136+
if stopped:
137+
break
138+
else:
139+
if episode_selection is not None:
140+
last_command = episode_selection
141+
console.print(f"\n[cyan]Using provided episode selection: [yellow]{episode_selection}")
142+
else:
143+
last_command = display_episodes_list(episodes)
144+
145+
# Prompt user for episode selection
146+
list_episode_select = manage_selection(last_command, episodes_count)
147+
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
148+
149+
# Download selected episodes
150+
for i_episode in list_episode_select:
151+
path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
152+
if stopped:
153+
break
154+
155+
156+
def download_series(select_season: MediaItem, season_selection: str = None, episode_selection: str = None) -> None:
157+
"""
158+
Handle downloading a complete series
159+
160+
Parameters:
161+
select_season (MediaItem): Series metadata from search
162+
season_selection (str, optional): Pre-defined season selection
163+
episode_selection (str, optional): Pre-defined episode selection
164+
"""
165+
id_parts = select_season.id.split('|')
166+
167+
# Initialize series scraper
168+
scrape_serie = GetSerieInfo(id_parts[1], id_parts[0])
169+
seasons_count = scrape_serie.getNumberSeason()
170+
171+
if seasons_count == 0:
172+
console.print("[red]No seasons found for this series")
173+
return
174+
175+
# Handle season selection
176+
if season_selection is None:
177+
index_season_selected = display_seasons_list(scrape_serie.seasons_manager)
178+
else:
179+
index_season_selected = season_selection
180+
console.print(f"\n[cyan]Using provided season selection: [yellow]{season_selection}")
181+
182+
# Validate the selection
183+
list_season_select = manage_selection(index_season_selected, seasons_count)
184+
list_season_select = validate_selection(list_season_select, seasons_count)
185+
186+
# Loop through selected seasons and download episodes
187+
for i_season in list_season_select:
188+
if len(list_season_select) > 1 or index_season_selected == "*":
189+
download_episode(i_season, scrape_serie, download_all=True)
190+
else:
191+
download_episode(i_season, scrape_serie, download_all=False, episode_selection=episode_selection)

0 commit comments

Comments
 (0)