StreamingCommunity 2.7.0__py3-none-any.whl → 2.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of StreamingCommunity might be problematic. Click here for more details.

Files changed (71) hide show
  1. StreamingCommunity/Api/Player/ddl.py +2 -2
  2. StreamingCommunity/Api/Player/maxstream.py +7 -13
  3. StreamingCommunity/Api/Player/supervideo.py +7 -33
  4. StreamingCommunity/Api/Player/vixcloud.py +8 -80
  5. StreamingCommunity/Api/Site/1337xx/__init__.py +8 -1
  6. StreamingCommunity/Api/Site/1337xx/site.py +10 -16
  7. StreamingCommunity/Api/Site/1337xx/title.py +4 -1
  8. StreamingCommunity/Api/Site/animeunity/__init__.py +9 -2
  9. StreamingCommunity/Api/Site/animeunity/film_serie.py +7 -1
  10. StreamingCommunity/Api/Site/animeunity/site.py +8 -10
  11. StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py +1 -1
  12. StreamingCommunity/Api/Site/cb01new/__init__.py +8 -1
  13. StreamingCommunity/Api/Site/cb01new/film.py +10 -6
  14. StreamingCommunity/Api/Site/cb01new/site.py +16 -15
  15. StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +9 -2
  16. StreamingCommunity/Api/Site/ddlstreamitaly/series.py +7 -1
  17. StreamingCommunity/Api/Site/ddlstreamitaly/site.py +10 -15
  18. StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +1 -1
  19. StreamingCommunity/Api/Site/guardaserie/__init__.py +9 -2
  20. StreamingCommunity/Api/Site/guardaserie/series.py +13 -8
  21. StreamingCommunity/Api/Site/guardaserie/site.py +12 -17
  22. StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +1 -1
  23. StreamingCommunity/Api/Site/mostraguarda/__init__.py +6 -2
  24. StreamingCommunity/Api/Site/mostraguarda/film.py +10 -8
  25. StreamingCommunity/Api/Site/streamingcommunity/__init__.py +9 -2
  26. StreamingCommunity/Api/Site/streamingcommunity/film.py +11 -6
  27. StreamingCommunity/Api/Site/streamingcommunity/series.py +17 -10
  28. StreamingCommunity/Api/Site/streamingcommunity/site.py +10 -15
  29. StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +2 -2
  30. StreamingCommunity/Api/Template/Util/__init__.py +0 -1
  31. StreamingCommunity/Api/Template/Util/get_domain.py +24 -66
  32. StreamingCommunity/Api/Template/Util/manage_ep.py +10 -5
  33. StreamingCommunity/Api/Template/config_loader.py +8 -8
  34. StreamingCommunity/Api/Template/site.py +3 -6
  35. StreamingCommunity/Lib/Downloader/HLS/downloader.py +15 -14
  36. StreamingCommunity/Lib/Downloader/HLS/segments.py +11 -31
  37. StreamingCommunity/Lib/Downloader/MP4/downloader.py +12 -9
  38. StreamingCommunity/Lib/Downloader/TOR/downloader.py +109 -101
  39. StreamingCommunity/Lib/FFmpeg/__init__.py +1 -1
  40. StreamingCommunity/Lib/FFmpeg/capture.py +10 -12
  41. StreamingCommunity/Lib/FFmpeg/command.py +15 -14
  42. StreamingCommunity/Lib/FFmpeg/util.py +9 -38
  43. StreamingCommunity/Lib/M3U8/decryptor.py +72 -146
  44. StreamingCommunity/Lib/M3U8/estimator.py +8 -16
  45. StreamingCommunity/Lib/M3U8/parser.py +25 -27
  46. StreamingCommunity/Lib/M3U8/url_fixer.py +1 -4
  47. StreamingCommunity/Lib/TMBD/__init__.py +2 -0
  48. StreamingCommunity/Lib/TMBD/obj_tmbd.py +3 -17
  49. StreamingCommunity/Lib/TMBD/tmdb.py +4 -9
  50. StreamingCommunity/TelegramHelp/telegram_bot.py +50 -50
  51. StreamingCommunity/Upload/update.py +3 -2
  52. StreamingCommunity/Upload/version.py +1 -1
  53. StreamingCommunity/Util/color.py +1 -1
  54. StreamingCommunity/Util/{_jsonConfig.py → config_json.py} +148 -54
  55. StreamingCommunity/Util/headers.py +2 -38
  56. StreamingCommunity/Util/logger.py +72 -42
  57. StreamingCommunity/Util/message.py +8 -3
  58. StreamingCommunity/Util/os.py +41 -93
  59. StreamingCommunity/Util/table.py +8 -17
  60. StreamingCommunity/run.py +26 -34
  61. {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.9.0.dist-info}/METADATA +165 -92
  62. StreamingCommunity-2.9.0.dist-info/RECORD +75 -0
  63. StreamingCommunity/Api/Template/Util/recall_search.py +0 -37
  64. StreamingCommunity/Lib/Downloader/HLS/proxyes.py +0 -110
  65. StreamingCommunity/Util/call_stack.py +0 -42
  66. StreamingCommunity/Util/console.py +0 -12
  67. StreamingCommunity-2.7.0.dist-info/RECORD +0 -79
  68. {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.9.0.dist-info}/LICENSE +0 -0
  69. {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.9.0.dist-info}/WHEEL +0 -0
  70. {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.9.0.dist-info}/entry_points.txt +0 -0
  71. {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.9.0.dist-info}/top_level.txt +0 -0
@@ -9,47 +9,55 @@ import psutil
9
9
  import logging
10
10
 
11
11
 
12
+ # External library
13
+ from rich.console import Console
14
+
15
+
12
16
  # Internal utilities
13
17
  from StreamingCommunity.Util.color import Colors
14
18
  from StreamingCommunity.Util.os import internet_manager
15
- from StreamingCommunity.Util.console import console
16
- from StreamingCommunity.Util._jsonConfig import config_manager
19
+ from StreamingCommunity.Util.config_json import config_manager, get_use_large_bar
17
20
 
18
21
 
19
22
  # External libraries
20
23
  from tqdm import tqdm
21
- from qbittorrent import Client
24
+ import qbittorrentapi
22
25
 
23
26
 
24
27
  # Tor config
25
- HOST = str(config_manager.get_dict('DEFAULT', 'config_qbit_tor')['host'])
26
- PORT = str(config_manager.get_dict('DEFAULT', 'config_qbit_tor')['port'])
27
- USERNAME = str(config_manager.get_dict('DEFAULT', 'config_qbit_tor')['user'])
28
- PASSWORD = str(config_manager.get_dict('DEFAULT', 'config_qbit_tor')['pass'])
28
+ HOST = config_manager.get_dict('QBIT_CONFIG', 'host')
29
+ PORT = config_manager.get_dict('QBIT_CONFIG', 'port')
30
+ USERNAME = config_manager.get_dict('QBIT_CONFIG', 'user')
31
+ PASSWORD = config_manager.get_dict('QBIT_CONFIG', 'pass')
29
32
 
30
33
 
31
- # Config
32
- USE_LARGE_BAR = not ("android" in sys.platform or "ios" in sys.platform)
34
+ # Variable
33
35
  REQUEST_TIMEOUT = config_manager.get_float('REQUESTS', 'timeout')
34
-
36
+ console = Console()
35
37
 
36
38
 
37
39
  class TOR_downloader:
38
40
  def __init__(self):
39
41
  """
40
42
  Initializes the TorrentManager instance.
41
-
43
+
42
44
  Parameters:
43
45
  - host (str): IP address or hostname of the qBittorrent Web UI.
44
- - port (int): Port number of the qBittorrent Web UI.
45
- - username (str): Username for logging into qBittorrent.
46
- - password (str): Password for logging into qBittorrent.
46
+ - port (int): Port of the qBittorrent Web UI.
47
+ - username (str): Username for accessing qBittorrent.
48
+ - password (str): Password for accessing qBittorrent.
47
49
  """
48
50
  try:
49
51
  console.print(f"[cyan]Connect to: [green]{HOST}:{PORT}")
50
- self.qb = Client(f'http://{HOST}:{PORT}/')
51
- except:
52
- logging.error("Start qbitorrent first.")
52
+ self.qb = qbittorrentapi.Client(
53
+ host=HOST,
54
+ port=PORT,
55
+ username=USERNAME,
56
+ password=PASSWORD
57
+ )
58
+
59
+ except:
60
+ logging.error("Start qbittorrent first.")
53
61
  sys.exit(0)
54
62
 
55
63
  self.username = USERNAME
@@ -65,7 +73,7 @@ class TOR_downloader:
65
73
  Logs into the qBittorrent Web UI.
66
74
  """
67
75
  try:
68
- self.qb.login(self.username, self.password)
76
+ self.qb.auth_log_in()
69
77
  self.logged_in = True
70
78
  logging.info("Successfully logged in to qBittorrent.")
71
79
 
@@ -74,96 +82,87 @@ class TOR_downloader:
74
82
  self.logged_in = False
75
83
 
76
84
  def delete_magnet(self, torrent_info):
77
-
78
- if (int(torrent_info.get('dl_speed')) == 0 and
79
- int(torrent_info.get('peers')) == 0 and
80
- int(torrent_info.get('seeds')) == 0):
81
-
82
- # Elimina il torrent appena aggiunto
83
- console.print(f"[bold red]⚠️ Torrent non scaricabile. Rimozione in corso...[/bold red]")
85
+ """
86
+ Deletes a torrent if it is not downloadable (no seeds/peers).
87
+
88
+ Parameters:
89
+ - torrent_info: Object containing torrent information obtained from the qBittorrent API.
90
+ """
91
+ if (int(torrent_info.dlspeed) == 0 and
92
+ int(torrent_info.num_leechs) == 0 and
93
+ int(torrent_info.num_seeds) == 0):
84
94
 
95
+ console.print(f"[bold red]Torrent not downloadable. Removing...[/bold red]")
85
96
  try:
86
- # Rimuovi il torrent
87
- self.qb.delete_permanently(torrent_info['hash'])
88
-
97
+ self.qb.torrents_delete(delete_files=True, torrent_hashes=torrent_info.hash)
89
98
  except Exception as delete_error:
90
- logging.error(f"Errore durante la rimozione del torrent: {delete_error}")
99
+ logging.error(f"Error while removing torrent: {delete_error}")
91
100
 
92
- # Resetta l'ultimo hash
93
101
  self.latest_torrent_hash = None
94
102
 
95
103
  def add_magnet_link(self, magnet_link):
96
104
  """
97
- Aggiunge un magnet link e recupera le informazioni dettagliate.
98
-
99
- Args:
100
- magnet_link (str): Magnet link da aggiungere
105
+ Adds a magnet link and retrieves detailed torrent information.
106
+
107
+ Arguments:
108
+ magnet_link (str): Magnet link to add.
101
109
 
102
110
  Returns:
103
- dict: Informazioni del torrent aggiunto, o None se fallisce
111
+ dict: Information about the added torrent, or None in case of error.
104
112
  """
105
-
106
- # Estrai l'hash dal magnet link
107
113
  magnet_hash_match = re.search(r'urn:btih:([0-9a-fA-F]+)', magnet_link)
108
-
109
114
  if not magnet_hash_match:
110
- raise ValueError("Hash del magnet link non trovato")
115
+ raise ValueError("Magnet link hash not found")
111
116
 
112
117
  magnet_hash = magnet_hash_match.group(1).lower()
113
118
 
114
- # Estrai il nome del file dal magnet link (se presente)
119
+ # Extract the torrent name, if available
115
120
  name_match = re.search(r'dn=([^&]+)', magnet_link)
116
- torrent_name = name_match.group(1).replace('+', ' ') if name_match else "Nome non disponibile"
121
+ torrent_name = name_match.group(1).replace('+', ' ') if name_match else "Name not available"
117
122
 
118
- # Salva il timestamp prima di aggiungere il torrent
123
+ # Save the timestamp before adding the torrent
119
124
  before_add_time = time.time()
120
125
 
121
- # Aggiungi il magnet link
122
- console.print(f"[cyan]Aggiunta magnet link[/cyan]: [red]{magnet_link}")
123
- self.qb.download_from_link(magnet_link)
126
+ console.print(f"[cyan]Adding magnet link ...")
127
+ self.qb.torrents_add(urls=magnet_link)
124
128
 
125
- # Aspetta un attimo per essere sicuri che il torrent sia stato aggiunto
126
129
  time.sleep(1)
127
130
 
128
- # Cerca il torrent
129
- torrents = self.qb.torrents()
131
+ torrents = self.qb.torrents_info()
130
132
  matching_torrents = [
131
133
  t for t in torrents
132
- if (t['hash'].lower() == magnet_hash) or (t.get('added_on', 0) > before_add_time)
134
+ if (t.hash.lower() == magnet_hash) or (getattr(t, 'added_on', 0) > before_add_time)
133
135
  ]
134
136
 
135
137
  if not matching_torrents:
136
- raise ValueError("Nessun torrent corrispondente trovato")
138
+ raise ValueError("No matching torrent found")
137
139
 
138
- # Prendi il primo torrent corrispondente
139
140
  torrent_info = matching_torrents[0]
140
141
 
141
- # Formatta e stampa le informazioni
142
- console.print("\n[bold green]🔗 Dettagli Torrent Aggiunto:[/bold green]")
143
- console.print(f"[yellow]Name:[/yellow] {torrent_info.get('name', torrent_name)}")
144
- console.print(f"[yellow]Hash:[/yellow] {torrent_info['hash']}")
142
+ console.print("\n[bold green]Added Torrent Details:[/bold green]")
143
+ console.print(f"[yellow]Name:[/yellow] {torrent_info.name or torrent_name}")
144
+ console.print(f"[yellow]Hash:[/yellow] {torrent_info.hash}")
145
145
  print()
146
146
 
147
- # Salva l'hash per usi successivi e il path
148
- self.latest_torrent_hash = torrent_info['hash']
149
- self.output_file = torrent_info['content_path']
150
- self.file_name = torrent_info['name']
147
+ self.latest_torrent_hash = torrent_info.hash
148
+ self.output_file = torrent_info.content_path
149
+ self.file_name = torrent_info.name
151
150
 
152
- # Controlla che sia possibile il download
151
+ # Wait and verify if the download is possible
153
152
  time.sleep(5)
154
- self.delete_magnet(self.qb.get_torrent(self.latest_torrent_hash))
153
+ self.delete_magnet(self.qb.torrents_info(torrent_hashes=self.latest_torrent_hash)[0])
155
154
 
156
155
  return torrent_info
157
156
 
158
157
  def start_download(self):
159
158
  """
160
- Starts downloading the latest added torrent and monitors progress.
161
- """
159
+ Starts downloading the added torrent and monitors its progress.
160
+ """
162
161
  if self.latest_torrent_hash is not None:
163
162
  try:
164
-
165
- # Custom bar for mobile and pc
166
- if USE_LARGE_BAR:
163
+
164
+ # Custom progress bar for mobile and PC
165
+ if get_use_large_bar():
167
166
  bar_format = (
168
167
  f"{Colors.YELLOW}[TOR] {Colors.WHITE}({Colors.CYAN}video{Colors.WHITE}): "
169
168
  f"{Colors.RED}{{percentage:.2f}}% {Colors.MAGENTA}{{bar}} {Colors.WHITE}[ "
@@ -189,35 +188,42 @@ class TOR_downloader:
189
188
  with progress_bar as pbar:
190
189
  while True:
191
190
 
192
- # Get variable from qtorrent
193
- torrent_info = self.qb.get_torrent(self.latest_torrent_hash)
194
- self.save_path = torrent_info['save_path']
195
- self.torrent_name = torrent_info['name']
191
+ torrent_info = self.qb.torrents_info(torrent_hashes=self.latest_torrent_hash)[0]
192
+ self.save_path = torrent_info.save_path
193
+ self.torrent_name = torrent_info.name
196
194
 
197
- # Fetch important variable
198
- pieces_have = torrent_info['pieces_have']
199
- pieces_num = torrent_info['pieces_num']
200
- progress = (pieces_have / pieces_num) * 100 if pieces_num else 0
195
+ progress = torrent_info.progress * 100
201
196
  pbar.n = progress
202
197
 
203
- download_speed = torrent_info['dl_speed']
204
- total_size = torrent_info['total_size']
205
- downloaded_size = torrent_info['total_downloaded']
198
+ download_speed = torrent_info.dlspeed
199
+ total_size = torrent_info.size
200
+ downloaded_size = torrent_info.downloaded
206
201
 
207
- # Format variable
202
+ # Format the downloaded size
208
203
  downloaded_size_str = internet_manager.format_file_size(downloaded_size)
209
204
  downloaded_size = downloaded_size_str.split(' ')[0]
210
205
 
206
+ # Safely format the total size
211
207
  total_size_str = internet_manager.format_file_size(total_size)
212
- total_size = total_size_str.split(' ')[0]
213
- total_size_unit = total_size_str.split(' ')[1]
208
+ total_size_parts = total_size_str.split(' ')
209
+ if len(total_size_parts) >= 2:
210
+ total_size = total_size_parts[0]
211
+ total_size_unit = total_size_parts[1]
212
+ else:
213
+ total_size = total_size_str
214
+ total_size_unit = ""
214
215
 
216
+ # Safely format the average download speed
215
217
  average_internet_str = internet_manager.format_transfer_speed(download_speed)
216
- average_internet = average_internet_str.split(' ')[0]
217
- average_internet_unit = average_internet_str.split(' ')[1]
218
+ average_internet_parts = average_internet_str.split(' ')
219
+ if len(average_internet_parts) >= 2:
220
+ average_internet = average_internet_parts[0]
221
+ average_internet_unit = average_internet_parts[1]
222
+ else:
223
+ average_internet = average_internet_str
224
+ average_internet_unit = ""
218
225
 
219
- # Update the progress bar's postfix
220
- if USE_LARGE_BAR:
226
+ if get_use_large_bar():
221
227
  pbar.set_postfix_str(
222
228
  f"{Colors.WHITE}[ {Colors.GREEN}{downloaded_size} {Colors.WHITE}< {Colors.GREEN}{total_size} {Colors.RED}{total_size_unit} "
223
229
  f"{Colors.WHITE}| {Colors.CYAN}{average_internet} {Colors.RED}{average_internet_unit}"
@@ -231,7 +237,6 @@ class TOR_downloader:
231
237
  pbar.refresh()
232
238
  time.sleep(0.2)
233
239
 
234
- # Break at the end
235
240
  if int(progress) == 100:
236
241
  break
237
242
 
@@ -239,7 +244,15 @@ class TOR_downloader:
239
244
  logging.info("Download process interrupted.")
240
245
 
241
246
  def is_file_in_use(self, file_path: str) -> bool:
242
- """Check if a file is in use by any process."""
247
+ """
248
+ Checks if a file is being used by any process.
249
+
250
+ Parameters:
251
+ - file_path (str): The file path to check.
252
+
253
+ Returns:
254
+ - bool: True if the file is in use, False otherwise.
255
+ """
243
256
  for proc in psutil.process_iter(['open_files']):
244
257
  try:
245
258
  if any(file_path == f.path for f in proc.info['open_files'] or []):
@@ -251,21 +264,20 @@ class TOR_downloader:
251
264
 
252
265
  def move_downloaded_files(self, destination: str):
253
266
  """
254
- Moves downloaded files of the latest torrent to another location.
255
-
267
+ Moves the downloaded files of the most recent torrent to a new location.
268
+
256
269
  Parameters:
257
- - destination (str): Destination directory to move files.
258
-
270
+ - destination (str): Destination folder.
271
+
259
272
  Returns:
260
- - bool: True if files are moved successfully, False otherwise.
273
+ - bool: True if the move was successful, False otherwise.
261
274
  """
262
275
  console.print(f"[cyan]Destination folder: [red]{destination}")
263
276
 
264
277
  try:
265
-
266
- # Ensure the file is not in use
267
- timeout = 3
278
+ timeout = 5
268
279
  elapsed = 0
280
+
269
281
  while self.is_file_in_use(self.output_file) and elapsed < timeout:
270
282
  time.sleep(1)
271
283
  elapsed += 1
@@ -273,24 +285,20 @@ class TOR_downloader:
273
285
  if elapsed == timeout:
274
286
  raise Exception(f"File '{self.output_file}' is in use and could not be moved.")
275
287
 
276
- # Ensure destination directory exists
277
288
  os.makedirs(destination, exist_ok=True)
278
289
 
279
- # Perform the move operation
280
290
  try:
281
291
  shutil.move(self.output_file, destination)
282
-
283
292
  except OSError as e:
284
- if e.errno == 17: # Cross-disk move error
285
- # Perform copy and delete manually
293
+ if e.errno == 17: # Error when moving between different disks
286
294
  shutil.copy2(self.output_file, destination)
287
295
  os.remove(self.output_file)
288
296
  else:
289
297
  raise
290
298
 
291
- # Delete the torrent data
292
299
  time.sleep(5)
293
- self.qb.delete_permanently(self.qb.torrents()[-1]['hash'])
300
+ last_torrent = self.qb.torrents_info()[-1]
301
+ self.qb.torrents_delete(delete_files=True, torrent_hashes=last_torrent.hash)
294
302
  return True
295
303
 
296
304
  except Exception as e:
@@ -1,4 +1,4 @@
1
1
  # 18.04.24
2
2
 
3
3
  from .command import join_video, join_audios, join_subtitle
4
- from .util import print_duration_table, get_video_duration
4
+ from .util import print_duration_table, get_video_duration
@@ -6,12 +6,16 @@ import threading
6
6
  import subprocess
7
7
 
8
8
 
9
+ # External library
10
+ from rich.console import Console
11
+
12
+
9
13
  # Internal utilities
10
- from StreamingCommunity.Util.console import console
11
14
  from StreamingCommunity.Util.os import internet_manager
12
15
 
13
16
 
14
17
  # Variable
18
+ console = Console()
15
19
  terminate_flag = threading.Event()
16
20
 
17
21
 
@@ -24,8 +28,6 @@ def capture_output(process: subprocess.Popen, description: str) -> None:
24
28
  - description (str): Description of the command being executed.
25
29
  """
26
30
  try:
27
-
28
- # Variable to store the length of the longest progress string
29
31
  max_length = 0
30
32
 
31
33
  for line in iter(process.stdout.readline, ''):
@@ -59,7 +61,7 @@ def capture_output(process: subprocess.Popen, description: str) -> None:
59
61
 
60
62
 
61
63
  # Construct the progress string with formatted output information
62
- progress_string = (f" {description}[white]: "
64
+ progress_string = (f" {description}[white]: "
63
65
  f"([green]'speed': [yellow]{data.get('speed', 'N/A')}[white], "
64
66
  f"[green]'size': [yellow]{internet_manager.format_file_size(byte_size)}[white])")
65
67
  max_length = max(max_length, len(progress_string))
@@ -94,10 +96,7 @@ def parse_output_line(line: str) -> dict:
94
96
  dict: A dictionary containing parsed information.
95
97
  """
96
98
  try:
97
-
98
99
  data = {}
99
-
100
- # Split the line by whitespace and extract key-value pairs
101
100
  parts = line.replace(" ", "").replace("= ", "=").split()
102
101
 
103
102
  for part in parts:
@@ -123,7 +122,7 @@ def terminate_process(process):
123
122
  - process (subprocess.Popen): The subprocess to terminate.
124
123
  """
125
124
  try:
126
- if process.poll() is None: # Check if the process is still running
125
+ if process.poll() is None:
127
126
  process.kill()
128
127
  except Exception as e:
129
128
  logging.error(f"Failed to terminate process: {e}")
@@ -137,7 +136,6 @@ def capture_ffmpeg_real_time(ffmpeg_command: list, description: str) -> None:
137
136
  - ffmpeg_command (list): The command to execute ffmpeg.
138
137
  - description (str): Description of the command being executed.
139
138
  """
140
-
141
139
  global terminate_flag
142
140
 
143
141
  # Clear the terminate_flag before starting a new capture
@@ -163,8 +161,8 @@ def capture_ffmpeg_real_time(ffmpeg_command: list, description: str) -> None:
163
161
  logging.error(f"Error in ffmpeg process: {e}")
164
162
 
165
163
  finally:
166
- terminate_flag.set() # Signal the output capture thread to terminate
167
- output_thread.join() # Wait for the output capture thread to complete
164
+ terminate_flag.set()
165
+ output_thread.join()
168
166
 
169
167
  except Exception as e:
170
- logging.error(f"Failed to start ffmpeg process: {e}")
168
+ logging.error(f"Failed to start ffmpeg process: {e}")
@@ -6,10 +6,13 @@ import subprocess
6
6
  from typing import List, Dict, Tuple, Optional
7
7
 
8
8
 
9
+ # External library
10
+ from rich.console import Console
11
+
12
+
9
13
  # Internal utilities
10
- from StreamingCommunity.Util._jsonConfig import config_manager
11
- from StreamingCommunity.Util.os import os_manager, os_summary, suppress_output
12
- from StreamingCommunity.Util.console import console
14
+ from StreamingCommunity.Util.config_json import config_manager, get_use_large_bar
15
+ from StreamingCommunity.Util.os import os_manager, suppress_output, get_ffmpeg_path
13
16
 
14
17
 
15
18
  # Logic class
@@ -30,9 +33,7 @@ FFMPEG_DEFAULT_PRESET = config_manager.get("M3U8_CONVERSION", "default_preset")
30
33
 
31
34
 
32
35
  # Variable
33
- USE_LARGE_BAR = not ("android" in sys.platform or "ios" in sys.platform)
34
- FFMPEG_PATH = os_summary.ffmpeg_path
35
-
36
+ console = Console()
36
37
 
37
38
 
38
39
  def check_subtitle_encoders() -> Tuple[Optional[bool], Optional[bool]]:
@@ -45,7 +46,7 @@ def check_subtitle_encoders() -> Tuple[Optional[bool], Optional[bool]]:
45
46
  """
46
47
  try:
47
48
  result = subprocess.run(
48
- [FFMPEG_PATH, '-encoders'],
49
+ [get_ffmpeg_path(), '-encoders'],
49
50
  capture_output=True,
50
51
  text=True,
51
52
  check=True
@@ -99,7 +100,7 @@ def join_video(video_path: str, out_path: str, codec: M3U8_Codec = None):
99
100
  - out_path (str): The path to save the output file.
100
101
  - codec (M3U8_Codec): The video codec to use. Defaults to 'copy'.
101
102
  """
102
- ffmpeg_cmd = [FFMPEG_PATH]
103
+ ffmpeg_cmd = [get_ffmpeg_path()]
103
104
 
104
105
  # Enabled the use of gpu
105
106
  if USE_GPU:
@@ -156,7 +157,7 @@ def join_video(video_path: str, out_path: str, codec: M3U8_Codec = None):
156
157
  subprocess.run(ffmpeg_cmd, check=True)
157
158
  else:
158
159
 
159
- if USE_LARGE_BAR:
160
+ if get_use_large_bar():
160
161
  capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join video")
161
162
  print()
162
163
 
@@ -182,7 +183,7 @@ def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: s
182
183
  video_audio_same_duration = check_duration_v_a(video_path, audio_tracks[0].get('path'))
183
184
 
184
185
  # Start command with locate ffmpeg
185
- ffmpeg_cmd = [FFMPEG_PATH]
186
+ ffmpeg_cmd = [get_ffmpeg_path()]
186
187
 
187
188
  # Enabled the use of gpu
188
189
  if USE_GPU:
@@ -252,7 +253,7 @@ def join_audios(video_path: str, audio_tracks: List[Dict[str, str]], out_path: s
252
253
  subprocess.run(ffmpeg_cmd, check=True)
253
254
  else:
254
255
 
255
- if USE_LARGE_BAR:
256
+ if get_use_large_bar():
256
257
  capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join audio")
257
258
  print()
258
259
 
@@ -275,7 +276,7 @@ def join_subtitle(video_path: str, subtitles_list: List[Dict[str, str]], out_pat
275
276
  Each dictionary should contain the 'path' key with the path to the subtitle file and the 'name' key with the name of the subtitle.
276
277
  - out_path (str): The path to save the output file.
277
278
  """
278
- ffmpeg_cmd = [FFMPEG_PATH, "-i", video_path]
279
+ ffmpeg_cmd = [get_ffmpeg_path(), "-i", video_path]
279
280
 
280
281
  # Add subtitle input files first
281
282
  for subtitle in subtitles_list:
@@ -305,9 +306,9 @@ def join_subtitle(video_path: str, subtitles_list: List[Dict[str, str]], out_pat
305
306
  # Run join
306
307
  if DEBUG_MODE:
307
308
  subprocess.run(ffmpeg_cmd, check=True)
308
- else:
309
309
 
310
- if USE_LARGE_BAR:
310
+ else:
311
+ if get_use_large_bar():
311
312
  capture_ffmpeg_real_time(ffmpeg_cmd, "[cyan]Join subtitle")
312
313
  print()
313
314
 
@@ -8,14 +8,16 @@ import logging
8
8
  from typing import Tuple
9
9
 
10
10
 
11
+ # External library
12
+ from rich.console import Console
13
+
14
+
11
15
  # Internal utilities
12
- from StreamingCommunity.Util.console import console
13
- from StreamingCommunity.Util.os import os_summary
16
+ from StreamingCommunity.Util.os import get_ffprobe_path
14
17
 
15
18
 
16
19
  # Variable
17
- FFPROB_PATH = os_summary.ffprobe_path
18
-
20
+ console = Console()
19
21
 
20
22
 
21
23
  def has_audio_stream(video_path: str) -> bool:
@@ -29,7 +31,7 @@ def has_audio_stream(video_path: str) -> bool:
29
31
  has_audio (bool): True if the input video has an audio stream, False otherwise.
30
32
  """
31
33
  try:
32
- ffprobe_cmd = [FFPROB_PATH, '-v', 'error', '-print_format', 'json', '-select_streams', 'a', '-show_streams', video_path]
34
+ ffprobe_cmd = [get_ffprobe_path(), '-v', 'error', '-print_format', 'json', '-select_streams', 'a', '-show_streams', video_path]
33
35
  logging.info(f"FFmpeg command: {ffprobe_cmd}")
34
36
 
35
37
  with subprocess.Popen(ffprobe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as proc:
@@ -57,7 +59,7 @@ def get_video_duration(file_path: str) -> float:
57
59
  """
58
60
 
59
61
  try:
60
- ffprobe_cmd = [FFPROB_PATH, '-v', 'error', '-show_format', '-print_format', 'json', file_path]
62
+ ffprobe_cmd = [get_ffprobe_path(), '-v', 'error', '-show_format', '-print_format', 'json', file_path]
61
63
  logging.info(f"FFmpeg command: {ffprobe_cmd}")
62
64
 
63
65
  # Use a with statement to ensure the subprocess is cleaned up properly
@@ -83,37 +85,6 @@ def get_video_duration(file_path: str) -> float:
83
85
  sys.exit(0)
84
86
 
85
87
 
86
- def get_video_duration_s(filename):
87
- """
88
- Get the duration of a video file using ffprobe.
89
-
90
- Parameters:
91
- - filename (str): Path to the video file (e.g., 'sim.mp4')
92
-
93
- Returns:
94
- - duration (float): Duration of the video in seconds, or None if an error occurs.
95
- """
96
- ffprobe_cmd = ['ffprobe', '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', filename]
97
-
98
- try:
99
-
100
- # Run ffprobe command and capture output
101
- result = subprocess.run(ffprobe_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, text=True)
102
-
103
- # Extract duration from the output
104
- duration_str = result.stdout.strip()
105
- duration = float(duration_str) # Convert duration to float
106
-
107
- return int(duration)
108
-
109
- except subprocess.CalledProcessError as e:
110
- print(f"Error running ffprobe: {e}")
111
- return None
112
- except ValueError as e:
113
- print(f"Error converting duration to float: {e}")
114
- return None
115
-
116
-
117
88
  def format_duration(seconds: float) -> Tuple[int, int, int]:
118
89
  """
119
90
  Format duration in seconds into hours, minutes, and seconds.
@@ -172,7 +143,7 @@ def get_ffprobe_info(file_path):
172
143
  """
173
144
  try:
174
145
  result = subprocess.run(
175
- [FFPROB_PATH, '-v', 'error', '-show_format', '-show_streams', '-print_format', 'json', file_path],
146
+ [get_ffprobe_path(), '-v', 'error', '-show_format', '-show_streams', '-print_format', 'json', file_path],
176
147
  stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True
177
148
  )
178
149
  output = result.stdout