StreamingCommunity 2.9.8__py3-none-any.whl → 3.0.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/ddl.py +2 -10
- StreamingCommunity/Api/Player/mediapolisvod.py +64 -0
- StreamingCommunity/Api/Player/sweetpixel.py +3 -3
- StreamingCommunity/Api/Player/vixcloud.py +4 -9
- StreamingCommunity/Api/Site/1337xx/__init__.py +2 -3
- StreamingCommunity/Api/Site/1337xx/site.py +6 -1
- StreamingCommunity/Api/Site/altadefinizione/__init__.py +24 -9
- StreamingCommunity/Api/Site/altadefinizione/film.py +0 -1
- StreamingCommunity/Api/Site/altadefinizione/series.py +66 -70
- StreamingCommunity/Api/Site/altadefinizione/site.py +8 -2
- StreamingCommunity/Api/Site/altadefinizione/util/ScrapeSerie.py +37 -2
- StreamingCommunity/Api/Site/animeunity/__init__.py +30 -12
- StreamingCommunity/Api/Site/animeunity/film.py +40 -0
- StreamingCommunity/Api/Site/animeunity/serie.py +153 -0
- StreamingCommunity/Api/Site/animeunity/site.py +64 -37
- StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py +41 -22
- StreamingCommunity/Api/Site/animeworld/__init__.py +26 -14
- StreamingCommunity/Api/Site/animeworld/film.py +63 -0
- StreamingCommunity/Api/Site/animeworld/serie.py +25 -22
- StreamingCommunity/Api/Site/animeworld/site.py +8 -2
- StreamingCommunity/Api/Site/animeworld/util/ScrapeSerie.py +32 -5
- StreamingCommunity/Api/Site/cb01new/__init__.py +2 -3
- StreamingCommunity/Api/Site/cb01new/site.py +7 -1
- StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +2 -3
- StreamingCommunity/Api/Site/ddlstreamitaly/series.py +31 -32
- StreamingCommunity/Api/Site/ddlstreamitaly/site.py +8 -3
- StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +30 -2
- StreamingCommunity/Api/Site/guardaserie/__init__.py +22 -9
- StreamingCommunity/Api/Site/guardaserie/series.py +55 -53
- StreamingCommunity/Api/Site/guardaserie/site.py +10 -3
- StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +27 -1
- StreamingCommunity/Api/Site/raiplay/__init__.py +92 -0
- StreamingCommunity/Api/Site/raiplay/film.py +65 -0
- StreamingCommunity/Api/Site/raiplay/series.py +162 -0
- StreamingCommunity/Api/Site/raiplay/site.py +173 -0
- StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py +127 -0
- StreamingCommunity/Api/Site/streamingcommunity/__init__.py +30 -24
- StreamingCommunity/Api/Site/streamingcommunity/film.py +1 -2
- StreamingCommunity/Api/Site/streamingcommunity/series.py +76 -90
- StreamingCommunity/Api/Site/streamingcommunity/site.py +8 -4
- StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +41 -15
- StreamingCommunity/Api/Template/site.py +2 -2
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +1 -1
- StreamingCommunity/Lib/Downloader/HLS/segments.py +9 -18
- StreamingCommunity/Lib/Downloader/MP4/downloader.py +2 -1
- StreamingCommunity/Lib/Downloader/TOR/downloader.py +7 -14
- StreamingCommunity/Lib/FFmpeg/capture.py +1 -5
- StreamingCommunity/Lib/FFmpeg/util.py +57 -19
- StreamingCommunity/Lib/M3U8/estimator.py +57 -41
- StreamingCommunity/Lib/M3U8/parser.py +26 -6
- StreamingCommunity/Upload/update.py +22 -3
- StreamingCommunity/Upload/version.py +1 -1
- StreamingCommunity/Util/ffmpeg_installer.py +26 -1
- StreamingCommunity/Util/os.py +13 -15
- StreamingCommunity/Util/table.py +4 -2
- StreamingCommunity/global_search.py +1 -4
- StreamingCommunity/run.py +1 -4
- {streamingcommunity-2.9.8.dist-info → streamingcommunity-3.0.0.dist-info}/METADATA +1 -1
- streamingcommunity-3.0.0.dist-info/RECORD +91 -0
- {streamingcommunity-2.9.8.dist-info → streamingcommunity-3.0.0.dist-info}/WHEEL +1 -1
- StreamingCommunity/Api/Site/animeunity/film_serie.py +0 -181
- StreamingCommunity/Api/Site/mostraguarda/__init__.py +0 -73
- StreamingCommunity/Api/Site/mostraguarda/film.py +0 -93
- streamingcommunity-2.9.8.dist-info/RECORD +0 -85
- {streamingcommunity-2.9.8.dist-info → streamingcommunity-3.0.0.dist-info}/entry_points.txt +0 -0
- {streamingcommunity-2.9.8.dist-info → streamingcommunity-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {streamingcommunity-2.9.8.dist-info → streamingcommunity-3.0.0.dist-info}/top_level.txt +0 -0
|
@@ -44,7 +44,13 @@ def title_search(query: str) -> int:
|
|
|
44
44
|
console.print(f"[cyan]Search url: [yellow]{search_url}")
|
|
45
45
|
|
|
46
46
|
try:
|
|
47
|
-
response = httpx.get(
|
|
47
|
+
response = httpx.get(
|
|
48
|
+
search_url,
|
|
49
|
+
headers={'user-agent': get_userAgent()},
|
|
50
|
+
timeout=max_timeout,
|
|
51
|
+
follow_redirects=True,
|
|
52
|
+
verify=False
|
|
53
|
+
)
|
|
48
54
|
response.raise_for_status()
|
|
49
55
|
|
|
50
56
|
except Exception as e:
|
|
@@ -62,9 +68,10 @@ def title_search(query: str) -> int:
|
|
|
62
68
|
link = serie_div.find('a').get("href")
|
|
63
69
|
|
|
64
70
|
serie_info = {
|
|
65
|
-
'name': title,
|
|
71
|
+
'name': title.replace("streaming guardaserie", ""),
|
|
66
72
|
'url': link,
|
|
67
|
-
'type': 'tv'
|
|
73
|
+
'type': 'tv',
|
|
74
|
+
'image': f"{site_constant.FULL_URL}/{serie_div.find('img').get('src')}",
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
media_search_manager.add_media(serie_info)
|
|
@@ -104,4 +104,30 @@ class GetSerieInfo:
|
|
|
104
104
|
except Exception as e:
|
|
105
105
|
logging.error(f"Error parsing HTML page: {e}")
|
|
106
106
|
|
|
107
|
-
return []
|
|
107
|
+
return []
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# ------------- FOR GUI -------------
|
|
111
|
+
def getNumberSeason(self) -> int:
|
|
112
|
+
"""
|
|
113
|
+
Get the total number of seasons available for the series.
|
|
114
|
+
"""
|
|
115
|
+
return self.get_seasons_number()
|
|
116
|
+
|
|
117
|
+
def getEpisodeSeasons(self, season_number: int) -> list:
|
|
118
|
+
"""
|
|
119
|
+
Get all episodes for a specific season.
|
|
120
|
+
"""
|
|
121
|
+
episodes = self.get_episode_number(season_number)
|
|
122
|
+
return episodes
|
|
123
|
+
|
|
124
|
+
def selectEpisode(self, season_number: int, episode_index: int) -> dict:
|
|
125
|
+
"""
|
|
126
|
+
Get information for a specific episode in a specific season.
|
|
127
|
+
"""
|
|
128
|
+
episodes = self.getEpisodeSeasons(season_number)
|
|
129
|
+
if not episodes or episode_index < 0 or episode_index >= len(episodes):
|
|
130
|
+
logging.error(f"Episode index {episode_index} is out of range for season {season_number}")
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
return episodes[episode_index]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# 21.05.24
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# External library
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.prompt import Prompt
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Internal utilities
|
|
10
|
+
from StreamingCommunity.Api.Template import get_select_title
|
|
11
|
+
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
12
|
+
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Logic class
|
|
16
|
+
from .site import title_search, table_show_manager, media_search_manager
|
|
17
|
+
from .series import download_series
|
|
18
|
+
from .film import download_film
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Variable
|
|
22
|
+
indice = 8
|
|
23
|
+
_useFor = "film_serie"
|
|
24
|
+
_priority = 1 # NOTE: Site search need the use of tmbd obj
|
|
25
|
+
_engineDownload = "hls"
|
|
26
|
+
|
|
27
|
+
msg = Prompt()
|
|
28
|
+
console = Console()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_user_input(string_to_search: str = None):
|
|
32
|
+
"""
|
|
33
|
+
Asks the user to input a search term.
|
|
34
|
+
"""
|
|
35
|
+
return msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
|
|
36
|
+
|
|
37
|
+
def process_search_result(select_title, selections=None):
|
|
38
|
+
"""
|
|
39
|
+
Handles the search result and initiates the download for either a film or series.
|
|
40
|
+
|
|
41
|
+
Parameters:
|
|
42
|
+
select_title (MediaItem): The selected media item
|
|
43
|
+
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
|
44
|
+
{'season': season_selection, 'episode': episode_selection}
|
|
45
|
+
"""
|
|
46
|
+
if select_title.type == 'tv':
|
|
47
|
+
season_selection = None
|
|
48
|
+
episode_selection = None
|
|
49
|
+
|
|
50
|
+
if selections:
|
|
51
|
+
season_selection = selections.get('season')
|
|
52
|
+
episode_selection = selections.get('episode')
|
|
53
|
+
|
|
54
|
+
download_series(select_title, season_selection, episode_selection)
|
|
55
|
+
|
|
56
|
+
else:
|
|
57
|
+
download_film(select_title)
|
|
58
|
+
|
|
59
|
+
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
|
|
60
|
+
"""
|
|
61
|
+
Main function of the application for search.
|
|
62
|
+
|
|
63
|
+
Parameters:
|
|
64
|
+
string_to_search (str, optional): String to search for
|
|
65
|
+
get_onlyDatabase (bool, optional): If True, return only the database object
|
|
66
|
+
direct_item (dict, optional): Direct item to process (bypass search)
|
|
67
|
+
selections (dict, optional): Dictionary containing selection inputs that bypass manual input
|
|
68
|
+
{'season': season_selection, 'episode': episode_selection}
|
|
69
|
+
"""
|
|
70
|
+
if direct_item:
|
|
71
|
+
select_title = MediaItem(**direct_item)
|
|
72
|
+
process_search_result(select_title, selections)
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
if string_to_search is None:
|
|
76
|
+
string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
|
|
77
|
+
|
|
78
|
+
# Search on database
|
|
79
|
+
len_database = title_search(string_to_search)
|
|
80
|
+
|
|
81
|
+
# If only the database is needed, return the manager
|
|
82
|
+
if get_onlyDatabase:
|
|
83
|
+
return media_search_manager
|
|
84
|
+
|
|
85
|
+
if len_database > 0:
|
|
86
|
+
select_title = get_select_title(table_show_manager, media_search_manager)
|
|
87
|
+
process_search_result(select_title, selections)
|
|
88
|
+
|
|
89
|
+
else:
|
|
90
|
+
# If no results are found, ask again
|
|
91
|
+
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
|
|
92
|
+
search()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# 21.05.24
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# External library
|
|
8
|
+
import httpx
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Internal utilities
|
|
13
|
+
from StreamingCommunity.Util.os import os_manager
|
|
14
|
+
from StreamingCommunity.Util.message import start_message
|
|
15
|
+
from StreamingCommunity.Lib.Downloader import HLS_Downloader
|
|
16
|
+
from StreamingCommunity.Util.headers import get_headers
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Logic class
|
|
20
|
+
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
21
|
+
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Player
|
|
25
|
+
from StreamingCommunity.Api.Player.mediapolisvod import VideoSource
|
|
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 MediaItem information.
|
|
35
|
+
|
|
36
|
+
Parameters:
|
|
37
|
+
- select_title (MediaItem): The media item containing film information
|
|
38
|
+
|
|
39
|
+
Return:
|
|
40
|
+
- str: Path to downloaded file
|
|
41
|
+
- bool: Whether download was stopped
|
|
42
|
+
"""
|
|
43
|
+
start_message()
|
|
44
|
+
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
|
|
45
|
+
|
|
46
|
+
# Extract m3u8 URL from the film's URL
|
|
47
|
+
response = httpx.get(select_title.url + ".json", headers=get_headers(), timeout=10)
|
|
48
|
+
first_item_path = "https://www.raiplay.it" + response.json().get("first_item_path")
|
|
49
|
+
master_playlist = VideoSource.extract_m3u8_url(first_item_path)
|
|
50
|
+
|
|
51
|
+
# Define the filename and path for the downloaded film
|
|
52
|
+
title_name = os_manager.get_sanitize_file(select_title.name) + ".mp4"
|
|
53
|
+
mp4_path = os.path.join(site_constant.MOVIE_FOLDER, title_name.replace(".mp4", ""))
|
|
54
|
+
|
|
55
|
+
# Download the film using the m3u8 playlist, and output filename
|
|
56
|
+
r_proc = HLS_Downloader(
|
|
57
|
+
m3u8_url=master_playlist,
|
|
58
|
+
output_path=os.path.join(mp4_path, title_name)
|
|
59
|
+
).start()
|
|
60
|
+
|
|
61
|
+
if r_proc['error'] is not None:
|
|
62
|
+
try: os.remove(r_proc['path'])
|
|
63
|
+
except: pass
|
|
64
|
+
|
|
65
|
+
return r_proc['path'], r_proc['stopped']
|
|
@@ -0,0 +1,162 @@
|
|
|
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
|
+
from rich.prompt import Prompt
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Internal utilities
|
|
13
|
+
from StreamingCommunity.Util.message import start_message
|
|
14
|
+
from StreamingCommunity.Lib.Downloader import HLS_Downloader
|
|
15
|
+
|
|
16
|
+
# Logic class
|
|
17
|
+
from .util.ScrapeSerie import GetSerieInfo
|
|
18
|
+
from StreamingCommunity.Api.Template.Util import (
|
|
19
|
+
manage_selection,
|
|
20
|
+
map_episode_title,
|
|
21
|
+
validate_selection,
|
|
22
|
+
validate_episode_selection,
|
|
23
|
+
display_episodes_list
|
|
24
|
+
)
|
|
25
|
+
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
26
|
+
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Player
|
|
30
|
+
from StreamingCommunity.Api.Player.mediapolisvod import VideoSource
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Variable
|
|
34
|
+
msg = Prompt()
|
|
35
|
+
console = Console()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: GetSerieInfo) -> Tuple[str,bool]:
|
|
39
|
+
"""
|
|
40
|
+
Downloads a specific episode from the specified season.
|
|
41
|
+
|
|
42
|
+
Parameters:
|
|
43
|
+
- index_season_selected (int): Season number
|
|
44
|
+
- index_episode_selected (int): Episode index
|
|
45
|
+
- scrape_serie (GetSerieInfo): Scraper object with series information
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
- str: Path to downloaded file
|
|
49
|
+
- bool: Whether download was stopped
|
|
50
|
+
"""
|
|
51
|
+
start_message()
|
|
52
|
+
|
|
53
|
+
# Get episode information
|
|
54
|
+
obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
|
|
55
|
+
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
|
+
|
|
57
|
+
# Get streaming URL
|
|
58
|
+
master_playlist = VideoSource.extract_m3u8_url(obj_episode.url)
|
|
59
|
+
|
|
60
|
+
# Define filename and path
|
|
61
|
+
mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4"
|
|
62
|
+
mp4_path = os.path.join(site_constant.SERIES_FOLDER, scrape_serie.series_name, f"S{index_season_selected}")
|
|
63
|
+
|
|
64
|
+
# Download the episode
|
|
65
|
+
r_proc = HLS_Downloader(
|
|
66
|
+
m3u8_url=master_playlist,
|
|
67
|
+
output_path=os.path.join(mp4_path, mp4_name)
|
|
68
|
+
).start()
|
|
69
|
+
|
|
70
|
+
if r_proc['error'] is not None:
|
|
71
|
+
try: os.remove(r_proc['path'])
|
|
72
|
+
except: pass
|
|
73
|
+
|
|
74
|
+
return r_proc['path'], r_proc['stopped']
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, download_all: bool = False, episode_selection: str = None) -> None:
|
|
78
|
+
"""
|
|
79
|
+
Handle downloading episodes for a specific season.
|
|
80
|
+
|
|
81
|
+
Parameters:
|
|
82
|
+
- index_season_selected (int): Season number
|
|
83
|
+
- scrape_serie (GetSerieInfo): Scraper object with series information
|
|
84
|
+
- download_all (bool): Whether to download all episodes
|
|
85
|
+
- episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
|
|
86
|
+
"""
|
|
87
|
+
# Get episodes for the selected season
|
|
88
|
+
episodes = scrape_serie.getEpisodeSeasons(index_season_selected)
|
|
89
|
+
episodes_count = len(episodes)
|
|
90
|
+
|
|
91
|
+
if download_all:
|
|
92
|
+
for i_episode in range(1, episodes_count + 1):
|
|
93
|
+
path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
|
|
94
|
+
if stopped:
|
|
95
|
+
break
|
|
96
|
+
console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.")
|
|
97
|
+
|
|
98
|
+
else:
|
|
99
|
+
# Display episodes list and manage user selection
|
|
100
|
+
if episode_selection is None:
|
|
101
|
+
last_command = display_episodes_list(episodes)
|
|
102
|
+
else:
|
|
103
|
+
last_command = episode_selection
|
|
104
|
+
console.print(f"\n[cyan]Using provided episode selection: [yellow]{episode_selection}")
|
|
105
|
+
|
|
106
|
+
# Validate the selection
|
|
107
|
+
list_episode_select = manage_selection(last_command, episodes_count)
|
|
108
|
+
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
|
|
109
|
+
|
|
110
|
+
# Download selected episodes if not stopped
|
|
111
|
+
for i_episode in list_episode_select:
|
|
112
|
+
path, stopped = download_video(index_season_selected, i_episode, scrape_serie)
|
|
113
|
+
if stopped:
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
def download_series(select_season: MediaItem, season_selection: str = None, episode_selection: str = None) -> None:
|
|
117
|
+
"""
|
|
118
|
+
Handle downloading a complete series.
|
|
119
|
+
|
|
120
|
+
Parameters:
|
|
121
|
+
- select_season (MediaItem): Series metadata from search
|
|
122
|
+
- season_selection (str, optional): Pre-defined season selection that bypasses manual input
|
|
123
|
+
- episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
|
|
124
|
+
"""
|
|
125
|
+
start_message()
|
|
126
|
+
|
|
127
|
+
# Extract program name from path_id
|
|
128
|
+
program_name = None
|
|
129
|
+
if select_season.path_id:
|
|
130
|
+
parts = select_season.path_id.strip('/').split('/')
|
|
131
|
+
if len(parts) >= 2:
|
|
132
|
+
program_name = parts[-1].split('.')[0]
|
|
133
|
+
|
|
134
|
+
# Init scraper
|
|
135
|
+
scrape_serie = GetSerieInfo(program_name)
|
|
136
|
+
|
|
137
|
+
# Get seasons info
|
|
138
|
+
scrape_serie.collect_info_title()
|
|
139
|
+
seasons_count = len(scrape_serie.seasons_manager)
|
|
140
|
+
console.print(f"\n[green]Seasons found: [red]{seasons_count}")
|
|
141
|
+
|
|
142
|
+
# If season_selection is provided, use it instead of asking for input
|
|
143
|
+
if season_selection is None:
|
|
144
|
+
index_season_selected = msg.ask(
|
|
145
|
+
"\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
|
|
146
|
+
"[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"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
else:
|
|
150
|
+
index_season_selected = season_selection
|
|
151
|
+
console.print(f"\n[cyan]Using provided season selection: [yellow]{season_selection}")
|
|
152
|
+
|
|
153
|
+
# Validate the selection
|
|
154
|
+
list_season_select = manage_selection(index_season_selected, seasons_count)
|
|
155
|
+
list_season_select = validate_selection(list_season_select, seasons_count)
|
|
156
|
+
|
|
157
|
+
# Loop through the selected seasons and download episodes
|
|
158
|
+
for season_number in list_season_select:
|
|
159
|
+
if len(list_season_select) > 1 or index_season_selected == "*":
|
|
160
|
+
download_episode(season_number, scrape_serie, download_all=True)
|
|
161
|
+
else:
|
|
162
|
+
download_episode(season_number, scrape_serie, download_all=False, episode_selection=episode_selection)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# 21.05.24
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
import queue
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# External libraries
|
|
8
|
+
import httpx
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Internal utilities
|
|
13
|
+
from StreamingCommunity.Util.config_json import config_manager
|
|
14
|
+
from StreamingCommunity.Util.headers import get_userAgent
|
|
15
|
+
from StreamingCommunity.Util.table import TVShowManager
|
|
16
|
+
from StreamingCommunity.Lib.TMBD.tmdb import tmdb
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Logic class
|
|
20
|
+
from StreamingCommunity.Api.Template.config_loader import site_constant
|
|
21
|
+
from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Variable
|
|
25
|
+
console = Console()
|
|
26
|
+
media_search_manager = MediaManager()
|
|
27
|
+
table_show_manager = TVShowManager()
|
|
28
|
+
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|
29
|
+
MAX_THREADS = 4
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def determine_media_type(title):
|
|
33
|
+
"""
|
|
34
|
+
Use TMDB to determine if a title is a movie or TV show.
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
# First search as a movie
|
|
38
|
+
movie_results = tmdb._make_request("search/movie", {"query": title})
|
|
39
|
+
movie_count = len(movie_results.get("results", []))
|
|
40
|
+
|
|
41
|
+
# Then search as a TV show
|
|
42
|
+
tv_results = tmdb._make_request("search/tv", {"query": title})
|
|
43
|
+
tv_count = len(tv_results.get("results", []))
|
|
44
|
+
|
|
45
|
+
# If results found in only one category, use that
|
|
46
|
+
if movie_count > 0 and tv_count == 0:
|
|
47
|
+
return "film"
|
|
48
|
+
elif tv_count > 0 and movie_count == 0:
|
|
49
|
+
return "tv"
|
|
50
|
+
|
|
51
|
+
# If both have results, compare popularity
|
|
52
|
+
if movie_count > 0 and tv_count > 0:
|
|
53
|
+
top_movie = movie_results["results"][0]
|
|
54
|
+
top_tv = tv_results["results"][0]
|
|
55
|
+
|
|
56
|
+
return "film" if top_movie.get("popularity", 0) > top_tv.get("popularity", 0) else "tv"
|
|
57
|
+
|
|
58
|
+
return "film"
|
|
59
|
+
|
|
60
|
+
except Exception as e:
|
|
61
|
+
console.log(f"Error determining media type with TMDB: {e}")
|
|
62
|
+
return "film"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def worker_determine_type(work_queue, result_dict, worker_id):
|
|
66
|
+
"""
|
|
67
|
+
Worker function to process items from queue and determine media types.
|
|
68
|
+
|
|
69
|
+
Parameters:
|
|
70
|
+
- work_queue: Queue containing items to process
|
|
71
|
+
- result_dict: Dictionary to store results
|
|
72
|
+
- worker_id: ID of the worker thread
|
|
73
|
+
"""
|
|
74
|
+
while not work_queue.empty():
|
|
75
|
+
try:
|
|
76
|
+
index, item = work_queue.get(block=False)
|
|
77
|
+
title = item.get('titolo', '')
|
|
78
|
+
media_type = determine_media_type(title)
|
|
79
|
+
|
|
80
|
+
result_dict[index] = {
|
|
81
|
+
'id': item.get('id', ''),
|
|
82
|
+
'name': title,
|
|
83
|
+
'type': media_type,
|
|
84
|
+
'path_id': item.get('path_id', ''),
|
|
85
|
+
'url': f"https://www.raiplay.it{item.get('url', '')}",
|
|
86
|
+
'image': f"https://www.raiplay.it{item.get('immagine', '')}",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
work_queue.task_done()
|
|
90
|
+
|
|
91
|
+
except queue.Empty:
|
|
92
|
+
break
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
console.log(f"Worker {worker_id} error: {e}")
|
|
96
|
+
work_queue.task_done()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def title_search(query: str) -> int:
|
|
100
|
+
"""
|
|
101
|
+
Search for titles based on a search query.
|
|
102
|
+
|
|
103
|
+
Parameters:
|
|
104
|
+
- query (str): The query to search for.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
int: The number of titles found.
|
|
108
|
+
"""
|
|
109
|
+
media_search_manager.clear()
|
|
110
|
+
table_show_manager.clear()
|
|
111
|
+
|
|
112
|
+
search_url = f"https://www.raiplay.it/atomatic/raiplay-search-service/api/v1/msearch"
|
|
113
|
+
console.print(f"[cyan]Search url: [yellow]{search_url}")
|
|
114
|
+
|
|
115
|
+
json_data = {
|
|
116
|
+
'templateIn': '6470a982e4e0301afe1f81f1',
|
|
117
|
+
'templateOut': '6516ac5d40da6c377b151642',
|
|
118
|
+
'params': {
|
|
119
|
+
'param': query,
|
|
120
|
+
'from': None,
|
|
121
|
+
'sort': 'relevance',
|
|
122
|
+
'onlyVideoQuery': False,
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
response = httpx.post(
|
|
128
|
+
search_url,
|
|
129
|
+
headers={'user-agent': get_userAgent()},
|
|
130
|
+
json=json_data,
|
|
131
|
+
timeout=max_timeout,
|
|
132
|
+
follow_redirects=True
|
|
133
|
+
)
|
|
134
|
+
response.raise_for_status()
|
|
135
|
+
|
|
136
|
+
except Exception as e:
|
|
137
|
+
console.print(f"Site: {site_constant.SITE_NAME}, request search error: {e}")
|
|
138
|
+
return 0
|
|
139
|
+
|
|
140
|
+
# Limit to only 15 results for performance
|
|
141
|
+
data = response.json().get('agg').get('titoli').get('cards')
|
|
142
|
+
data = data[:15] if len(data) > 15 else data
|
|
143
|
+
|
|
144
|
+
# Use multithreading to determine media types in parallel
|
|
145
|
+
work_queue = queue.Queue()
|
|
146
|
+
result_dict = {}
|
|
147
|
+
|
|
148
|
+
# Add items to the work queue
|
|
149
|
+
for i, item in enumerate(data):
|
|
150
|
+
work_queue.put((i, item))
|
|
151
|
+
|
|
152
|
+
# Create and start worker threads
|
|
153
|
+
threads = []
|
|
154
|
+
for i in range(min(MAX_THREADS, len(data))):
|
|
155
|
+
thread = threading.Thread(
|
|
156
|
+
target=worker_determine_type,
|
|
157
|
+
args=(work_queue, result_dict, i),
|
|
158
|
+
daemon=True
|
|
159
|
+
)
|
|
160
|
+
threads.append(thread)
|
|
161
|
+
thread.start()
|
|
162
|
+
|
|
163
|
+
# Wait for all threads to complete
|
|
164
|
+
for thread in threads:
|
|
165
|
+
thread.join()
|
|
166
|
+
|
|
167
|
+
# Add all results to media manager in correct order
|
|
168
|
+
for i in range(len(data)):
|
|
169
|
+
if i in result_dict:
|
|
170
|
+
media_search_manager.add_media(result_dict[i])
|
|
171
|
+
|
|
172
|
+
# Return the number of titles found
|
|
173
|
+
return media_search_manager.get_length()
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# 21.05.24
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# External libraries
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Internal utilities
|
|
11
|
+
from StreamingCommunity.Util.headers import get_headers
|
|
12
|
+
from StreamingCommunity.Util.config_json import config_manager
|
|
13
|
+
from StreamingCommunity.Api.Player.Helper.Vixcloud.util import SeasonManager
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Variable
|
|
17
|
+
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GetSerieInfo:
|
|
21
|
+
def __init__(self, program_name: str):
|
|
22
|
+
"""Initialize the GetSerieInfo class."""
|
|
23
|
+
self.base_url = "https://www.raiplay.it"
|
|
24
|
+
self.program_name = program_name
|
|
25
|
+
self.series_name = program_name
|
|
26
|
+
self.seasons_manager = SeasonManager()
|
|
27
|
+
|
|
28
|
+
def collect_info_title(self) -> None:
|
|
29
|
+
"""Get series info including seasons."""
|
|
30
|
+
try:
|
|
31
|
+
program_url = f"{self.base_url}/programmi/{self.program_name}.json"
|
|
32
|
+
response = httpx.get(url=program_url, headers=get_headers(), timeout=max_timeout)
|
|
33
|
+
response.raise_for_status()
|
|
34
|
+
|
|
35
|
+
json_data = response.json()
|
|
36
|
+
|
|
37
|
+
# Look for seasons in the 'blocks' property
|
|
38
|
+
for block in json_data.get('blocks'):
|
|
39
|
+
if block.get('type') == 'RaiPlay Multimedia Block' and block.get('name', '').lower() == 'episodi':
|
|
40
|
+
self.publishing_block_id = block.get('id')
|
|
41
|
+
|
|
42
|
+
# Extract seasons from sets array
|
|
43
|
+
for season_set in block.get('sets', []):
|
|
44
|
+
if 'stagione' in season_set.get('name', '').lower():
|
|
45
|
+
self.seasons_manager.add_season({
|
|
46
|
+
'id': season_set.get('id', ''),
|
|
47
|
+
'number': len(self.seasons_manager.seasons) + 1,
|
|
48
|
+
'name': season_set.get('name', ''),
|
|
49
|
+
'path': season_set.get('path_id', ''),
|
|
50
|
+
'episodes_count': season_set.get('episode_size', {}).get('number', 0)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logging.error(f"Error collecting series info: {e}")
|
|
55
|
+
|
|
56
|
+
def collect_info_season(self, number_season: int) -> None:
|
|
57
|
+
"""Get episodes for a specific season."""
|
|
58
|
+
try:
|
|
59
|
+
season = self.seasons_manager.get_season_by_number(number_season)
|
|
60
|
+
|
|
61
|
+
url = f"{self.base_url}/programmi/{self.program_name}/{self.publishing_block_id}/{season.id}/episodes.json"
|
|
62
|
+
response = httpx.get(url=url, headers=get_headers(), timeout=max_timeout)
|
|
63
|
+
response.raise_for_status()
|
|
64
|
+
|
|
65
|
+
episodes_data = response.json()
|
|
66
|
+
cards = []
|
|
67
|
+
|
|
68
|
+
# Extract episodes from different possible structures
|
|
69
|
+
if 'seasons' in episodes_data:
|
|
70
|
+
for season_data in episodes_data.get('seasons', []):
|
|
71
|
+
for episode_set in season_data.get('episodes', []):
|
|
72
|
+
cards.extend(episode_set.get('cards', []))
|
|
73
|
+
|
|
74
|
+
if not cards:
|
|
75
|
+
cards = episodes_data.get('cards', [])
|
|
76
|
+
|
|
77
|
+
# Add episodes to season
|
|
78
|
+
for ep in cards:
|
|
79
|
+
episode = {
|
|
80
|
+
'id': ep.get('id', ''),
|
|
81
|
+
'number': ep.get('episode', ''),
|
|
82
|
+
'name': ep.get('episode_title', '') or ep.get('toptitle', ''),
|
|
83
|
+
'duration': ep.get('duration', ''),
|
|
84
|
+
'url': f"{self.base_url}{ep.get('weblink', '')}" if 'weblink' in ep else f"{self.base_url}{ep.get('url', '')}"
|
|
85
|
+
}
|
|
86
|
+
season.episodes.add(episode)
|
|
87
|
+
|
|
88
|
+
except Exception as e:
|
|
89
|
+
logging.error(f"Error collecting episodes for season {number_season}: {e}")
|
|
90
|
+
raise
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ------------- FOR GUI -------------
|
|
94
|
+
def getNumberSeason(self) -> int:
|
|
95
|
+
"""
|
|
96
|
+
Get the total number of seasons available for the series.
|
|
97
|
+
"""
|
|
98
|
+
if not self.seasons_manager.seasons:
|
|
99
|
+
self.collect_info_title()
|
|
100
|
+
|
|
101
|
+
return len(self.seasons_manager.seasons)
|
|
102
|
+
|
|
103
|
+
def getEpisodeSeasons(self, season_number: int) -> list:
|
|
104
|
+
"""
|
|
105
|
+
Get all episodes for a specific season.
|
|
106
|
+
"""
|
|
107
|
+
season = self.seasons_manager.get_season_by_number(season_number)
|
|
108
|
+
|
|
109
|
+
if not season:
|
|
110
|
+
logging.error(f"Season {season_number} not found")
|
|
111
|
+
return []
|
|
112
|
+
|
|
113
|
+
if not season.episodes.episodes:
|
|
114
|
+
self.collect_info_season(season_number)
|
|
115
|
+
|
|
116
|
+
return season.episodes.episodes
|
|
117
|
+
|
|
118
|
+
def selectEpisode(self, season_number: int, episode_index: int) -> dict:
|
|
119
|
+
"""
|
|
120
|
+
Get information for a specific episode in a specific season.
|
|
121
|
+
"""
|
|
122
|
+
episodes = self.getEpisodeSeasons(season_number)
|
|
123
|
+
if not episodes or episode_index < 0 or episode_index >= len(episodes):
|
|
124
|
+
logging.error(f"Episode index {episode_index} is out of range for season {season_number}")
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
return episodes[episode_index]
|