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.

Files changed (67) hide show
  1. StreamingCommunity/Api/Player/ddl.py +2 -10
  2. StreamingCommunity/Api/Player/mediapolisvod.py +64 -0
  3. StreamingCommunity/Api/Player/sweetpixel.py +3 -3
  4. StreamingCommunity/Api/Player/vixcloud.py +4 -9
  5. StreamingCommunity/Api/Site/1337xx/__init__.py +2 -3
  6. StreamingCommunity/Api/Site/1337xx/site.py +6 -1
  7. StreamingCommunity/Api/Site/altadefinizione/__init__.py +24 -9
  8. StreamingCommunity/Api/Site/altadefinizione/film.py +0 -1
  9. StreamingCommunity/Api/Site/altadefinizione/series.py +66 -70
  10. StreamingCommunity/Api/Site/altadefinizione/site.py +8 -2
  11. StreamingCommunity/Api/Site/altadefinizione/util/ScrapeSerie.py +37 -2
  12. StreamingCommunity/Api/Site/animeunity/__init__.py +30 -12
  13. StreamingCommunity/Api/Site/animeunity/film.py +40 -0
  14. StreamingCommunity/Api/Site/animeunity/serie.py +153 -0
  15. StreamingCommunity/Api/Site/animeunity/site.py +64 -37
  16. StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py +41 -22
  17. StreamingCommunity/Api/Site/animeworld/__init__.py +26 -14
  18. StreamingCommunity/Api/Site/animeworld/film.py +63 -0
  19. StreamingCommunity/Api/Site/animeworld/serie.py +25 -22
  20. StreamingCommunity/Api/Site/animeworld/site.py +8 -2
  21. StreamingCommunity/Api/Site/animeworld/util/ScrapeSerie.py +32 -5
  22. StreamingCommunity/Api/Site/cb01new/__init__.py +2 -3
  23. StreamingCommunity/Api/Site/cb01new/site.py +7 -1
  24. StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +2 -3
  25. StreamingCommunity/Api/Site/ddlstreamitaly/series.py +31 -32
  26. StreamingCommunity/Api/Site/ddlstreamitaly/site.py +8 -3
  27. StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +30 -2
  28. StreamingCommunity/Api/Site/guardaserie/__init__.py +22 -9
  29. StreamingCommunity/Api/Site/guardaserie/series.py +55 -53
  30. StreamingCommunity/Api/Site/guardaserie/site.py +10 -3
  31. StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +27 -1
  32. StreamingCommunity/Api/Site/raiplay/__init__.py +92 -0
  33. StreamingCommunity/Api/Site/raiplay/film.py +65 -0
  34. StreamingCommunity/Api/Site/raiplay/series.py +162 -0
  35. StreamingCommunity/Api/Site/raiplay/site.py +173 -0
  36. StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py +127 -0
  37. StreamingCommunity/Api/Site/streamingcommunity/__init__.py +30 -24
  38. StreamingCommunity/Api/Site/streamingcommunity/film.py +1 -2
  39. StreamingCommunity/Api/Site/streamingcommunity/series.py +76 -90
  40. StreamingCommunity/Api/Site/streamingcommunity/site.py +8 -4
  41. StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +41 -15
  42. StreamingCommunity/Api/Template/site.py +2 -2
  43. StreamingCommunity/Lib/Downloader/HLS/downloader.py +1 -1
  44. StreamingCommunity/Lib/Downloader/HLS/segments.py +9 -18
  45. StreamingCommunity/Lib/Downloader/MP4/downloader.py +2 -1
  46. StreamingCommunity/Lib/Downloader/TOR/downloader.py +7 -14
  47. StreamingCommunity/Lib/FFmpeg/capture.py +1 -5
  48. StreamingCommunity/Lib/FFmpeg/util.py +57 -19
  49. StreamingCommunity/Lib/M3U8/estimator.py +57 -41
  50. StreamingCommunity/Lib/M3U8/parser.py +26 -6
  51. StreamingCommunity/Upload/update.py +22 -3
  52. StreamingCommunity/Upload/version.py +1 -1
  53. StreamingCommunity/Util/ffmpeg_installer.py +26 -1
  54. StreamingCommunity/Util/os.py +13 -15
  55. StreamingCommunity/Util/table.py +4 -2
  56. StreamingCommunity/global_search.py +1 -4
  57. StreamingCommunity/run.py +1 -4
  58. {streamingcommunity-2.9.8.dist-info → streamingcommunity-3.0.0.dist-info}/METADATA +1 -1
  59. streamingcommunity-3.0.0.dist-info/RECORD +91 -0
  60. {streamingcommunity-2.9.8.dist-info → streamingcommunity-3.0.0.dist-info}/WHEEL +1 -1
  61. StreamingCommunity/Api/Site/animeunity/film_serie.py +0 -181
  62. StreamingCommunity/Api/Site/mostraguarda/__init__.py +0 -73
  63. StreamingCommunity/Api/Site/mostraguarda/film.py +0 -93
  64. streamingcommunity-2.9.8.dist-info/RECORD +0 -85
  65. {streamingcommunity-2.9.8.dist-info → streamingcommunity-3.0.0.dist-info}/entry_points.txt +0 -0
  66. {streamingcommunity-2.9.8.dist-info → streamingcommunity-3.0.0.dist-info}/licenses/LICENSE +0 -0
  67. {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(search_url, headers={'user-agent': get_userAgent()}, timeout=max_timeout, follow_redirects=True)
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]