StreamingCommunity 3.2.8__py3-none-any.whl → 3.2.9__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 (79) hide show
  1. StreamingCommunity/Api/Player/Helper/Vixcloud/util.py +2 -1
  2. StreamingCommunity/Api/Player/hdplayer.py +2 -2
  3. StreamingCommunity/Api/Player/sweetpixel.py +5 -8
  4. StreamingCommunity/Api/Site/altadefinizione/__init__.py +2 -2
  5. StreamingCommunity/Api/Site/altadefinizione/film.py +10 -8
  6. StreamingCommunity/Api/Site/altadefinizione/series.py +9 -7
  7. StreamingCommunity/Api/Site/altadefinizione/site.py +1 -1
  8. StreamingCommunity/Api/Site/animeunity/__init__.py +2 -2
  9. StreamingCommunity/Api/Site/animeunity/serie.py +2 -2
  10. StreamingCommunity/Api/Site/animeworld/site.py +3 -5
  11. StreamingCommunity/Api/Site/animeworld/util/ScrapeSerie.py +8 -10
  12. StreamingCommunity/Api/Site/cb01new/film.py +7 -5
  13. StreamingCommunity/Api/Site/crunchyroll/__init__.py +1 -3
  14. StreamingCommunity/Api/Site/crunchyroll/film.py +9 -7
  15. StreamingCommunity/Api/Site/crunchyroll/series.py +9 -7
  16. StreamingCommunity/Api/Site/crunchyroll/site.py +10 -1
  17. StreamingCommunity/Api/Site/guardaserie/series.py +8 -6
  18. StreamingCommunity/Api/Site/guardaserie/site.py +0 -3
  19. StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +1 -2
  20. StreamingCommunity/Api/Site/mediasetinfinity/__init__.py +1 -1
  21. StreamingCommunity/Api/Site/mediasetinfinity/film.py +10 -16
  22. StreamingCommunity/Api/Site/mediasetinfinity/series.py +12 -18
  23. StreamingCommunity/Api/Site/mediasetinfinity/site.py +11 -3
  24. StreamingCommunity/Api/Site/mediasetinfinity/util/ScrapeSerie.py +214 -180
  25. StreamingCommunity/Api/Site/mediasetinfinity/util/get_license.py +2 -31
  26. StreamingCommunity/Api/Site/raiplay/__init__.py +1 -1
  27. StreamingCommunity/Api/Site/raiplay/film.py +41 -10
  28. StreamingCommunity/Api/Site/raiplay/series.py +44 -12
  29. StreamingCommunity/Api/Site/raiplay/site.py +4 -1
  30. StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py +2 -1
  31. StreamingCommunity/Api/Site/raiplay/util/get_license.py +40 -0
  32. StreamingCommunity/Api/Site/streamingcommunity/__init__.py +0 -1
  33. StreamingCommunity/Api/Site/streamingcommunity/film.py +7 -5
  34. StreamingCommunity/Api/Site/streamingcommunity/series.py +9 -7
  35. StreamingCommunity/Api/Site/streamingcommunity/site.py +4 -2
  36. StreamingCommunity/Api/Site/streamingwatch/film.py +7 -5
  37. StreamingCommunity/Api/Site/streamingwatch/series.py +8 -6
  38. StreamingCommunity/Api/Site/streamingwatch/site.py +3 -1
  39. StreamingCommunity/Api/Site/streamingwatch/util/ScrapeSerie.py +3 -3
  40. StreamingCommunity/Api/Template/Util/__init__.py +10 -1
  41. StreamingCommunity/Api/Template/Util/manage_ep.py +4 -4
  42. StreamingCommunity/Api/Template/__init__.py +5 -1
  43. StreamingCommunity/Api/Template/site.py +10 -6
  44. StreamingCommunity/Lib/Downloader/DASH/cdm_helpher.py +5 -12
  45. StreamingCommunity/Lib/Downloader/DASH/decrypt.py +1 -1
  46. StreamingCommunity/Lib/Downloader/DASH/downloader.py +1 -1
  47. StreamingCommunity/Lib/Downloader/DASH/parser.py +1 -1
  48. StreamingCommunity/Lib/Downloader/DASH/segments.py +4 -3
  49. StreamingCommunity/Lib/Downloader/HLS/downloader.py +11 -9
  50. StreamingCommunity/Lib/Downloader/HLS/segments.py +4 -9
  51. StreamingCommunity/Lib/Downloader/MP4/downloader.py +4 -3
  52. StreamingCommunity/Lib/Downloader/TOR/downloader.py +3 -5
  53. StreamingCommunity/Lib/Downloader/__init__.py +9 -1
  54. StreamingCommunity/Lib/FFmpeg/__init__.py +10 -1
  55. StreamingCommunity/Lib/FFmpeg/command.py +4 -6
  56. StreamingCommunity/Lib/FFmpeg/util.py +1 -1
  57. StreamingCommunity/Lib/M3U8/__init__.py +9 -1
  58. StreamingCommunity/Lib/M3U8/decryptor.py +8 -4
  59. StreamingCommunity/Lib/M3U8/estimator.py +0 -6
  60. StreamingCommunity/Lib/M3U8/parser.py +1 -1
  61. StreamingCommunity/Lib/M3U8/url_fixer.py +1 -1
  62. StreamingCommunity/Lib/TMBD/__init__.py +6 -1
  63. StreamingCommunity/TelegramHelp/config.json +1 -5
  64. StreamingCommunity/TelegramHelp/telegram_bot.py +9 -10
  65. StreamingCommunity/Upload/version.py +2 -2
  66. StreamingCommunity/Util/config_json.py +139 -59
  67. StreamingCommunity/Util/http_client.py +201 -0
  68. StreamingCommunity/Util/message.py +1 -1
  69. StreamingCommunity/Util/os.py +5 -5
  70. StreamingCommunity/Util/table.py +3 -3
  71. StreamingCommunity/__init__.py +9 -1
  72. StreamingCommunity/run.py +396 -260
  73. {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.2.9.dist-info}/METADATA +143 -45
  74. streamingcommunity-3.2.9.dist-info/RECORD +113 -0
  75. streamingcommunity-3.2.8.dist-info/RECORD +0 -111
  76. {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.2.9.dist-info}/WHEEL +0 -0
  77. {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.2.9.dist-info}/entry_points.txt +0 -0
  78. {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.2.9.dist-info}/licenses/LICENSE +0 -0
  79. {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.2.9.dist-info}/top_level.txt +0 -0
@@ -10,11 +10,15 @@ from rich.prompt import Prompt
10
10
 
11
11
 
12
12
  # Internal utilities
13
+ from StreamingCommunity.Util.headers import get_headers
14
+ from StreamingCommunity.Util.os import get_wvd_path
13
15
  from StreamingCommunity.Util.message import start_message
14
16
 
15
17
 
18
+
16
19
  # Logic class
17
20
  from .util.ScrapeSerie import GetSerieInfo
21
+ from .util.get_license import generate_license_url
18
22
  from StreamingCommunity.Api.Template.Util import (
19
23
  manage_selection,
20
24
  map_episode_title,
@@ -27,7 +31,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
27
31
 
28
32
 
29
33
  # Player
30
- from StreamingCommunity import HLS_Downloader
34
+ from StreamingCommunity import HLS_Downloader, DASH_Downloader
31
35
  from StreamingCommunity.Api.Player.mediapolisvod import VideoSource
32
36
 
33
37
 
@@ -53,24 +57,52 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
53
57
 
54
58
  # Get episode information
55
59
  obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
56
- 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")
57
-
58
- # Get streaming URL
59
- master_playlist = VideoSource.extract_m3u8_url(obj_episode.url)
60
+ console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{scrape_serie.series_name}[/cyan] \\ [bold magenta]{obj_episode.name}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
60
61
 
61
62
  # Define filename and path
62
63
  mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4"
63
64
  mp4_path = os.path.join(site_constant.SERIES_FOLDER, scrape_serie.series_name, f"S{index_season_selected}")
64
65
 
65
- # Download the episode
66
- r_proc = HLS_Downloader(
67
- m3u8_url=master_playlist,
68
- output_path=os.path.join(mp4_path, mp4_name)
69
- ).start()
66
+ # Get streaming URL
67
+ master_playlist = VideoSource.extract_m3u8_url(obj_episode.url)
68
+
69
+ # HLS
70
+ if ".mpd" not in master_playlist:
71
+ r_proc = HLS_Downloader(
72
+ m3u8_url=master_playlist,
73
+ output_path=os.path.join(mp4_path, mp4_name)
74
+ ).start()
75
+
76
+ # MPD
77
+ else:
78
+
79
+ # Check CDM file before usage
80
+ cdm_device_path = get_wvd_path()
81
+ if not cdm_device_path or not isinstance(cdm_device_path, (str, bytes, os.PathLike)) or not os.path.isfile(cdm_device_path):
82
+ console.print(f"[bold red] CDM file not found or invalid path: {cdm_device_path}[/bold red]")
83
+ return None
84
+
85
+ license_url = generate_license_url(obj_episode.mpd_id)
86
+
87
+ dash_process = DASH_Downloader(
88
+ cdm_device=cdm_device_path,
89
+ license_url=license_url,
90
+ mpd_url=master_playlist,
91
+ output_path=os.path.join(mp4_path, mp4_name),
92
+ )
93
+ dash_process.parse_manifest(custom_headers=get_headers())
94
+
95
+ if dash_process.download_and_decrypt():
96
+ dash_process.finalize_output()
97
+
98
+ # Get final output path and status
99
+ r_proc = dash_process.get_status()
70
100
 
71
101
  if r_proc['error'] is not None:
72
- try: os.remove(r_proc['path'])
73
- except: pass
102
+ try:
103
+ os.remove(r_proc['path'])
104
+ except Exception:
105
+ pass
74
106
 
75
107
  return r_proc['path'], r_proc['stopped']
76
108
 
@@ -11,6 +11,9 @@ from StreamingCommunity.Util.headers import get_userAgent
11
11
  from StreamingCommunity.Util.table import TVShowManager
12
12
  from StreamingCommunity.Api.Template.config_loader import site_constant
13
13
  from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
14
+
15
+
16
+ # Logic Import
14
17
  from .util.ScrapeSerie import GetSerieInfo
15
18
 
16
19
 
@@ -59,7 +62,7 @@ def title_search(query: str) -> int:
59
62
  media_search_manager.clear()
60
63
  table_show_manager.clear()
61
64
 
62
- search_url = f"https://www.raiplay.it/atomatic/raiplay-search-service/api/v1/msearch"
65
+ search_url = "https://www.raiplay.it/atomatic/raiplay-search-service/api/v1/msearch"
63
66
  console.print(f"[cyan]Search url: [yellow]{search_url}")
64
67
 
65
68
  json_data = {
@@ -101,7 +101,8 @@ class GetSerieInfo:
101
101
  'number': ep.get('episode', ''),
102
102
  'name': ep.get('episode_title', '') or ep.get('toptitle', ''),
103
103
  'duration': ep.get('duration', ''),
104
- 'url': f"{self.base_url}{ep.get('weblink', '')}" if 'weblink' in ep else f"{self.base_url}{ep.get('url', '')}"
104
+ 'url': f"{self.base_url}{ep.get('weblink', '')}" if 'weblink' in ep else f"{self.base_url}{ep.get('url', '')}",
105
+ 'mpd_id': ep.get('video_url').split("=")[1].strip()
105
106
  }
106
107
  season.episodes.add(episode)
107
108
 
@@ -0,0 +1,40 @@
1
+ # 16.03.25
2
+
3
+
4
+ # External library
5
+ import httpx
6
+
7
+
8
+ # Internal utilities
9
+ from StreamingCommunity.Util.config_json import config_manager
10
+ from StreamingCommunity.Util.headers import get_headers
11
+
12
+
13
+ # Variable
14
+ MAX_TIMEOUT = config_manager.get_int("REQUESTS", "timeout")
15
+
16
+
17
+
18
+ def generate_license_url(mpd_id: str):
19
+ """
20
+ Generates the URL to obtain the Widevine license.
21
+
22
+ Args:
23
+ mpd_id (str): The ID of the MPD (Media Presentation Description) file.
24
+
25
+ Returns:
26
+ str: The full license URL.
27
+ """
28
+ params = {
29
+ 'cont': mpd_id,
30
+ 'output': '62',
31
+ }
32
+
33
+ response = httpx.get('https://mediapolisvod.rai.it/relinker/relinkerServlet.htm', params=params, headers=get_headers(), timeout=MAX_TIMEOUT)
34
+ response.raise_for_status()
35
+
36
+ # Extract the license URL from the response in two lines
37
+ json_data = response.json()
38
+ license_url = json_data.get('licence_server_map').get('drmLicenseUrlValues')[0].get('licenceUrl')
39
+
40
+ return license_url
@@ -2,7 +2,6 @@
2
2
 
3
3
  import sys
4
4
  import subprocess
5
- from urllib.parse import quote_plus
6
5
 
7
6
 
8
7
  # External library
@@ -71,7 +71,7 @@ def download_film(select_title: MediaItem) -> str:
71
71
  mp4_path = os.path.join(site_constant.MOVIE_FOLDER, title_name.replace(".mp4", ""))
72
72
 
73
73
  # Download the film using the m3u8 playlist, and output filename
74
- r_proc = HLS_Downloader(
74
+ hls_process = HLS_Downloader(
75
75
  m3u8_url=master_playlist,
76
76
  output_path=os.path.join(mp4_path, title_name)
77
77
  ).start()
@@ -83,8 +83,10 @@ def download_film(select_title: MediaItem) -> str:
83
83
  if script_id != "unknown":
84
84
  TelegramSession.deleteScriptId(script_id)
85
85
 
86
- if r_proc['error'] is not None:
87
- try: os.remove(r_proc['path'])
88
- except: pass
86
+ if hls_process['error'] is not None:
87
+ try:
88
+ os.remove(hls_process['path'])
89
+ except Exception:
90
+ pass
89
91
 
90
- return r_proc['path']
92
+ return hls_process['path']
@@ -55,7 +55,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
55
55
 
56
56
  # Get episode information
57
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.name}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
58
+ console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{scrape_serie.series_name}[/cyan] \\ [bold magenta]{obj_episode.name}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
59
59
 
60
60
  if site_constant.TELEGRAM_BOT:
61
61
  bot = get_bot_instance()
@@ -81,16 +81,18 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
81
81
  master_playlist = video_source.get_playlist()
82
82
 
83
83
  # Download the episode
84
- r_proc = HLS_Downloader(
84
+ hls_process = HLS_Downloader(
85
85
  m3u8_url=master_playlist,
86
86
  output_path=os.path.join(mp4_path, mp4_name)
87
87
  ).start()
88
88
 
89
- if r_proc['error'] is not None:
90
- try: os.remove(r_proc['path'])
91
- except: pass
89
+ if hls_process['error'] is not None:
90
+ try:
91
+ os.remove(hls_process['path'])
92
+ except Exception:
93
+ pass
92
94
 
93
- return r_proc['path'], r_proc['stopped']
95
+ return hls_process['path'], hls_process['stopped']
94
96
 
95
97
 
96
98
  def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, video_source: VideoSource, download_all: bool = False, episode_selection: str = None) -> None:
@@ -214,7 +216,7 @@ def download_series(select_season: MediaItem, season_selection: str = None, epis
214
216
  download_episode(season_number, scrape_serie, video_source, download_all=False, episode_selection=episode_selection)
215
217
 
216
218
  if site_constant.TELEGRAM_BOT:
217
- bot.send_message(f"Finito di scaricare tutte le serie e episodi", None)
219
+ bot.send_message("Finito di scaricare tutte le serie e episodi", None)
218
220
 
219
221
  # Get script_id
220
222
  script_id = TelegramSession.get_session()
@@ -57,7 +57,9 @@ def title_search(query: str) -> int:
57
57
  version = json.loads(soup.find('div', {'id': "app"}).get("data-page"))['version']
58
58
 
59
59
  except Exception as e:
60
- if "WinError" in str(e) or "Errno" in str(e): console.print("\n[bold yellow]Please make sure you have enabled and configured a valid proxy.[/bold yellow]")
60
+ if "WinError" in str(e) or "Errno" in str(e):
61
+ console.print("\n[bold yellow]Please make sure you have enabled and configured a valid proxy.[/bold yellow]")
62
+
61
63
  console.print(f"[red]Site: {site_constant.SITE_NAME} version, request error: {e}")
62
64
  return 0
63
65
 
@@ -116,7 +118,7 @@ def title_search(query: str) -> int:
116
118
 
117
119
  if site_constant.TELEGRAM_BOT:
118
120
  if choices:
119
- bot.send_message(f"Lista dei risultati:", choices)
121
+ bot.send_message("Lista dei risultati:", choices)
120
122
 
121
123
  # Return the number of titles found
122
124
  return media_search_manager.get_length()
@@ -49,13 +49,15 @@ def download_film(select_title: MediaItem) -> str:
49
49
  mp4_path = os.path.join(site_constant.MOVIE_FOLDER, title_name.replace(".mp4", ""))
50
50
 
51
51
  # Download the film using the m3u8 playlist, and output filename
52
- r_proc = HLS_Downloader(
52
+ hls_process = HLS_Downloader(
53
53
  m3u8_url=master_playlist,
54
54
  output_path=os.path.join(mp4_path, title_name)
55
55
  ).start()
56
56
 
57
- if r_proc['error'] is not None:
58
- try: os.remove(r_proc['path'])
59
- except: pass
57
+ if hls_process['error'] is not None:
58
+ try:
59
+ os.remove(hls_process['path'])
60
+ except Exception:
61
+ pass
60
62
 
61
- return r_proc['path']
63
+ return hls_process['path']
@@ -53,7 +53,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
53
53
 
54
54
  # Get episode information
55
55
  obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
56
- 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
+ console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{scrape_serie.series_name}[/cyan] \\ [bold magenta]{obj_episode.name}[/bold magenta] ([cyan]S{index_season_selected}E{index_episode_selected}[/cyan]) \n")
57
57
 
58
58
  # Define filename and path for the downloaded video
59
59
  mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4"
@@ -64,16 +64,18 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
64
64
  master_playlist = video_source.get_m3u8_url(obj_episode.url)
65
65
 
66
66
  # Download the episode
67
- r_proc = HLS_Downloader(
67
+ hls_process = HLS_Downloader(
68
68
  m3u8_url=master_playlist,
69
69
  output_path=os.path.join(mp4_path, mp4_name)
70
70
  ).start()
71
71
 
72
- if r_proc['error'] is not None:
73
- try: os.remove(r_proc['path'])
74
- except: pass
72
+ if hls_process['error'] is not None:
73
+ try:
74
+ os.remove(hls_process['path'])
75
+ except Exception:
76
+ pass
75
77
 
76
- return r_proc['path'], r_proc['stopped']
78
+ return hls_process['path'], hls_process['stopped']
77
79
 
78
80
 
79
81
  def download_episode(index_season_selected: int, scrape_serie: GetSerieInfo, download_all: bool = False, episode_selection: str = None) -> None:
@@ -86,7 +86,9 @@ def title_search(query: str) -> int:
86
86
  soup = BeautifulSoup(response.text, 'html.parser')
87
87
 
88
88
  except Exception as e:
89
- if "WinError" in str(e) or "Errno" in str(e): console.print("\n[bold yellow]Please make sure you have enabled and configured a valid proxy.[/bold yellow]")
89
+ if "WinError" in str(e) or "Errno" in str(e):
90
+ console.print("\n[bold yellow]Please make sure you have enabled and configured a valid proxy.[/bold yellow]")
91
+
90
92
  console.print(f"[red]Site: {site_constant.SITE_NAME}, request search error: {e}")
91
93
  return 0
92
94
 
@@ -5,11 +5,12 @@ import logging
5
5
 
6
6
 
7
7
  # External libraries
8
- import httpx
9
8
  from bs4 import BeautifulSoup
10
9
 
10
+
11
11
  # Internal utilities
12
12
  from StreamingCommunity.Util.headers import get_userAgent
13
+ from StreamingCommunity.Util.http_client import create_client
13
14
  from StreamingCommunity.Util.config_json import config_manager
14
15
  from StreamingCommunity.Api.Player.Helper.Vixcloud.util import SeasonManager, Episode
15
16
 
@@ -24,8 +25,7 @@ class GetSerieInfo:
24
25
  self.url = url
25
26
  self.seasons_manager = SeasonManager()
26
27
  self.series_name = None
27
-
28
- self.client = httpx.Client(headers=self.headers, timeout=max_timeout)
28
+ self.client = create_client(headers=self.headers)
29
29
 
30
30
  def collect_info_season(self) -> None:
31
31
  """
@@ -7,4 +7,13 @@ from .manage_ep import (
7
7
  validate_selection,
8
8
  dynamic_format_number,
9
9
  display_episodes_list
10
- )
10
+ )
11
+
12
+ __all__ = [
13
+ "manage_selection",
14
+ "map_episode_title",
15
+ "validate_episode_selection",
16
+ "validate_selection",
17
+ "dynamic_format_number",
18
+ "display_episodes_list"
19
+ ]
@@ -125,20 +125,20 @@ def map_episode_title(tv_name: str, number_season: int, episode_number: int, epi
125
125
  """
126
126
  map_episode_temp = MAP_EPISODE
127
127
 
128
- if tv_name != None:
128
+ if tv_name is not None:
129
129
  map_episode_temp = map_episode_temp.replace("%(tv_name)", os_manager.get_sanitize_file(tv_name))
130
130
 
131
- if number_season != None:
131
+ if number_season is not None:
132
132
  map_episode_temp = map_episode_temp.replace("%(season)", str(number_season))
133
133
  else:
134
134
  map_episode_temp = map_episode_temp.replace("%(season)", dynamic_format_number(str(0)))
135
135
 
136
- if episode_number != None:
136
+ if episode_number is not None:
137
137
  map_episode_temp = map_episode_temp.replace("%(episode)", dynamic_format_number(str(episode_number)))
138
138
  else:
139
139
  map_episode_temp = map_episode_temp.replace("%(episode)", dynamic_format_number(str(0)))
140
140
 
141
- if episode_name != None:
141
+ if episode_name is not None:
142
142
  map_episode_temp = map_episode_temp.replace("%(episode_name)", os_manager.get_sanitize_file(episode_name))
143
143
 
144
144
  logging.info(f"Map episode string return: {map_episode_temp}")
@@ -1,3 +1,7 @@
1
1
  # 19.06.24
2
2
 
3
- from .site import get_select_title
3
+ from .site import get_select_title
4
+
5
+ __all__ = [
6
+ "get_select_title"
7
+ ]
@@ -1,8 +1,5 @@
1
1
  # 19.06.24
2
2
 
3
- import sys
4
-
5
-
6
3
  # External library
7
4
  from rich.console import Console
8
5
 
@@ -83,12 +80,19 @@ def get_select_title(table_show_manager, media_search_manager, num_results_avail
83
80
 
84
81
  color_index = 1
85
82
  for key in first_media_item.__dict__.keys():
83
+
86
84
  if key.capitalize() in column_to_hide:
87
85
  continue
86
+
88
87
  if key in ('id', 'type', 'name', 'score'):
89
- if key == 'type': column_info["Type"] = {'color': 'yellow'}
90
- elif key == 'name': column_info["Name"] = {'color': 'magenta'}
91
- elif key == 'score': column_info["Score"] = {'color': 'cyan'}
88
+ if key == 'type':
89
+ column_info["Type"] = {'color': 'yellow'}
90
+
91
+ elif key == 'name':
92
+ column_info["Name"] = {'color': 'magenta'}
93
+ elif key == 'score':
94
+ column_info["Score"] = {'color': 'cyan'}
95
+
92
96
  else:
93
97
  column_info[key.capitalize()] = {'color': available_colors[color_index % len(available_colors)]}
94
98
  color_index += 1
@@ -2,7 +2,6 @@
2
2
 
3
3
  import base64
4
4
  import logging
5
- import os
6
5
 
7
6
 
8
7
  # External libraries
@@ -31,16 +30,11 @@ def get_widevine_keys(pssh, license_url, cdm_device_path, headers=None, payload=
31
30
  list: List of dicts {'kid': ..., 'key': ...} (only CONTENT keys) or None if error.
32
31
  """
33
32
 
34
- # Check if CDM file exists
35
- if not os.path.isfile(cdm_device_path):
36
- console.print(f"[bold red] CDM file not found: {cdm_device_path}[/bold red]")
37
- return None
38
-
39
33
  # Check if PSSH is a valid base64 string
40
34
  try:
41
35
  base64.b64decode(pssh)
42
36
  except Exception:
43
- console.print(f"[bold red] Invalid PSSH base64 string.[/bold red]")
37
+ console.print("[bold red] Invalid PSSH base64 string.[/bold red]")
44
38
  return None
45
39
 
46
40
  try:
@@ -67,7 +61,7 @@ def get_widevine_keys(pssh, license_url, cdm_device_path, headers=None, payload=
67
61
  response = httpx.post(license_url, data=challenge, headers=req_headers, content=payload)
68
62
 
69
63
  if response.status_code != 200:
70
- console.print(f"[bold red]License error:[/bold red] {response.status_code}")
64
+ console.print(f"[bold red]License error:[/bold red] {response.status_code}, {response.text}")
71
65
  return None
72
66
 
73
67
  # Handle (JSON) or classic (binary) license response
@@ -77,18 +71,17 @@ def get_widevine_keys(pssh, license_url, cdm_device_path, headers=None, payload=
77
71
 
78
72
  # Check if license_data is empty
79
73
  if not license_data:
80
- console.print(f"[bold red]License response is empty.[/bold red]")
74
+ console.print("[bold red]License response is empty.[/bold red]")
81
75
  return None
82
76
 
83
77
  if "application/json" in content_type:
84
78
  try:
85
79
 
86
80
  # Try to decode as JSON only if plausible
87
- text = response.text
88
81
  data = None
89
82
  try:
90
83
  data = response.json()
91
- except Exception as e:
84
+ except Exception:
92
85
  data = None
93
86
 
94
87
  if data and "license" in data:
@@ -118,7 +111,7 @@ def get_widevine_keys(pssh, license_url, cdm_device_path, headers=None, payload=
118
111
 
119
112
  # Check if content_keys list is empty
120
113
  if not content_keys:
121
- console.print(f"[bold yellow]⚠️ No CONTENT keys found in license.[/bold yellow]")
114
+ console.print("[bold yellow]⚠️ No CONTENT keys found in license.[/bold yellow]")
122
115
  return None
123
116
 
124
117
  return content_keys
@@ -38,7 +38,7 @@ def decrypt_with_mp4decrypt(encrypted_path, kid, key, output_path=None, cleanup=
38
38
  bytes.fromhex(kid)
39
39
  bytes.fromhex(key)
40
40
  except Exception:
41
- console.print(f"[bold red] Invalid KID or KEY (not hex).[/bold red]")
41
+ console.print("[bold red] Invalid KID or KEY (not hex).[/bold red]")
42
42
  return None
43
43
 
44
44
  if not output_path:
@@ -22,7 +22,7 @@ from .cdm_helpher import get_widevine_keys
22
22
 
23
23
  # Config
24
24
  DOWNLOAD_SPECIFIC_AUDIO = config_manager.get_list('M3U8_DOWNLOAD', 'specific_list_audio')
25
- FILTER_CUSTOM_REOLUTION = str(config_manager.get('M3U8_PARSER', 'force_resolution')).strip().lower()
25
+ FILTER_CUSTOM_REOLUTION = str(config_manager.get('M3U8_CONVERSION', 'force_resolution')).strip().lower()
26
26
  CLEANUP_TMP = config_manager.get_bool('M3U8_DOWNLOAD', 'cleanup_tmp_folder')
27
27
 
28
28
 
@@ -61,7 +61,7 @@ class MPDParser:
61
61
  self.base_url = mpd_url.rsplit('/', 1)[0] + '/'
62
62
 
63
63
  def parse(self, custom_headers):
64
- response = httpx.get(self.mpd_url, headers=custom_headers, timeout=max_timeout)
64
+ response = httpx.get(self.mpd_url, headers=custom_headers, timeout=max_timeout, follow_redirects=True)
65
65
  response.raise_for_status()
66
66
 
67
67
  root = ET.fromstring(response.content)
@@ -134,7 +134,7 @@ class MPD_Segments:
134
134
 
135
135
  try:
136
136
  headers = {'User-Agent': get_userAgent()}
137
- response = await client.get(init_url, headers=headers)
137
+ response = await client.get(init_url, headers=headers, follow_redirects=True)
138
138
 
139
139
  with open(concat_path, 'wb') as outfile:
140
140
  if response.status_code == 200:
@@ -160,7 +160,8 @@ class MPD_Segments:
160
160
  headers = {'User-Agent': get_userAgent()}
161
161
  for attempt in range(max_retry):
162
162
  try:
163
- resp = await client.get(url, headers=headers)
163
+ resp = await client.get(url, headers=headers, follow_redirects=True)
164
+
164
165
  if resp.status_code == 200:
165
166
  return idx, resp.content, attempt
166
167
  else:
@@ -214,7 +215,7 @@ class MPD_Segments:
214
215
  for attempt in range(max_retry):
215
216
  try:
216
217
  resp = await client.get(url, headers=headers)
217
-
218
+
218
219
  if resp.status_code == 200:
219
220
  return idx, resp.content, attempt
220
221
  else:
@@ -17,6 +17,7 @@ from rich.panel import Panel
17
17
  # Internal utilities
18
18
  from StreamingCommunity.Util.config_json import config_manager
19
19
  from StreamingCommunity.Util.headers import get_userAgent
20
+ from StreamingCommunity.Util.http_client import create_client
20
21
  from StreamingCommunity.Util.os import os_manager, internet_manager
21
22
  from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
22
23
 
@@ -38,7 +39,7 @@ DOWNLOAD_SPECIFIC_AUDIO = config_manager.get_list('M3U8_DOWNLOAD', 'specific_lis
38
39
  DOWNLOAD_SPECIFIC_SUBTITLE = config_manager.get_list('M3U8_DOWNLOAD', 'specific_list_subtitles')
39
40
  MERGE_SUBTITLE = config_manager.get_bool('M3U8_DOWNLOAD', 'merge_subs')
40
41
  CLEANUP_TMP = config_manager.get_bool('M3U8_DOWNLOAD', 'cleanup_tmp_folder')
41
- FILTER_CUSTOM_REOLUTION = str(config_manager.get('M3U8_PARSER', 'force_resolution')).strip().lower()
42
+ FILTER_CUSTOM_RESOLUTION = str(config_manager.get('M3U8_CONVERSION', 'force_resolution')).strip().lower()
42
43
  RETRY_LIMIT = config_manager.get_int('REQUESTS', 'max_retry')
43
44
  MAX_TIMEOUT = config_manager.get_int("REQUESTS", "timeout")
44
45
  TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
@@ -62,7 +63,8 @@ class HLSClient:
62
63
  Returns:
63
64
  Response content/text or None if all retries fail
64
65
  """
65
- client = httpx.Client(headers=self.headers, timeout=MAX_TIMEOUT, follow_redirects=True)
66
+ # Use unified HTTP client (inherits timeout/verify/proxy from config)
67
+ client = create_client(headers=self.headers)
66
68
 
67
69
  for attempt in range(RETRY_LIMIT):
68
70
  try:
@@ -154,12 +156,12 @@ class M3U8Manager:
154
156
  self.sub_streams = []
155
157
 
156
158
  else:
157
- if str(FILTER_CUSTOM_REOLUTION) == "best":
159
+ if str(FILTER_CUSTOM_RESOLUTION) == "best":
158
160
  self.video_url, self.video_res = self.parser._video.get_best_uri()
159
- elif str(FILTER_CUSTOM_REOLUTION) == "worst":
161
+ elif str(FILTER_CUSTOM_RESOLUTION) == "worst":
160
162
  self.video_url, self.video_res = self.parser._video.get_worst_uri()
161
- elif str(FILTER_CUSTOM_REOLUTION).replace("p", "").replace("px", "").isdigit():
162
- resolution_value = int(str(FILTER_CUSTOM_REOLUTION).replace("p", "").replace("px", ""))
163
+ elif str(FILTER_CUSTOM_RESOLUTION).replace("p", "").replace("px", "").isdigit():
164
+ resolution_value = int(str(FILTER_CUSTOM_RESOLUTION).replace("p", "").replace("px", ""))
163
165
  self.video_url, self.video_res = self.parser._video.get_custom_uri(resolution_value)
164
166
  else:
165
167
  logging.error("Resolution not recognized.")
@@ -188,7 +190,7 @@ class M3U8Manager:
188
190
 
189
191
  console.print(
190
192
  f"[cyan bold]Video [/cyan bold] [green]Available:[/green] [purple]{', '.join(list_available_resolution)}[/purple] | "
191
- f"[red]Set:[/red] [purple]{FILTER_CUSTOM_REOLUTION}[/purple] | "
193
+ f"[red]Set:[/red] [purple]{FILTER_CUSTOM_RESOLUTION}[/purple] | "
192
194
  f"[yellow]Downloadable:[/yellow] [purple]{self.video_res[0]}x{self.video_res[1]}[/purple]"
193
195
  )
194
196
 
@@ -418,7 +420,7 @@ class HLS_Downloader:
418
420
  - is_master: Whether the M3U8 was a master playlist
419
421
  Or raises an exception if there's an error
420
422
  """
421
- console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
423
+ console.print("[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
422
424
 
423
425
  if TELEGRAM_BOT:
424
426
  bot = get_bot_instance()
@@ -435,7 +437,7 @@ class HLS_Downloader:
435
437
  'stopped': False
436
438
  }
437
439
  if TELEGRAM_BOT:
438
- bot.send_message(f"Contenuto già scaricato!", None)
440
+ bot.send_message("Contenuto già scaricato!", None)
439
441
  return response
440
442
 
441
443
  self.path_manager.setup_directories()