StreamingCommunity 2.9.1__py3-none-any.whl → 2.9.3__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 (28) hide show
  1. StreamingCommunity/Api/Player/Helper/Vixcloud/util.py +1 -6
  2. StreamingCommunity/Api/Player/maxstream.py +3 -0
  3. StreamingCommunity/Api/Player/supervideo.py +4 -0
  4. StreamingCommunity/Api/Site/1337xx/__init__.py +1 -1
  5. StreamingCommunity/Api/Site/animeunity/film_serie.py +1 -1
  6. StreamingCommunity/Api/Site/animeunity/site.py +14 -32
  7. StreamingCommunity/Api/Site/cb01new/__init__.py +1 -1
  8. StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +1 -1
  9. StreamingCommunity/Api/Site/guardaserie/__init__.py +1 -1
  10. StreamingCommunity/Api/Site/guardaserie/series.py +1 -1
  11. StreamingCommunity/Api/Site/mostraguarda/__init__.py +1 -1
  12. StreamingCommunity/Api/Site/streamingcommunity/series.py +1 -1
  13. StreamingCommunity/Api/Template/Util/manage_ep.py +42 -16
  14. StreamingCommunity/Api/Template/site.py +1 -1
  15. StreamingCommunity/Lib/Downloader/HLS/downloader.py +13 -2
  16. StreamingCommunity/Lib/Downloader/HLS/segments.py +37 -11
  17. StreamingCommunity/Lib/Downloader/MP4/downloader.py +2 -1
  18. StreamingCommunity/Lib/FFmpeg/command.py +2 -2
  19. StreamingCommunity/Lib/FFmpeg/util.py +11 -15
  20. StreamingCommunity/Lib/M3U8/estimator.py +1 -1
  21. StreamingCommunity/Lib/TMBD/tmdb.py +1 -1
  22. StreamingCommunity/Upload/version.py +1 -1
  23. {StreamingCommunity-2.9.1.dist-info → StreamingCommunity-2.9.3.dist-info}/METADATA +2 -1
  24. {StreamingCommunity-2.9.1.dist-info → StreamingCommunity-2.9.3.dist-info}/RECORD +28 -28
  25. {StreamingCommunity-2.9.1.dist-info → StreamingCommunity-2.9.3.dist-info}/LICENSE +0 -0
  26. {StreamingCommunity-2.9.1.dist-info → StreamingCommunity-2.9.3.dist-info}/WHEEL +0 -0
  27. {StreamingCommunity-2.9.1.dist-info → StreamingCommunity-2.9.3.dist-info}/entry_points.txt +0 -0
  28. {StreamingCommunity-2.9.1.dist-info → StreamingCommunity-2.9.3.dist-info}/top_level.txt +0 -0
@@ -8,14 +8,12 @@ class Episode:
8
8
  self.data = data
9
9
 
10
10
  self.id: int = data.get('id', 0)
11
- self.scws_id: int = data.get('scws_id', 0)
12
11
  self.number: int = data.get('number', 1)
13
12
  self.name: str = data.get('name', '')
14
- self.plot: str = data.get('plot', '')
15
13
  self.duration: int = data.get('duration', 0)
16
14
 
17
15
  def __str__(self):
18
- return f"Episode(id={self.id}, number={self.number}, name='{self.name}', plot='{self.plot}', duration={self.duration} sec)"
16
+ return f"Episode(id={self.id}, number={self.number}, name='{self.name}', duration={self.duration} sec)"
19
17
 
20
18
  class EpisodeManager:
21
19
  def __init__(self):
@@ -89,12 +87,9 @@ class Season:
89
87
  self.season_data = season_data
90
88
 
91
89
  self.id: int = season_data.get('id', 0)
92
- self.scws_id: int = season_data.get('scws_id', 0)
93
- self.imdb_id: int = season_data.get('imdb_id', 0)
94
90
  self.number: int = season_data.get('number', 0)
95
91
  self.name: str = season_data.get('name', '')
96
92
  self.slug: str = season_data.get('slug', '')
97
- self.plot: str = season_data.get('plot', '')
98
93
  self.type: str = season_data.get('type', '')
99
94
  self.seasons_count: int = season_data.get('seasons_count', 0)
100
95
 
@@ -130,6 +130,9 @@ class VideoSource:
130
130
  logging.info(f"M3U8 URL: {self.m3u8_url}")
131
131
  break
132
132
 
133
+ else:
134
+ logging.error("Failed to find M3U8 URL: No match found")
135
+
133
136
  return self.m3u8_url
134
137
 
135
138
  except Exception as e:
@@ -119,6 +119,8 @@ class VideoSource:
119
119
 
120
120
  if match:
121
121
  return match.group(1)
122
+ else:
123
+ logging.error("Failed to find M3U8 URL: No match found")
122
124
 
123
125
  else:
124
126
 
@@ -151,6 +153,8 @@ class VideoSource:
151
153
 
152
154
  if match:
153
155
  return match.group(1)
156
+ else:
157
+ logging.error("Failed to find M3U8 URL: No match found")
154
158
 
155
159
  return None
156
160
 
@@ -19,7 +19,7 @@ from .title import download_title
19
19
 
20
20
 
21
21
  # Variable
22
- indice = 8
22
+ indice = 3
23
23
  _useFor = "film_serie"
24
24
  _deprecate = False
25
25
  _priority = 2
@@ -70,7 +70,7 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so
70
70
  video_source.get_embed(obj_episode.id)
71
71
 
72
72
  # Create output path
73
- title_name = f"{scrape_serie.series_name}_EP_{dynamic_format_number(int(obj_episode.number))}.mp4"
73
+ title_name = f"{scrape_serie.series_name}_EP_{dynamic_format_number(str(obj_episode.number))}.mp4"
74
74
 
75
75
  if scrape_serie.is_series:
76
76
  mp4_path = os_manager.get_sanitize_path(os.path.join(site_constant.ANIME_FOLDER, scrape_serie.series_name))
@@ -12,6 +12,7 @@ from rich.console import Console
12
12
 
13
13
  # Internal utilities
14
14
  from StreamingCommunity.Util.config_json import config_manager
15
+ from StreamingCommunity.Util.headers import get_userAgent
15
16
  from StreamingCommunity.Util.table import TVShowManager
16
17
  from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
17
18
 
@@ -29,7 +30,7 @@ table_show_manager = TVShowManager()
29
30
  max_timeout = config_manager.get_int("REQUESTS", "timeout")
30
31
 
31
32
 
32
- def get_token(site_name: str, domain: str) -> dict:
33
+ def get_token() -> dict:
33
34
  """
34
35
  Function to retrieve session tokens from a specified website.
35
36
 
@@ -40,8 +41,6 @@ def get_token(site_name: str, domain: str) -> dict:
40
41
  Returns:
41
42
  - dict: A dictionary containing session tokens. The keys are 'XSRF_TOKEN', 'animeunity_session', and 'csrf_token'.
42
43
  """
43
-
44
- # Send a GET request to the specified URL composed of the site name and domain
45
44
  response = httpx.get(
46
45
  url=site_constant.FULL_URL,
47
46
  timeout=max_timeout
@@ -50,17 +49,11 @@ def get_token(site_name: str, domain: str) -> dict:
50
49
 
51
50
  # Initialize variables to store CSRF token
52
51
  find_csrf_token = None
53
-
54
- # Parse the HTML response using BeautifulSoup
55
52
  soup = BeautifulSoup(response.text, "html.parser")
56
53
 
57
- # Loop through all meta tags in the HTML response
58
54
  for html_meta in soup.find_all("meta"):
59
-
60
- # Check if the meta tag has a 'name' attribute equal to "csrf-token"
61
55
  if html_meta.get('name') == "csrf-token":
62
56
 
63
- # If found, retrieve the content of the meta tag, which is the CSRF token
64
57
  find_csrf_token = html_meta.get('content')
65
58
 
66
59
  logging.info(f"Extract: ('animeunity_session': {response.cookies['animeunity_session']}, 'csrf_token': {find_csrf_token})")
@@ -83,13 +76,12 @@ def get_real_title(record):
83
76
  Returns:
84
77
  - str: The title found in the record. If no title is found, returns None.
85
78
  """
86
-
87
- if record['title'] is not None:
88
- return record['title']
89
-
90
- elif record['title_eng'] is not None:
79
+ if record['title_eng'] is not None:
91
80
  return record['title_eng']
92
81
 
82
+ elif record['title'] is not None:
83
+ return record['title']
84
+
93
85
  else:
94
86
  return record['title_it']
95
87
 
@@ -117,26 +109,15 @@ def title_search(title: str) -> int:
117
109
  console.print("[bold red]Error: Unable to determine valid domain or base URL.[/bold red]")
118
110
  console.print("[yellow]The service might be temporarily unavailable or the domain may have changed.[/yellow]")
119
111
  sys.exit(1)
120
-
121
- data = get_token(site_constant.SITE_NAME, domain_to_use)
122
-
123
- # Prepare cookies to be used in the request
124
- cookies = {
125
- 'animeunity_session': data.get('animeunity_session')
126
- }
127
112
 
128
- # Prepare headers for the request
113
+ # Create parameter for request
114
+ data = get_token()
115
+ cookies = {'animeunity_session': data.get('animeunity_session')}
129
116
  headers = {
130
- 'accept': 'application/json, text/plain, */*',
131
- 'accept-language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7',
132
- 'content-type': 'application/json;charset=UTF-8',
117
+ 'user-agent': get_userAgent(),
133
118
  'x-csrf-token': data.get('csrf_token')
134
119
  }
135
-
136
- # Prepare JSON data to be sent in the request
137
- json_data = {
138
- 'title': title
139
- }
120
+ json_data = {'title': title}
140
121
 
141
122
  # Send a POST request to the API endpoint for live search
142
123
  try:
@@ -167,8 +148,9 @@ def title_search(title: str) -> int:
167
148
  'slug': dict_title.get('slug'),
168
149
  'name': dict_title.get('name'),
169
150
  'type': dict_title.get('type'),
170
- 'score': dict_title.get('score'),
171
- 'episodes_count': dict_title.get('episodes_count')
151
+ 'status': dict_title.get('status'),
152
+ 'episodes_count': dict_title.get('episodes_count'),
153
+ 'plot': ' '.join((words := str(dict_title.get('plot', '')).split())[:10]) + ('...' if len(words) > 10 else '')
172
154
  })
173
155
 
174
156
  if site_constant.TELEGRAM_BOT:
@@ -19,7 +19,7 @@ from .film import download_film
19
19
 
20
20
 
21
21
  # Variable
22
- indice = 9
22
+ indice = 4
23
23
  _useFor = "film"
24
24
  _deprecate = False
25
25
  _priority = 2
@@ -20,7 +20,7 @@ from .series import download_thread
20
20
 
21
21
 
22
22
  # Variable
23
- indice = 3
23
+ indice = 6
24
24
  _useFor = "serie"
25
25
  _deprecate = False
26
26
  _priority = 2
@@ -19,7 +19,7 @@ from .series import download_series
19
19
 
20
20
 
21
21
  # Variable
22
- indice = 4
22
+ indice = 5
23
23
  _useFor = "serie"
24
24
  _deprecate = False
25
25
  _priority = 2
@@ -51,7 +51,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scap
51
51
  - bool: kill handler status
52
52
  """
53
53
  start_message()
54
- index_season_selected = dynamic_format_number(index_season_selected)
54
+ index_season_selected = dynamic_format_number(str(index_season_selected))
55
55
 
56
56
  # Get info about episode
57
57
  obj_episode = scape_info_serie.list_episodes[index_episode_selected - 1]
@@ -15,7 +15,7 @@ from .film import download_film
15
15
 
16
16
 
17
17
  # Variable
18
- indice = 9
18
+ indice = 7
19
19
  _useFor = "film"
20
20
  _deprecate = False
21
21
  _priority = 2
@@ -50,7 +50,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
50
50
  - bool: kill handler status
51
51
  """
52
52
  start_message()
53
- index_season_selected = dynamic_format_number(index_season_selected)
53
+ index_season_selected = dynamic_format_number(str(index_season_selected))
54
54
 
55
55
  # Get info about episode
56
56
  obj_episode = scrape_serie.episode_manager.get(index_episode_selected - 1)
@@ -22,24 +22,50 @@ console = Console()
22
22
  MAP_EPISODE = config_manager.get('OUT_FOLDER', 'map_episode_name')
23
23
 
24
24
 
25
- def dynamic_format_number(n: int) -> str:
25
+ def dynamic_format_number(number_str: str) -> str:
26
26
  """
27
- Formats a number by adding a leading zero if it is less than 9.
28
- The width of the resulting string is dynamic, calculated as the number of digits in the number plus one
29
- for numbers less than 9, otherwise the width remains the same.
27
+ Formats an episode number string, intelligently handling both integer and decimal episode numbers.
28
+
29
+ This function is designed to handle various episode number formats commonly found in media series:
30
+ 1. For integer episode numbers less than 10 (e.g., 1, 2, ..., 9), it adds a leading zero (e.g., 01, 02, ..., 09)
31
+ 2. For integer episode numbers 10 and above, it preserves the original format without adding leading zeros
32
+ 3. For decimal episode numbers (e.g., "7.5", "10.5"), it preserves the decimal format exactly as provided
33
+
34
+ The function is particularly useful for media file naming conventions where special episodes
35
+ or OVAs may have decimal notations (like episode 7.5) which should be preserved in their original format.
30
36
 
31
37
  Parameters:
32
- - n (int): The number to format.
38
+ - number_str (str): The episode number as a string, which may contain integers or decimals.
33
39
 
34
40
  Returns:
35
- - str: The formatted number as a string with a leading zero if the number is less than 9.
41
+ - str: The formatted episode number string, with appropriate handling based on the input type.
42
+
43
+ Examples:
44
+ >>> dynamic_format_number("7")
45
+ "07"
46
+ >>> dynamic_format_number("15")
47
+ "15"
48
+ >>> dynamic_format_number("7.5")
49
+ "7.5"
50
+ >>> dynamic_format_number("10.5")
51
+ "10.5"
36
52
  """
37
- if n < 10:
38
- width = len(str(n)) + 1
39
- else:
40
- width = len(str(n))
53
+ try:
54
+ if '.' in number_str:
55
+ return number_str
56
+
57
+ n = int(number_str)
58
+
59
+ if n < 10:
60
+ width = len(str(n)) + 1
61
+ else:
62
+ width = len(str(n))
41
63
 
42
- return str(n).zfill(width)
64
+ return str(n).zfill(width)
65
+
66
+ except Exception as e:
67
+ logging.warning(f"Could not format episode number '{number_str}': {str(e)}. Using original format.")
68
+ return number_str
43
69
 
44
70
 
45
71
  def manage_selection(cmd_insert: str, max_count: int) -> List[int]:
@@ -103,14 +129,14 @@ def map_episode_title(tv_name: str, number_season: int, episode_number: int, epi
103
129
  map_episode_temp = map_episode_temp.replace("%(tv_name)", os_manager.get_sanitize_file(tv_name))
104
130
 
105
131
  if number_season != None:
106
- map_episode_temp = map_episode_temp.replace("%(season)", number_season)
132
+ map_episode_temp = map_episode_temp.replace("%(season)", str(number_season))
107
133
  else:
108
- map_episode_temp = map_episode_temp.replace("%(season)", dynamic_format_number(0))
134
+ map_episode_temp = map_episode_temp.replace("%(season)", dynamic_format_number(str(0)))
109
135
 
110
136
  if episode_number != None:
111
- map_episode_temp = map_episode_temp.replace("%(episode)", dynamic_format_number(episode_number))
137
+ map_episode_temp = map_episode_temp.replace("%(episode)", dynamic_format_number(str(episode_number)))
112
138
  else:
113
- map_episode_temp = map_episode_temp.replace("%(episode)", dynamic_format_number(0))
139
+ map_episode_temp = map_episode_temp.replace("%(episode)", dynamic_format_number(str(0)))
114
140
 
115
141
  if episode_name != None:
116
142
  map_episode_temp = map_episode_temp.replace("%(episode_name)", os_manager.get_sanitize_file(episode_name))
@@ -227,7 +253,7 @@ def display_episodes_list(episodes_manager) -> str:
227
253
  last_command = table_show_manager.run()
228
254
 
229
255
  if last_command in ("q", "quit"):
230
- console.print("\n[red]Quit [white]...")
256
+ console.print("\n[red]Quit ...")
231
257
  sys.exit(0)
232
258
 
233
259
  return last_command
@@ -72,7 +72,7 @@ def get_select_title(table_show_manager, media_search_manager):
72
72
 
73
73
  # Handle user's quit command
74
74
  if last_command == "q" or last_command == "quit":
75
- console.print("\n[red]Quit [white]...")
75
+ console.print("\n[red]Quit ...")
76
76
  sys.exit(0)
77
77
 
78
78
  # Check if the selected index is within range
@@ -436,6 +436,18 @@ class HLS_Downloader:
436
436
  if TELEGRAM_BOT:
437
437
  bot.send_message(f"Contenuto già scaricato!", None)
438
438
  return response
439
+
440
+ if GET_ONLY_LINK:
441
+ console.print(f"URL: {self.m3u8_url}[/bold red]")
442
+ return {
443
+ 'path': None,
444
+ 'url': self.m3u8_url,
445
+ 'is_master': getattr(self.m3u8_manager, 'is_master', None),
446
+ 'msg': None,
447
+ 'error': error_msg,
448
+ 'stopped': True
449
+ }
450
+
439
451
 
440
452
  self.path_manager.setup_directories()
441
453
 
@@ -466,9 +478,8 @@ class HLS_Downloader:
466
478
 
467
479
  final_file = self.merge_manager.merge()
468
480
  self.path_manager.move_final_file(final_file)
469
- self.path_manager.cleanup()
470
-
471
481
  self._print_summary()
482
+ self.path_manager.cleanup()
472
483
 
473
484
  return {
474
485
  'path': self.path_manager.output_path,
@@ -74,6 +74,9 @@ class M3U8_Segments:
74
74
 
75
75
  # Sync
76
76
  self.queue = PriorityQueue()
77
+ self.buffer = {}
78
+ self.expected_index = 0
79
+
77
80
  self.stop_event = threading.Event()
78
81
  self.downloaded_segments = set()
79
82
  self.base_timeout = 0.5
@@ -94,6 +97,15 @@ class M3U8_Segments:
94
97
  self.active_retries_lock = threading.Lock()
95
98
 
96
99
  def __get_key__(self, m3u8_parser: M3U8_Parser) -> bytes:
100
+ """
101
+ Fetches the encryption key from the M3U8 playlist.
102
+
103
+ Args:
104
+ m3u8_parser (M3U8_Parser): An instance of M3U8_Parser containing parsed M3U8 data.
105
+
106
+ Returns:
107
+ bytes: The decryption key in byte format.
108
+ """
97
109
  key_uri = urljoin(self.url, m3u8_parser.keys.get('uri'))
98
110
  parsed_url = urlparse(key_uri)
99
111
  self.key_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}/"
@@ -110,6 +122,12 @@ class M3U8_Segments:
110
122
  raise Exception(f"Failed to fetch key: {e}")
111
123
 
112
124
  def parse_data(self, m3u8_content: str) -> None:
125
+ """
126
+ Parses the M3U8 content and extracts necessary data.
127
+
128
+ Args:
129
+ m3u8_content (str): The raw M3U8 playlist content.
130
+ """
113
131
  m3u8_parser = M3U8_Parser()
114
132
  m3u8_parser.parse_data(uri=self.url, raw_content=m3u8_content)
115
133
 
@@ -131,6 +149,14 @@ class M3U8_Segments:
131
149
  self.class_ts_estimator.total_segments = len(self.segments)
132
150
 
133
151
  def get_info(self) -> None:
152
+ """
153
+ Retrieves M3U8 playlist information from the given URL.
154
+
155
+ If the URL is an index URL, this method:
156
+ - Sends an HTTP GET request to fetch the M3U8 playlist.
157
+ - Parses the M3U8 content using `parse_data`.
158
+ - Saves the playlist to a temporary folder.
159
+ """
134
160
  if self.is_index_url:
135
161
  try:
136
162
  client_params = {'headers': {'User-Agent': get_userAgent()}, 'timeout': MAX_TIMEOOUT}
@@ -251,9 +277,6 @@ class M3U8_Segments:
251
277
  """
252
278
  Writes segments to file with additional verification.
253
279
  """
254
- buffer = {}
255
- expected_index = 0
256
-
257
280
  with open(self.tmp_file_path, 'wb') as f:
258
281
  while not self.stop_event.is_set() or not self.queue.empty():
259
282
  if self.interrupt_flag.is_set():
@@ -267,28 +290,28 @@ class M3U8_Segments:
267
290
 
268
291
  # Handle failed segments
269
292
  if segment_content is None:
270
- if index == expected_index:
271
- expected_index += 1
293
+ if index == self.expected_index:
294
+ self.expected_index += 1
272
295
  continue
273
296
 
274
297
  # Write segment if it's the next expected one
275
- if index == expected_index:
298
+ if index == self.expected_index:
276
299
  f.write(segment_content)
277
300
  f.flush()
278
- expected_index += 1
301
+ self.expected_index += 1
279
302
 
280
303
  # Write any buffered segments that are now in order
281
- while expected_index in buffer:
282
- next_segment = buffer.pop(expected_index)
304
+ while self.expected_index in self.buffer:
305
+ next_segment = self.buffer.pop(self.expected_index)
283
306
 
284
307
  if next_segment is not None:
285
308
  f.write(next_segment)
286
309
  f.flush()
287
310
 
288
- expected_index += 1
311
+ self.expected_index += 1
289
312
 
290
313
  else:
291
- buffer[index] = segment_content
314
+ self.buffer[index] = segment_content
292
315
 
293
316
  except queue.Empty:
294
317
  self.current_timeout = min(MAX_TIMEOOUT, self.current_timeout * 1.1)
@@ -440,6 +463,9 @@ class M3U8_Segments:
440
463
  if self.info_nFailed > 0:
441
464
  self._display_error_summary()
442
465
 
466
+ self.buffer = {}
467
+ self.expected_index = 0
468
+
443
469
  def _display_error_summary(self) -> None:
444
470
  """Generate final error report."""
445
471
  console.print(f"\n[cyan]Retry Summary: "
@@ -87,7 +87,8 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No
87
87
  return None, False
88
88
 
89
89
  if GET_ONLY_LINK:
90
- return {'path': path, 'url': url}
90
+ console.print(f"URL: {url}[/bold red]")
91
+ return path, True
91
92
 
92
93
  if not (url.lower().startswith('http://') or url.lower().startswith('https://')):
93
94
  logging.error(f"Invalid URL: {url}")
@@ -180,7 +180,7 @@ def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: s
180
180
  Each dictionary should contain the 'path' key with the path to the audio file.
181
181
  - out_path (str): The path to save the output file.
182
182
  """
183
- video_audio_same_duration = check_duration_v_a(video_path, audio_tracks[0].get('path'))
183
+ video_audio_same_duration, duration_diff = check_duration_v_a(video_path, audio_tracks[0].get('path'))
184
184
 
185
185
  # Start command with locate ffmpeg
186
186
  ffmpeg_cmd = [get_ffmpeg_path()]
@@ -242,7 +242,7 @@ def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: s
242
242
 
243
243
  # Use shortest input path for video and audios
244
244
  if not video_audio_same_duration:
245
- logging.info("[red]Use shortest input.")
245
+ console.log(f"[red]Use shortest input (Duration difference: {duration_diff:.2f} seconds)...")
246
246
  ffmpeg_cmd.extend(['-shortest', '-strict', 'experimental'])
247
247
 
248
248
  # Overwrite
@@ -57,7 +57,6 @@ def get_video_duration(file_path: str) -> float:
57
57
  Returns:
58
58
  (float): The duration of the video in seconds if successful, None if there's an error.
59
59
  """
60
-
61
60
  try:
62
61
  ffprobe_cmd = [get_ffprobe_path(), '-v', 'error', '-show_format', '-print_format', 'json', file_path]
63
62
  logging.info(f"FFmpeg command: {ffprobe_cmd}")
@@ -95,7 +94,6 @@ def format_duration(seconds: float) -> Tuple[int, int, int]:
95
94
  Returns:
96
95
  list[int, int, int]: List containing hours, minutes, and seconds.
97
96
  """
98
-
99
97
  hours, remainder = divmod(seconds, 3600)
100
98
  minutes, seconds = divmod(remainder, 60)
101
99
 
@@ -157,11 +155,7 @@ def get_ffprobe_info(file_path):
157
155
  'codec_names': codec_names
158
156
  }
159
157
 
160
- except subprocess.CalledProcessError as e:
161
- logging.error(f"ffprobe failed for file {file_path}: {e}")
162
- return None
163
-
164
- except json.JSONDecodeError as e:
158
+ except Exception as e:
165
159
  logging.error(f"Failed to parse JSON output from ffprobe for file {file_path}: {e}")
166
160
  return None
167
161
 
@@ -198,23 +192,25 @@ def need_to_force_to_ts(file_path):
198
192
  return False
199
193
 
200
194
 
201
- def check_duration_v_a(video_path, audio_path):
195
+ def check_duration_v_a(video_path, audio_path, tolerance=1.0):
202
196
  """
203
197
  Check if the duration of the video and audio matches.
204
198
 
205
199
  Parameters:
206
200
  - video_path (str): Path to the video file.
207
201
  - audio_path (str): Path to the audio file.
202
+ - tolerance (float): Allowed tolerance for the duration difference (in seconds).
208
203
 
209
204
  Returns:
210
- - bool: True if the duration of the video and audio matches, False otherwise.
205
+ - tuple: (bool, float) -> True if the duration of the video and audio matches, False otherwise, along with the difference in duration.
211
206
  """
212
-
213
- # Ottieni la durata del video
214
207
  video_duration = get_video_duration(video_path)
215
-
216
- # Ottieni la durata dell'audio
217
208
  audio_duration = get_video_duration(audio_path)
218
209
 
219
- # Verifica se le durate corrispondono
220
- return video_duration == audio_duration
210
+ duration_difference = abs(video_duration - audio_duration)
211
+
212
+ # Check if the duration difference is within the tolerance
213
+ if duration_difference <= tolerance:
214
+ return True, duration_difference
215
+ else:
216
+ return False, duration_difference
@@ -52,7 +52,7 @@ class M3U8_Ts_Estimator:
52
52
  self.now_downloaded_size += size_download
53
53
  logging.debug(f"Current total downloaded size: {self.now_downloaded_size}")
54
54
 
55
- def capture_speed(self, interval: float = 1):
55
+ def capture_speed(self, interval: float = 1.5):
56
56
  """Capture the internet speed periodically."""
57
57
  last_upload, last_download = 0, 0
58
58
  speed_buffer = deque(maxlen=3)
@@ -73,7 +73,7 @@ def get_select_title(table_show_manager, generic_obj):
73
73
 
74
74
  # Handle user's quit command
75
75
  if last_command == "q" or last_command == "quit":
76
- console.print("\n[red]Quit [white]...")
76
+ console.print("\n[red]Quit ...")
77
77
  sys.exit(0)
78
78
 
79
79
  # Check if the selected index is within range
@@ -1,5 +1,5 @@
1
1
  __title__ = 'StreamingCommunity'
2
- __version__ = '2.9.1'
2
+ __version__ = '2.9.3'
3
3
  __author__ = 'Arrowar'
4
4
  __description__ = 'A command-line program to download film'
5
5
  __copyright__ = 'Copyright 2024'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: StreamingCommunity
3
- Version: 2.9.1
3
+ Version: 2.9.3
4
4
  Home-page: https://github.com/Lovi-0/StreamingCommunity
5
5
  Author: Lovi-0
6
6
  Project-URL: Bug Reports, https://github.com/Lovi-0/StreamingCommunity/issues
@@ -72,6 +72,7 @@ Requires-Dist: pyTelegramBotAPI
72
72
  - 🔍 [Parser](#m3u8_parser-settings)
73
73
  - 📝 [Command](#command)
74
74
  - 💻 [Examples of terminal](#examples-of-terminal-usage)
75
+ - 🔧 [Manual domain configuration](#update-domains)
75
76
  - 🐳 [Docker](#docker)
76
77
  - 📝 [Telegram Usage](#telegram-usage)
77
78
  - 🎓 [Tutorial](#tutorials)
@@ -1,64 +1,64 @@
1
1
  StreamingCommunity/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  StreamingCommunity/run.py,sha256=AbEL0cyAaRgaG5qE1c7Z6SVZ4Wu7WIH9pZmwC4FDWW8,12076
3
3
  StreamingCommunity/Api/Player/ddl.py,sha256=M_ePETCMBpIHr5K5Yb7EML5VXwqkR7vJHQcGIv4AEQw,2261
4
- StreamingCommunity/Api/Player/maxstream.py,sha256=lQ6sbhBqMR9e6N-03nWZ3qsUlRT9l68d8dTA0th1XVQ,4984
5
- StreamingCommunity/Api/Player/supervideo.py,sha256=owyoSkLDJwASNYQw77ZbJ5ptCXOFsAC5ghN-o0RaML0,5001
4
+ StreamingCommunity/Api/Player/maxstream.py,sha256=abUQeE4EOUV0uuVOd22_C8IdeKrAAoO5USdgdm6EzfM,5092
5
+ StreamingCommunity/Api/Player/supervideo.py,sha256=hr9QViI-XD0Dqhcx90oaH8_j0d6cxpVaf-EuCjMs6hI,5199
6
6
  StreamingCommunity/Api/Player/vixcloud.py,sha256=NOZhW59hyBnG5FfmppcnIudAztr7seWzQBzAN3KC_3M,6317
7
7
  StreamingCommunity/Api/Player/Helper/Vixcloud/js_parser.py,sha256=U-8QlD5kGzIk3-4t4D6QyYmiDe8UBrSuVi1YHRQb7AU,4295
8
- StreamingCommunity/Api/Player/Helper/Vixcloud/util.py,sha256=xt96JIbfE5W4Y59W888Cp-baRwnQVEDQO_x7Wq5iyNQ,5492
9
- StreamingCommunity/Api/Site/1337xx/__init__.py,sha256=r0K84qeonGnk9bgbKklF6rJ1qhT1Si1MH2hnXnH_SIE,1378
8
+ StreamingCommunity/Api/Player/Helper/Vixcloud/util.py,sha256=dnyVxZTjjV3C6PUCu2OMeHI4NbiioBWhixlY2Ol9I74,5206
9
+ StreamingCommunity/Api/Site/1337xx/__init__.py,sha256=pyaQ3QlLdiqLcyNyfE6jKhOR5BXUiKLfPcIBHbk8U8U,1378
10
10
  StreamingCommunity/Api/Site/1337xx/site.py,sha256=u6a453wVy3vJrGcLlSUhErKzr63lv-o9cx36RjFpPRs,2647
11
11
  StreamingCommunity/Api/Site/1337xx/title.py,sha256=lGb-IbWEIfg9Eu3XIu6IfxTOjvXkFL_NO9UEZcxOAfE,1831
12
12
  StreamingCommunity/Api/Site/animeunity/__init__.py,sha256=Lz9trBzQZL11wGyIT2Y4CWe3JxOkDUPzTQXXO76s0oQ,2278
13
- StreamingCommunity/Api/Site/animeunity/film_serie.py,sha256=noYqH5NHUk_JO6GeqN7-PQy3tplDRKYWYI_cgT1SzqI,5771
14
- StreamingCommunity/Api/Site/animeunity/site.py,sha256=7NX59B89xpZ6Tj96a_ZCsVMDwOq8fxdw81vrlnvNjn4,5912
13
+ StreamingCommunity/Api/Site/animeunity/film_serie.py,sha256=I44DJ26evVpJPveoWYZvlswB6f9gEG0Br_IrogleAk0,5771
14
+ StreamingCommunity/Api/Site/animeunity/site.py,sha256=SRtei5Ftx9Vb9z5F4C-nbdrVV8s--VwtfE6l8Y7_8Ko,5401
15
15
  StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py,sha256=6Vbw5KVwUbgooGjUIRAuXr9cWSkHDkAFP7EiXF2T4OM,2709
16
- StreamingCommunity/Api/Site/cb01new/__init__.py,sha256=yOfCNYZU5M5rCvNtwvyTgGjJv7qGmNWRAzk0AtAgbhU,1393
16
+ StreamingCommunity/Api/Site/cb01new/__init__.py,sha256=jw-eyJunemd3uNwpow75_8s7TX8bYyiRA-zkF5NZ75w,1393
17
17
  StreamingCommunity/Api/Site/cb01new/film.py,sha256=trrEGcklB6FhqpJvGaEwHI0EThK__e9O6DuknKAFNHw,1628
18
18
  StreamingCommunity/Api/Site/cb01new/site.py,sha256=q9bHKgHkXWE0vOcfQ9bKU0QDzbLassfSN2im2O4EYiI,2523
19
- StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py,sha256=_tQbsAJgZufjszrFZ0Om3ia6oYJHMjiHfXAMScqf4yQ,1533
19
+ StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py,sha256=XkpTFeb-yFI_bJCIph72cVE8GVoJP7Tby-NqkZNNDM4,1533
20
20
  StreamingCommunity/Api/Site/ddlstreamitaly/series.py,sha256=z3te51do5C_O77rDTR1N01aQ76BIGe5pm5i_PWJepQ4,3369
21
21
  StreamingCommunity/Api/Site/ddlstreamitaly/site.py,sha256=_I4fZuzE5DnwAGOBOJK_IYW04RETwl3N2J0FUJAjQqg,2913
22
22
  StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py,sha256=HY8YEvzWp3sy1q07rFLXLZhGYvapA1amMZByYvs0iJM,2553
23
- StreamingCommunity/Api/Site/guardaserie/__init__.py,sha256=ZR9Fz6v2cS2Ve8BfAlvTNK-d5_Xwp5_eqrnFupgxj5Q,1385
24
- StreamingCommunity/Api/Site/guardaserie/series.py,sha256=52OjjItPuVglqlpFFVeJiSXrUL5WFhC-GfyRo7yWnsM,5665
23
+ StreamingCommunity/Api/Site/guardaserie/__init__.py,sha256=NjMn1EFWdFi9P89qpKNg3Dc84DDOSyuSBX0V5K24OjQ,1385
24
+ StreamingCommunity/Api/Site/guardaserie/series.py,sha256=xXuMR9NiAtXkHrErsmXRY9IkE2FVGuhqjATYEfapb0Y,5670
25
25
  StreamingCommunity/Api/Site/guardaserie/site.py,sha256=5Y5svAT4QYZrNDPM9couf6M94g0g320NfoAuJt4dp94,2538
26
26
  StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py,sha256=4sZRWm8r5X80q285hemRf7MAWeaN5yfOU6i1SjKU4Tg,3268
27
- StreamingCommunity/Api/Site/mostraguarda/__init__.py,sha256=rsM2_7FmwyV3ibUbpcflk-eyH8ouBv0ZdCA5tCCVB70,1234
27
+ StreamingCommunity/Api/Site/mostraguarda/__init__.py,sha256=6oGv_Q6pXFERkbpKjYjFJFblvDYSfe1zZph6_Ch8gNY,1234
28
28
  StreamingCommunity/Api/Site/mostraguarda/film.py,sha256=dA7Vo9bU7g8eY8Vaj06_n2MHlKBMHh4B_MIw2sO872A,2719
29
29
  StreamingCommunity/Api/Site/streamingcommunity/__init__.py,sha256=FV3ch-farw3tN_-Ay3JV_-TIoHzgQzxEJWPlFibE62Y,2351
30
30
  StreamingCommunity/Api/Site/streamingcommunity/film.py,sha256=LaZzEQms9t7r30_PjHPgIOUkVDyotX0qFDBMKMVNSWo,2530
31
- StreamingCommunity/Api/Site/streamingcommunity/series.py,sha256=T4Yw-cjdQ8GsOLPJwd8iDIcTND1olg81MopsaSDd1Ik,7921
31
+ StreamingCommunity/Api/Site/streamingcommunity/series.py,sha256=LmvcGTr7GR4_n2G0QYnLUKFiA30OxQD_H9Y3pF4tamo,7926
32
32
  StreamingCommunity/Api/Site/streamingcommunity/site.py,sha256=OID6QB_WMydwVCYD0SupHpEP0VwHIBmwifwuIHW9HaE,2973
33
33
  StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py,sha256=YYetljW4yr6eoiLe6nDUuFZ9NI8K85tF6859ikITmsY,3571
34
34
  StreamingCommunity/Api/Template/__init__.py,sha256=oyfd_4_g5p5q6mxb_rKwSsudZnTM3W3kg1tLwxg-v-Q,46
35
35
  StreamingCommunity/Api/Template/config_loader.py,sha256=seJjLi05_8xrs4tdt4zXNCPa9TLp7n5FufFM2XErokI,2166
36
- StreamingCommunity/Api/Template/site.py,sha256=MJF8oc4iW29nU8SF_J94UaSjanTVBMhKiRl3xrA0qBA,2849
36
+ StreamingCommunity/Api/Template/site.py,sha256=lD7FCp-xHCTwTam51MYtI-JKRuw1VLsY9KQDtsk3MYE,2842
37
37
  StreamingCommunity/Api/Template/Class/SearchType.py,sha256=FtO8xDUGEeJgMWsK2Ab7ZzAFsncalTYL2oEYi8uCnuk,2531
38
38
  StreamingCommunity/Api/Template/Util/__init__.py,sha256=GZZgT816VwTYekPOwLExHenpak0gO-V3LLGTYIElt3A,234
39
39
  StreamingCommunity/Api/Template/Util/get_domain.py,sha256=tWQKWs5LJZna4a2P0IfxlSZDbZTDjiqq9SmHTbK-PyU,2763
40
- StreamingCommunity/Api/Template/Util/manage_ep.py,sha256=xYDC3tlx6gjQqCqcyKCfQVQeE6aWU5sdrovj8uuvbd8,8118
40
+ StreamingCommunity/Api/Template/Util/manage_ep.py,sha256=aPRgSB6OiUNfYQKMTEDrjxeTFfOUfx3JypvA6nehwP4,9251
41
41
  StreamingCommunity/Lib/Downloader/__init__.py,sha256=JhbBh5hOnSM7VmtkxJ7zZ_FtWEC1JdnKThsSBjLV5FY,140
42
- StreamingCommunity/Lib/Downloader/HLS/downloader.py,sha256=h-44aunSdcCbG4CXPeZP2LWypA9T905E4RnTP4kSyZ0,21168
43
- StreamingCommunity/Lib/Downloader/HLS/segments.py,sha256=4D-B_KfwiQq6suMgJxL_aMrOqqX5RVc4Vy0Ktc7tFKM,17609
44
- StreamingCommunity/Lib/Downloader/MP4/downloader.py,sha256=KTNivIvVwn72rA34fNPxT5Q88u7IS3vHLn_Y_jdrZww,7434
42
+ StreamingCommunity/Lib/Downloader/HLS/downloader.py,sha256=2MzuMtXgRb6TcurQi0z6fJcXi0mzrbecWOk-L46bPgw,21585
43
+ StreamingCommunity/Lib/Downloader/HLS/segments.py,sha256=eVQzB06EochPM1E0VNWgfvEyc5wGDq_yG6Fqo7WPNic,18468
44
+ StreamingCommunity/Lib/Downloader/MP4/downloader.py,sha256=4rgaVTlBvNCfS2dWbTC_eCiXL8MbC1mwRGlUtvJv4nw,7466
45
45
  StreamingCommunity/Lib/Downloader/TOR/downloader.py,sha256=KVZxCl1VB1-OuTjUhVS5Ycog_P0vCGTfRzhZPv8O7Ps,11267
46
46
  StreamingCommunity/Lib/FFmpeg/__init__.py,sha256=6PBsZdE1jrD2EKOVyx3JEHnyDZzVeKlPkH5T0zyfOgU,130
47
47
  StreamingCommunity/Lib/FFmpeg/capture.py,sha256=73BEpTijksErZOu46iRxwl3idKzZ-sVXXRr4nocIGY0,5168
48
- StreamingCommunity/Lib/FFmpeg/command.py,sha256=hHMLOmb6I3VvuztxcRCFxhYgeGaqHNA78qYERJr5NXs,10640
49
- StreamingCommunity/Lib/FFmpeg/util.py,sha256=Yyo-z9TOPGFdD_VscWi35Kcw88y4loGNLpNOzy7ADm8,7063
48
+ StreamingCommunity/Lib/FFmpeg/command.py,sha256=ubpffE02nsZM7xch5gbwGlEiUzecpxcFOd63n2cqM1k,10708
49
+ StreamingCommunity/Lib/FFmpeg/util.py,sha256=6QzTbk5BNxKYsTl-cJgOO2XGQTWUdMloWMyrGGBs6eU,7168
50
50
  StreamingCommunity/Lib/M3U8/__init__.py,sha256=H_KS2eDd3kVXMziFJnD0FCPvPHEizaqfoA36ElTv_r8,170
51
51
  StreamingCommunity/Lib/M3U8/decryptor.py,sha256=kuxxsd3eN0VGRrMJWXzHo8gCpT0u3fSZs_lwxlE5Fqs,2948
52
- StreamingCommunity/Lib/M3U8/estimator.py,sha256=5Qevboxu1h5Q-72bkHy0-Vess9pOIaUrpFYfBVWNDN8,5709
52
+ StreamingCommunity/Lib/M3U8/estimator.py,sha256=U2xnUvoAeLZT8KA1ZDZO9TRz7sXgzIyCtHClT2wcI28,5711
53
53
  StreamingCommunity/Lib/M3U8/parser.py,sha256=xN16pQZSCN9mQl_s7OcuH07-mNgVMpAS_hERq6zp7XM,21558
54
54
  StreamingCommunity/Lib/M3U8/url_fixer.py,sha256=zldE4yOuNBV6AAvL1KI6p7XdRI_R5YZRscbDgT1564M,1735
55
55
  StreamingCommunity/Lib/TMBD/__init__.py,sha256=XzE42tw3Ws59DD1PF8WmGtZ0D4D7Hk3Af8QthNE-22U,66
56
56
  StreamingCommunity/Lib/TMBD/obj_tmbd.py,sha256=dRSvJFS5yqmsBZcw2wqbStcBtXNjU_3n5czMyremAtU,1187
57
- StreamingCommunity/Lib/TMBD/tmdb.py,sha256=LjO21d7iexnSwKBYjgXFECojVSE8WCspQszJJcaZexU,10692
57
+ StreamingCommunity/Lib/TMBD/tmdb.py,sha256=byg0EFnlmd9JeLvn1N9K3QkB1KEfeMuFa7OVfGqks1Y,10685
58
58
  StreamingCommunity/TelegramHelp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
59
  StreamingCommunity/TelegramHelp/telegram_bot.py,sha256=Qe1__aoK4PpDuing8JtWgdHzLee8LuYYyfeLNA7yADU,26307
60
60
  StreamingCommunity/Upload/update.py,sha256=TXWAOfvZr1So_oME11YvX_L5zRy2tM-ijF-_g1jf87o,2548
61
- StreamingCommunity/Upload/version.py,sha256=7zxploGPmBn4yRD_nqHbaaNTjsXjfVwEsD0XQTKoZp8,171
61
+ StreamingCommunity/Upload/version.py,sha256=bREs7N-kz1d-zZ5B1i3S2s-N5hBkN0o0LPTL9AuAqbY,171
62
62
  StreamingCommunity/Util/color.py,sha256=NvD0Eni-25oOOkY-szCEoc0lGvzQxyL7xhM0RE4EvUM,458
63
63
  StreamingCommunity/Util/config_json.py,sha256=dmo7FTboEuEs1nYf17RYaTyT03qRvOLJzGgBitYHXs4,19379
64
64
  StreamingCommunity/Util/ffmpeg_installer.py,sha256=q5yb_ZXKe9PhcG7JbKLfo1AZa8DNukgHqymPbudDuAY,13585
@@ -67,9 +67,9 @@ StreamingCommunity/Util/logger.py,sha256=9kGD6GmWj2pM8ADpJc85o7jm8DD0c5Aguqnq-9k
67
67
  StreamingCommunity/Util/message.py,sha256=SJaIPLvWeQqsIODVUKw3TgYRmBChovmlbcF6OUxqMI8,1425
68
68
  StreamingCommunity/Util/os.py,sha256=MUGJKQbNMWeoUrnJ2Ug3hoyYlrPDqU9BY94UmiUbxfA,14858
69
69
  StreamingCommunity/Util/table.py,sha256=X1t9VPYl9GemLMk_-x_WfpysQ-3Iv8vh0aTIJKm0fK0,8565
70
- StreamingCommunity-2.9.1.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
71
- StreamingCommunity-2.9.1.dist-info/METADATA,sha256=3FfkiFkbxuqlPg_CUvgg3eQs2mKLkH9bFaAfjiIXUOs,21498
72
- StreamingCommunity-2.9.1.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
73
- StreamingCommunity-2.9.1.dist-info/entry_points.txt,sha256=Qph9XYfDC8n4LfDLOSl6gJGlkb9eFb5f-JOr_Wb_5rk,67
74
- StreamingCommunity-2.9.1.dist-info/top_level.txt,sha256=YsOcxKP-WOhWpIWgBlh0coll9XUx7aqmRPT7kmt3fH0,19
75
- StreamingCommunity-2.9.1.dist-info/RECORD,,
70
+ StreamingCommunity-2.9.3.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
71
+ StreamingCommunity-2.9.3.dist-info/METADATA,sha256=UPtxd9uEBSd8bvBBTexZ5kaeROrDyX_WGxBT6dcRYhc,21552
72
+ StreamingCommunity-2.9.3.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
73
+ StreamingCommunity-2.9.3.dist-info/entry_points.txt,sha256=Qph9XYfDC8n4LfDLOSl6gJGlkb9eFb5f-JOr_Wb_5rk,67
74
+ StreamingCommunity-2.9.3.dist-info/top_level.txt,sha256=YsOcxKP-WOhWpIWgBlh0coll9XUx7aqmRPT7kmt3fH0,19
75
+ StreamingCommunity-2.9.3.dist-info/RECORD,,