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.

Files changed (67) hide show
  1. StreamingCommunity/Api/Player/Helper/Vixcloud/util.py +4 -0
  2. StreamingCommunity/Api/Player/hdplayer.py +2 -2
  3. StreamingCommunity/Api/Player/mixdrop.py +1 -1
  4. StreamingCommunity/Api/Player/vixcloud.py +4 -5
  5. StreamingCommunity/Api/Site/altadefinizione/film.py +2 -2
  6. StreamingCommunity/Api/Site/altadefinizione/series.py +1 -1
  7. StreamingCommunity/Api/Site/animeunity/serie.py +1 -1
  8. StreamingCommunity/Api/Site/animeworld/film.py +1 -1
  9. StreamingCommunity/Api/Site/animeworld/serie.py +1 -2
  10. StreamingCommunity/Api/Site/cb01new/film.py +1 -1
  11. StreamingCommunity/Api/Site/crunchyroll/__init__.py +103 -0
  12. StreamingCommunity/Api/Site/crunchyroll/film.py +82 -0
  13. StreamingCommunity/Api/Site/crunchyroll/series.py +186 -0
  14. StreamingCommunity/Api/Site/crunchyroll/site.py +113 -0
  15. StreamingCommunity/Api/Site/crunchyroll/util/ScrapeSerie.py +238 -0
  16. StreamingCommunity/Api/Site/crunchyroll/util/get_license.py +227 -0
  17. StreamingCommunity/Api/Site/guardaserie/series.py +1 -2
  18. StreamingCommunity/Api/Site/guardaserie/site.py +1 -2
  19. StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +9 -8
  20. StreamingCommunity/Api/Site/mediasetinfinity/__init__.py +96 -0
  21. StreamingCommunity/Api/Site/mediasetinfinity/film.py +85 -0
  22. StreamingCommunity/Api/Site/mediasetinfinity/series.py +185 -0
  23. StreamingCommunity/Api/Site/mediasetinfinity/site.py +112 -0
  24. StreamingCommunity/Api/Site/mediasetinfinity/util/ScrapeSerie.py +259 -0
  25. StreamingCommunity/Api/Site/mediasetinfinity/util/fix_mpd.py +64 -0
  26. StreamingCommunity/Api/Site/mediasetinfinity/util/get_license.py +214 -0
  27. StreamingCommunity/Api/Site/raiplay/film.py +2 -2
  28. StreamingCommunity/Api/Site/raiplay/series.py +2 -1
  29. StreamingCommunity/Api/Site/streamingcommunity/__init__.py +6 -17
  30. StreamingCommunity/Api/Site/streamingcommunity/film.py +3 -3
  31. StreamingCommunity/Api/Site/streamingcommunity/series.py +11 -11
  32. StreamingCommunity/Api/Site/streamingcommunity/site.py +2 -4
  33. StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +3 -6
  34. StreamingCommunity/Api/Site/streamingwatch/__init__.py +6 -14
  35. StreamingCommunity/Api/Site/streamingwatch/film.py +3 -3
  36. StreamingCommunity/Api/Site/streamingwatch/series.py +9 -9
  37. StreamingCommunity/Api/Site/streamingwatch/site.py +5 -7
  38. StreamingCommunity/Api/Site/streamingwatch/util/ScrapeSerie.py +2 -2
  39. StreamingCommunity/Lib/Downloader/DASH/cdm_helpher.py +131 -0
  40. StreamingCommunity/Lib/Downloader/DASH/decrypt.py +79 -0
  41. StreamingCommunity/Lib/Downloader/DASH/downloader.py +218 -0
  42. StreamingCommunity/Lib/Downloader/DASH/parser.py +249 -0
  43. StreamingCommunity/Lib/Downloader/DASH/segments.py +332 -0
  44. StreamingCommunity/Lib/Downloader/HLS/downloader.py +10 -30
  45. StreamingCommunity/Lib/Downloader/HLS/segments.py +146 -263
  46. StreamingCommunity/Lib/Downloader/MP4/downloader.py +0 -5
  47. StreamingCommunity/Lib/FFmpeg/capture.py +3 -3
  48. StreamingCommunity/Lib/FFmpeg/command.py +1 -1
  49. StreamingCommunity/TelegramHelp/config.json +3 -7
  50. StreamingCommunity/Upload/version.py +1 -1
  51. StreamingCommunity/Util/bento4_installer.py +191 -0
  52. StreamingCommunity/Util/config_json.py +1 -1
  53. StreamingCommunity/Util/headers.py +0 -3
  54. StreamingCommunity/Util/os.py +36 -46
  55. StreamingCommunity/__init__.py +2 -1
  56. StreamingCommunity/run.py +11 -10
  57. {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/METADATA +7 -9
  58. streamingcommunity-3.2.7.dist-info/RECORD +111 -0
  59. StreamingCommunity/Api/Site/1337xx/__init__.py +0 -72
  60. StreamingCommunity/Api/Site/1337xx/site.py +0 -82
  61. StreamingCommunity/Api/Site/1337xx/title.py +0 -61
  62. StreamingCommunity/Lib/Proxies/proxy.py +0 -72
  63. streamingcommunity-3.2.1.dist-info/RECORD +0 -96
  64. {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/WHEEL +0 -0
  65. {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/entry_points.txt +0 -0
  66. {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/licenses/LICENSE +0 -0
  67. {streamingcommunity-3.2.1.dist-info → streamingcommunity-3.2.7.dist-info}/top_level.txt +0 -0
@@ -90,9 +90,13 @@ class SeasonManager:
90
90
  Parameters:
91
91
  - number (int): The season number (1-based index)
92
92
  """
93
+ if len(self.seasons) == 1:
94
+ return self.seasons[0]
95
+
93
96
  for season in self.seasons:
94
97
  if season.number == number:
95
98
  return season
99
+
96
100
  return None
97
101
 
98
102
  def __len__(self) -> int:
@@ -18,8 +18,8 @@ REQUEST_VERIFY = config_manager.get_bool('REQUESTS', 'verify')
18
18
 
19
19
 
20
20
  class VideoSource:
21
- def __init__(self, proxy=None):
22
- self.client = httpx.Client(headers={'user-agent': get_userAgent()}, timeout=MAX_TIMEOUT, proxy=proxy, verify=REQUEST_VERIFY)
21
+ def __init__(self):
22
+ self.client = httpx.Client(headers={'user-agent': get_userAgent()}, timeout=MAX_TIMEOUT, verify=REQUEST_VERIFY)
23
23
 
24
24
  def extractLinkHdPlayer(self, response):
25
25
  """Extract iframe source from the page."""
@@ -25,7 +25,7 @@ class VideoSource:
25
25
 
26
26
  def __init__(self, url: str):
27
27
  self.url = url
28
- self.redirect_url: str | None = None
28
+ self.redirect_url: str = None
29
29
  self._init_headers()
30
30
 
31
31
  def _init_headers(self) -> None:
@@ -25,7 +25,7 @@ console = Console()
25
25
 
26
26
 
27
27
  class VideoSource:
28
- def __init__(self, url: str, is_series: bool, media_id: int = None, proxy: str = None):
28
+ def __init__(self, url: str, is_series: bool, media_id: int = None):
29
29
  """
30
30
  Initialize video source for streaming site.
31
31
 
@@ -36,7 +36,6 @@ class VideoSource:
36
36
  """
37
37
  self.headers = {'user-agent': get_userAgent()}
38
38
  self.url = url
39
- self.proxy = proxy
40
39
  self.is_series = is_series
41
40
  self.media_id = media_id
42
41
  self.iframe_src = None
@@ -58,7 +57,7 @@ class VideoSource:
58
57
  }
59
58
 
60
59
  try:
61
- response = httpx.get(f"{self.url}/iframe/{self.media_id}", headers=self.headers, params=params, timeout=MAX_TIMEOUT, proxy=self.proxy, verify=REQUEST_VERIFY)
60
+ response = httpx.get(f"{self.url}/iframe/{self.media_id}", headers=self.headers, params=params, timeout=MAX_TIMEOUT, verify=REQUEST_VERIFY)
62
61
  response.raise_for_status()
63
62
 
64
63
  # Parse response with BeautifulSoup to get iframe source
@@ -123,12 +122,12 @@ class VideoSource:
123
122
  logging.error(f"Error getting content: {e}")
124
123
  raise
125
124
 
126
- def get_playlist(self) -> str | None:
125
+ def get_playlist(self) -> str:
127
126
  """
128
127
  Generate authenticated playlist URL.
129
128
 
130
129
  Returns:
131
- str | None: Fully constructed playlist URL with authentication parameters, or None if content unavailable
130
+ str: Fully constructed playlist URL with authentication parameters, or None if content unavailable
132
131
  """
133
132
  if not self.window_parameter:
134
133
  return None
@@ -12,10 +12,9 @@ from rich.console import Console
12
12
 
13
13
  # Internal utilities
14
14
  from StreamingCommunity.Util.os import os_manager
15
- from StreamingCommunity.Util.message import start_message
16
15
  from StreamingCommunity.Util.headers import get_headers
16
+ from StreamingCommunity.Util.message import start_message
17
17
  from StreamingCommunity.Util.config_json import config_manager
18
- from StreamingCommunity.Lib.Downloader import HLS_Downloader
19
18
  from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance, TelegramSession
20
19
 
21
20
 
@@ -25,6 +24,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
25
24
 
26
25
 
27
26
  # Player
27
+ from StreamingCommunity import HLS_Downloader
28
28
  from StreamingCommunity.Api.Player.supervideo import VideoSource
29
29
 
30
30
 
@@ -11,7 +11,6 @@ from rich.prompt import Prompt
11
11
 
12
12
  # Internal utilities
13
13
  from StreamingCommunity.Util.message import start_message
14
- from StreamingCommunity.Lib.Downloader import HLS_Downloader
15
14
  from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance, TelegramSession
16
15
 
17
16
 
@@ -29,6 +28,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
29
28
 
30
29
 
31
30
  # Player
31
+ from StreamingCommunity import HLS_Downloader
32
32
  from StreamingCommunity.Api.Player.supervideo import VideoSource
33
33
 
34
34
 
@@ -12,7 +12,6 @@ from rich.prompt import Prompt
12
12
  # Internal utilities
13
13
  from StreamingCommunity.Util.os import os_manager
14
14
  from StreamingCommunity.Util.message import start_message
15
- from StreamingCommunity.Lib.Downloader import MP4_downloader
16
15
  from StreamingCommunity.TelegramHelp.telegram_bot import TelegramSession, get_bot_instance
17
16
 
18
17
 
@@ -24,6 +23,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
24
23
 
25
24
 
26
25
  # Player
26
+ from StreamingCommunity import MP4_downloader
27
27
  from StreamingCommunity.Api.Player.vixcloud import VideoSourceAnime
28
28
 
29
29
 
@@ -9,7 +9,6 @@ from rich.console import Console
9
9
  # Internal utilities
10
10
  from StreamingCommunity.Util.os import os_manager
11
11
  from StreamingCommunity.Util.message import start_message
12
- from StreamingCommunity.Lib.Downloader import MP4_downloader
13
12
 
14
13
 
15
14
  # Logic class
@@ -19,6 +18,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
19
18
 
20
19
 
21
20
  # Player
21
+ from StreamingCommunity import MP4_downloader
22
22
  from StreamingCommunity.Api.Player.sweetpixel import VideoSource
23
23
 
24
24
 
@@ -1,7 +1,6 @@
1
1
  # 11.03.24
2
2
 
3
3
  import os
4
- import logging
5
4
  from typing import Tuple
6
5
 
7
6
 
@@ -13,7 +12,6 @@ from rich.prompt import Prompt
13
12
  # Internal utilities
14
13
  from StreamingCommunity.Util.os import os_manager
15
14
  from StreamingCommunity.Util.message import start_message
16
- from StreamingCommunity.Lib.Downloader import MP4_downloader
17
15
 
18
16
 
19
17
  # Logic class
@@ -24,6 +22,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
24
22
 
25
23
 
26
24
  # Player
25
+ from StreamingCommunity import MP4_downloader
27
26
  from StreamingCommunity.Api.Player.sweetpixel import VideoSource
28
27
 
29
28
 
@@ -10,7 +10,6 @@ from rich.console import Console
10
10
  # Internal utilities
11
11
  from StreamingCommunity.Util.os import os_manager
12
12
  from StreamingCommunity.Util.message import start_message
13
- from StreamingCommunity.Lib.Downloader import HLS_Downloader
14
13
 
15
14
 
16
15
  # Logic class
@@ -19,6 +18,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
19
18
 
20
19
 
21
20
  # Player
21
+ from StreamingCommunity import HLS_Downloader
22
22
  from StreamingCommunity.Api.Player.maxstream import VideoSource
23
23
 
24
24
 
@@ -0,0 +1,103 @@
1
+ # 16.03.25
2
+
3
+ import sys
4
+ import subprocess
5
+ from urllib.parse import quote_plus
6
+
7
+
8
+ # External library
9
+ from rich.console import Console
10
+ from rich.prompt import Prompt
11
+
12
+
13
+ # Internal utilities
14
+ from StreamingCommunity.Api.Template import get_select_title
15
+ from StreamingCommunity.Api.Template.config_loader import site_constant
16
+ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
17
+
18
+
19
+ # Logic class
20
+ from .site import title_search, table_show_manager, media_search_manager
21
+ from .film import download_film
22
+ from .series import download_series
23
+
24
+ # Variable
25
+ indice = 8
26
+ _useFor = "Anime"
27
+ _priority = 0
28
+ _engineDownload = "hls"
29
+ _deprecate = False
30
+
31
+ msg = Prompt()
32
+ console = Console()
33
+
34
+
35
+ def get_user_input(string_to_search: str = None):
36
+ """
37
+ Asks the user to input a search term.
38
+ Handles both Telegram bot input and direct input.
39
+ """
40
+ if string_to_search is None:
41
+ string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
42
+
43
+ return string_to_search
44
+
45
+ def process_search_result(select_title, selections=None):
46
+ """
47
+ Handles the search result and initiates the download for either a film or series.
48
+
49
+ Parameters:
50
+ select_title (MediaItem): The selected media item
51
+ selections (dict, optional): Dictionary containing selection inputs that bypass manual input
52
+ {'season': season_selection, 'episode': episode_selection}
53
+ """
54
+ if select_title.type == 'tv':
55
+ season_selection = None
56
+ episode_selection = None
57
+
58
+ if selections:
59
+ season_selection = selections.get('season')
60
+ episode_selection = selections.get('episode')
61
+
62
+ download_series(select_title, season_selection, episode_selection)
63
+
64
+ else:
65
+ download_film(select_title)
66
+
67
+ # search("Game of Thrones", selections={"season": "1", "episode": "1-3"})
68
+ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
69
+ """
70
+ Main function of the application for search.
71
+
72
+ Parameters:
73
+ string_to_search (str, optional): String to search for
74
+ get_onylDatabase (bool, optional): If True, return only the database object
75
+ direct_item (dict, optional): Direct item to process (bypass search)
76
+ selections (dict, optional): Dictionary containing selection inputs that bypass manual input
77
+ {'season': season_selection, 'episode': episode_selection}
78
+ """
79
+ if direct_item:
80
+ select_title = MediaItem(**direct_item)
81
+ process_search_result(select_title, selections)
82
+ return
83
+
84
+ # Get the user input for the search term
85
+ string_to_search = get_user_input(string_to_search)
86
+
87
+ # Perform the database search
88
+ len_database = title_search(quote_plus(string_to_search))
89
+
90
+ # If only the database is needed, return the manager
91
+ if get_onlyDatabase:
92
+ return media_search_manager
93
+
94
+ if len_database > 0:
95
+ select_title = get_select_title(table_show_manager, media_search_manager, len_database)
96
+ process_search_result(select_title, selections)
97
+
98
+ else:
99
+ console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
100
+
101
+ # If no results are found, ask again
102
+ string_to_search = get_user_input()
103
+ search(string_to_search, get_onlyDatabase, None, selections)
@@ -0,0 +1,82 @@
1
+ # 16.03.25
2
+
3
+ import os
4
+ from urllib.parse import urlparse, parse_qs
5
+
6
+
7
+ # External library
8
+ from rich.console import Console
9
+
10
+
11
+ # Internal utilities
12
+ from StreamingCommunity.Util.message import start_message
13
+ from StreamingCommunity.Util.config_json import config_manager
14
+ from StreamingCommunity.Util.os import os_manager, get_wvd_path
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 StreamingCommunity import DASH_Downloader
24
+ from .util.get_license import get_playback_session, get_auth_token, generate_device_id
25
+
26
+
27
+ # Variable
28
+ console = Console()
29
+ max_timeout = config_manager.get_int("REQUESTS", "timeout")
30
+
31
+
32
+ def download_film(select_title: MediaItem) -> str:
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 filename and path for the downloaded video
46
+ mp4_name = os_manager.get_sanitize_file(select_title.name) + ".mp4"
47
+ mp4_path = os.path.join(site_constant.MOVIE_FOLDER, mp4_name.replace(".mp4", ""))
48
+
49
+ # Generate mpd and license URLs
50
+ url_id = select_title.get('url').split('/')[-1]
51
+ device_id = generate_device_id()
52
+ mpd_url, mpd_headers = get_playback_session(get_auth_token(device_id), device_id, url_id)
53
+ parsed_url = urlparse(mpd_url)
54
+ query_params = parse_qs(parsed_url.query)
55
+
56
+ # Download the episode
57
+ r_proc = DASH_Downloader(
58
+ cdm_device=get_wvd_path(),
59
+ license_url='https://www.crunchyroll.com/license/v1/license/widevine',
60
+ mpd_url=mpd_url,
61
+ output_path=os.path.join(mp4_path, mp4_name),
62
+ )
63
+ r_proc.parse_manifest(custom_headers=mpd_headers)
64
+
65
+ # Create headers for license request
66
+ license_headers = mpd_headers.copy()
67
+ license_headers.update({
68
+ "x-cr-content-id": url_id,
69
+ "x-cr-video-token": query_params['playbackGuid'][0],
70
+ })
71
+
72
+ if r_proc.download_and_decrypt(custom_headers=license_headers):
73
+ r_proc.finalize_output()
74
+
75
+ # Get final output path and status
76
+ status = r_proc.get_status()
77
+
78
+ if status['error'] is not None and status['path']:
79
+ try: os.remove(status['path'])
80
+ except Exception: pass
81
+
82
+ return status['path'], status['stopped']
@@ -0,0 +1,186 @@
1
+ # 16.03.25
2
+
3
+ import os
4
+ from typing import Tuple
5
+ from urllib.parse import urlparse, parse_qs
6
+
7
+
8
+ # External library
9
+ from rich.console import Console
10
+ from rich.prompt import Prompt
11
+
12
+
13
+ # Internal utilities
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, delete_stream_episode
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 StreamingCommunity import DASH_Downloader
33
+ from .util.get_license import get_playback_session, get_auth_token, generate_device_id
34
+
35
+
36
+ # Variable
37
+ msg = Prompt()
38
+ console = Console()
39
+
40
+
41
+ def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: GetSerieInfo) -> Tuple[str,bool]:
42
+ """
43
+ Downloads a specific episode from a specified season.
44
+
45
+ Parameters:
46
+ - index_season_selected (int): Season number
47
+ - index_episode_selected (int): Episode index
48
+ - scrape_serie (GetSerieInfo): Scraper object with series information
49
+
50
+ Returns:
51
+ - str: Path to downloaded file
52
+ - bool: Whether download was stopped
53
+ """
54
+ start_message()
55
+
56
+ # Get episode information
57
+ obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
58
+ console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.get('name')}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
59
+
60
+ # Define filename and path for the downloaded video
61
+ mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.get('name'))}.mp4"
62
+ mp4_path = os_manager.get_sanitize_path(os.path.join(site_constant.SERIES_FOLDER, scrape_serie.series_name, f"S{index_season_selected}"))
63
+
64
+ # Generate mpd and license URLs
65
+ url_id = obj_episode.get('url').split('/')[-1]
66
+ device_id = generate_device_id()
67
+ token_mpd = get_auth_token(device_id)
68
+
69
+ mpd_url, mpd_headers = get_playback_session(token_mpd, device_id, url_id)
70
+ parsed_url = urlparse(mpd_url)
71
+ query_params = parse_qs(parsed_url.query)
72
+
73
+ # Download the episode
74
+ r_proc = DASH_Downloader(
75
+ cdm_device=get_wvd_path(),
76
+ license_url='https://www.crunchyroll.com/license/v1/license/widevine',
77
+ mpd_url=mpd_url,
78
+ output_path=os.path.join(mp4_path, mp4_name),
79
+ )
80
+ r_proc.parse_manifest(custom_headers=mpd_headers)
81
+
82
+ # Create headers for license request
83
+ license_headers = mpd_headers.copy()
84
+ license_headers.update({
85
+ "x-cr-content-id": url_id,
86
+ "x-cr-video-token": query_params['playbackGuid'][0],
87
+ })
88
+
89
+ if r_proc.download_and_decrypt(custom_headers=license_headers):
90
+ r_proc.finalize_output()
91
+
92
+ # Get final output path and status
93
+ status = r_proc.get_status()
94
+
95
+ if status['error'] is not None and status['path']:
96
+ try: os.remove(status['path'])
97
+ except Exception: pass
98
+
99
+ # Delete episode stream
100
+ delete_stream_episode(url_id, query_params['playbackGuid'][0], mpd_headers)
101
+
102
+ return status['path'], status['stopped']
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.split("/")[-1])
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
+
184
+ else:
185
+ # Otherwise, let the user select specific episodes for the single season
186
+ download_episode(i_season, scrape_serie, download_all=False, episode_selection=episode_selection)
@@ -0,0 +1,113 @@
1
+ # 16.03.25
2
+
3
+
4
+ # External libraries
5
+ from curl_cffi import requests
6
+ from rich.console import Console
7
+
8
+
9
+ # Internal utilities
10
+ from StreamingCommunity.Util.config_json import config_manager
11
+ from StreamingCommunity.Util.headers import get_headers
12
+ from StreamingCommunity.Util.table import TVShowManager
13
+
14
+
15
+ # Logic class
16
+ from StreamingCommunity.Api.Template.config_loader import site_constant
17
+ from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
18
+ from .util.get_license import get_auth_token, generate_device_id
19
+
20
+
21
+ # Variable
22
+ console = Console()
23
+ media_search_manager = MediaManager()
24
+ table_show_manager = TVShowManager()
25
+ max_timeout = config_manager.get_int("REQUESTS", "timeout")
26
+
27
+
28
+ def title_search(query: str) -> int:
29
+ """
30
+ Search for titles based on a search query.
31
+
32
+ Parameters:
33
+ - query (str): The query to search for.
34
+
35
+ Returns:
36
+ int: The number of titles found.
37
+ """
38
+ media_search_manager.clear()
39
+ table_show_manager.clear()
40
+
41
+ # Build new Crunchyroll API search URL
42
+ api_url = f"https://www.crunchyroll.com/content/v2/discover/search"
43
+
44
+ params = {
45
+ "q": query,
46
+ "n": 20,
47
+ "type": "series,movie_listing",
48
+ "ratings": "true",
49
+ "preferred_audio_language": "it-IT",
50
+ "locale": "it-IT"
51
+ }
52
+
53
+ headers = get_headers()
54
+ headers['authorization'] = f"Bearer {get_auth_token(generate_device_id()).access_token}"
55
+
56
+ console.print(f"[cyan]Search url: [yellow]{api_url}")
57
+
58
+ try:
59
+ response = requests.get(
60
+ api_url,
61
+ params=params,
62
+ headers=headers,
63
+ timeout=max_timeout,
64
+ impersonate="chrome110"
65
+ )
66
+ response.raise_for_status()
67
+
68
+ except Exception as e:
69
+ console.print(f"[red]Site: {site_constant.SITE_NAME}, request search error: {e}")
70
+ return 0
71
+
72
+ data = response.json()
73
+ found = 0
74
+
75
+ # Parse results
76
+ for block in data.get("data", []):
77
+ if block.get("type") not in ("series", "movie_listing", "top_results"):
78
+ continue
79
+
80
+ for item in block.get("items", []):
81
+ tipo = None
82
+
83
+ if item.get("type") == "movie_listing":
84
+ tipo = "film"
85
+ elif item.get("type") == "series":
86
+ meta = item.get("series_metadata", {})
87
+
88
+ if meta.get("episode_count") == 1 and meta.get("season_count", 1) == 1 and meta.get("series_launch_year"):
89
+ tipo = "film" if "film" in item.get("description", "").lower() or "movie" in item.get("description", "").lower() else "tv"
90
+ else:
91
+ tipo = "tv"
92
+
93
+ else:
94
+ continue
95
+
96
+ url = ""
97
+ if tipo == "tv":
98
+ url = f"https://www.crunchyroll.com/series/{item.get('id')}"
99
+ elif tipo == "film":
100
+ url = f"https://www.crunchyroll.com/series/{item.get('id')}"
101
+ else:
102
+ continue
103
+
104
+ title = item.get("title", "")
105
+
106
+ media_search_manager.add_media({
107
+ 'url': url,
108
+ 'name': title,
109
+ 'type': tipo
110
+ })
111
+ found += 1
112
+
113
+ return media_search_manager.get_length()