StreamingCommunity 1.9.8__py3-none-any.whl → 1.9.90__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 (93) hide show
  1. StreamingCommunity/Api/Player/Helper/Vixcloud/js_parser.py +143 -0
  2. StreamingCommunity/Api/Player/Helper/Vixcloud/util.py +145 -0
  3. StreamingCommunity/Api/Player/ddl.py +89 -0
  4. StreamingCommunity/Api/Player/maxstream.py +151 -0
  5. StreamingCommunity/Api/Player/supervideo.py +194 -0
  6. StreamingCommunity/Api/Player/vixcloud.py +273 -0
  7. StreamingCommunity/Api/Site/1337xx/__init__.py +51 -0
  8. StreamingCommunity/Api/Site/1337xx/costant.py +15 -0
  9. StreamingCommunity/Api/Site/1337xx/site.py +86 -0
  10. StreamingCommunity/Api/Site/1337xx/title.py +66 -0
  11. StreamingCommunity/Api/Site/altadefinizione/__init__.py +51 -0
  12. StreamingCommunity/Api/Site/altadefinizione/costant.py +15 -0
  13. StreamingCommunity/Api/Site/altadefinizione/film.py +74 -0
  14. StreamingCommunity/Api/Site/altadefinizione/site.py +89 -0
  15. StreamingCommunity/Api/Site/animeunity/__init__.py +51 -0
  16. StreamingCommunity/Api/Site/animeunity/costant.py +15 -0
  17. StreamingCommunity/Api/Site/animeunity/film_serie.py +135 -0
  18. StreamingCommunity/Api/Site/animeunity/site.py +167 -0
  19. StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py +97 -0
  20. StreamingCommunity/Api/Site/cb01new/__init__.py +52 -0
  21. StreamingCommunity/Api/Site/cb01new/costant.py +15 -0
  22. StreamingCommunity/Api/Site/cb01new/film.py +73 -0
  23. StreamingCommunity/Api/Site/cb01new/site.py +76 -0
  24. StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +58 -0
  25. StreamingCommunity/Api/Site/ddlstreamitaly/costant.py +16 -0
  26. StreamingCommunity/Api/Site/ddlstreamitaly/series.py +146 -0
  27. StreamingCommunity/Api/Site/ddlstreamitaly/site.py +95 -0
  28. StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +85 -0
  29. StreamingCommunity/Api/Site/guardaserie/__init__.py +53 -0
  30. StreamingCommunity/Api/Site/guardaserie/costant.py +15 -0
  31. StreamingCommunity/Api/Site/guardaserie/series.py +199 -0
  32. StreamingCommunity/Api/Site/guardaserie/site.py +86 -0
  33. StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +110 -0
  34. StreamingCommunity/Api/Site/ilcorsaronero/__init__.py +52 -0
  35. StreamingCommunity/Api/Site/ilcorsaronero/costant.py +15 -0
  36. StreamingCommunity/Api/Site/ilcorsaronero/site.py +63 -0
  37. StreamingCommunity/Api/Site/ilcorsaronero/title.py +46 -0
  38. StreamingCommunity/Api/Site/ilcorsaronero/util/ilCorsarScraper.py +141 -0
  39. StreamingCommunity/Api/Site/mostraguarda/__init__.py +49 -0
  40. StreamingCommunity/Api/Site/mostraguarda/costant.py +15 -0
  41. StreamingCommunity/Api/Site/mostraguarda/film.py +99 -0
  42. StreamingCommunity/Api/Site/streamingcommunity/__init__.py +56 -0
  43. StreamingCommunity/Api/Site/streamingcommunity/costant.py +15 -0
  44. StreamingCommunity/Api/Site/streamingcommunity/film.py +75 -0
  45. StreamingCommunity/Api/Site/streamingcommunity/series.py +206 -0
  46. StreamingCommunity/Api/Site/streamingcommunity/site.py +137 -0
  47. StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +123 -0
  48. StreamingCommunity/Api/Template/Class/SearchType.py +101 -0
  49. StreamingCommunity/Api/Template/Util/__init__.py +5 -0
  50. StreamingCommunity/Api/Template/Util/get_domain.py +173 -0
  51. StreamingCommunity/Api/Template/Util/manage_ep.py +179 -0
  52. StreamingCommunity/Api/Template/Util/recall_search.py +37 -0
  53. StreamingCommunity/Api/Template/__init__.py +3 -0
  54. StreamingCommunity/Api/Template/site.py +87 -0
  55. StreamingCommunity/Lib/Downloader/HLS/downloader.py +946 -0
  56. StreamingCommunity/Lib/Downloader/HLS/proxyes.py +110 -0
  57. StreamingCommunity/Lib/Downloader/HLS/segments.py +561 -0
  58. StreamingCommunity/Lib/Downloader/MP4/downloader.py +155 -0
  59. StreamingCommunity/Lib/Downloader/TOR/downloader.py +296 -0
  60. StreamingCommunity/Lib/Downloader/__init__.py +5 -0
  61. StreamingCommunity/Lib/FFmpeg/__init__.py +4 -0
  62. StreamingCommunity/Lib/FFmpeg/capture.py +170 -0
  63. StreamingCommunity/Lib/FFmpeg/command.py +296 -0
  64. StreamingCommunity/Lib/FFmpeg/util.py +249 -0
  65. StreamingCommunity/Lib/M3U8/__init__.py +6 -0
  66. StreamingCommunity/Lib/M3U8/decryptor.py +164 -0
  67. StreamingCommunity/Lib/M3U8/estimator.py +176 -0
  68. StreamingCommunity/Lib/M3U8/parser.py +666 -0
  69. StreamingCommunity/Lib/M3U8/url_fixer.py +52 -0
  70. StreamingCommunity/Lib/TMBD/__init__.py +2 -0
  71. StreamingCommunity/Lib/TMBD/obj_tmbd.py +39 -0
  72. StreamingCommunity/Lib/TMBD/tmdb.py +346 -0
  73. StreamingCommunity/Upload/update.py +68 -0
  74. StreamingCommunity/Upload/version.py +5 -0
  75. StreamingCommunity/Util/_jsonConfig.py +204 -0
  76. StreamingCommunity/Util/call_stack.py +42 -0
  77. StreamingCommunity/Util/color.py +20 -0
  78. StreamingCommunity/Util/console.py +12 -0
  79. StreamingCommunity/Util/ffmpeg_installer.py +311 -0
  80. StreamingCommunity/Util/headers.py +147 -0
  81. StreamingCommunity/Util/logger.py +53 -0
  82. StreamingCommunity/Util/message.py +64 -0
  83. StreamingCommunity/Util/os.py +554 -0
  84. StreamingCommunity/Util/table.py +229 -0
  85. StreamingCommunity/__init__.py +0 -0
  86. StreamingCommunity/run.py +2 -2
  87. {StreamingCommunity-1.9.8.dist-info → StreamingCommunity-1.9.90.dist-info}/METADATA +7 -10
  88. StreamingCommunity-1.9.90.dist-info/RECORD +92 -0
  89. {StreamingCommunity-1.9.8.dist-info → StreamingCommunity-1.9.90.dist-info}/WHEEL +1 -1
  90. {StreamingCommunity-1.9.8.dist-info → StreamingCommunity-1.9.90.dist-info}/entry_points.txt +0 -1
  91. StreamingCommunity-1.9.8.dist-info/RECORD +0 -7
  92. {StreamingCommunity-1.9.8.dist-info → StreamingCommunity-1.9.90.dist-info}/LICENSE +0 -0
  93. {StreamingCommunity-1.9.8.dist-info → StreamingCommunity-1.9.90.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,155 @@
1
+ # 09.06.24
2
+
3
+ import os
4
+ import sys
5
+ import ssl
6
+ import certifi
7
+ import logging
8
+
9
+
10
+ # External libraries
11
+ import httpx
12
+ from tqdm import tqdm
13
+
14
+
15
+ # Internal utilities
16
+ from StreamingCommunity.Util.headers import get_headers
17
+ from StreamingCommunity.Util.color import Colors
18
+ from StreamingCommunity.Util.console import console, Panel
19
+ from StreamingCommunity.Util._jsonConfig import config_manager
20
+ from StreamingCommunity.Util.os import internet_manager
21
+
22
+
23
+ # Logic class
24
+ from ...FFmpeg import print_duration_table
25
+
26
+
27
+ # Suppress SSL warnings
28
+ import urllib3
29
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
30
+
31
+
32
+ # Config
33
+ GET_ONLY_LINK = config_manager.get_bool('M3U8_PARSER', 'get_only_link')
34
+ TQDM_USE_LARGE_BAR = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_use_large_bar')
35
+ REQUEST_TIMEOUT = config_manager.get_float('REQUESTS', 'timeout')
36
+
37
+
38
+
39
+ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = None):
40
+ """
41
+ Downloads an MP4 video from a given URL with robust error handling and SSL bypass.
42
+
43
+ Parameters:
44
+ - url (str): The URL of the MP4 video to download.
45
+ - path (str): The local path where the downloaded MP4 file will be saved.
46
+ - referer (str, optional): The referer header value.
47
+ - headers_ (dict, optional): Custom headers for the request.
48
+ """
49
+ # Early return for link-only mode
50
+ if GET_ONLY_LINK:
51
+ return {'path': path, 'url': url}
52
+
53
+ # Validate URL
54
+ if not (url.lower().startswith('http://') or url.lower().startswith('https://')):
55
+ logging.error(f"Invalid URL: {url}")
56
+ console.print(f"[bold red]Invalid URL: {url}[/bold red]")
57
+ return None
58
+
59
+ # Prepare headers
60
+ try:
61
+ headers = {}
62
+ if referer:
63
+ headers['Referer'] = referer
64
+
65
+ # Use custom headers if provided, otherwise use default user agent
66
+ if headers_:
67
+ headers.update(headers_)
68
+ else:
69
+ headers['User-Agent'] = get_headers()
70
+
71
+ except Exception as header_err:
72
+ logging.error(f"Error preparing headers: {header_err}")
73
+ console.print(f"[bold red]Error preparing headers: {header_err}[/bold red]")
74
+ return None
75
+
76
+ try:
77
+ # Create a custom transport that bypasses SSL verification
78
+ transport = httpx.HTTPTransport(
79
+ verify=False, # Disable SSL certificate verification
80
+ http2=True # Optional: enable HTTP/2 support
81
+ )
82
+
83
+ # Download with streaming and progress tracking
84
+ with httpx.Client(transport=transport, timeout=httpx.Timeout(60.0)) as client:
85
+ with client.stream("GET", url, headers=headers, timeout=REQUEST_TIMEOUT) as response:
86
+ response.raise_for_status()
87
+
88
+ # Get total file size
89
+ total = int(response.headers.get('content-length', 0))
90
+
91
+ # Handle empty streams
92
+ if total == 0:
93
+ console.print("[bold red]No video stream found.[/bold red]")
94
+ return None
95
+
96
+ # Create progress bar
97
+ progress_bar = tqdm(
98
+ total=total,
99
+ ascii='░▒█',
100
+ bar_format=f"{Colors.YELLOW}[MP4] {Colors.WHITE}({Colors.CYAN}video{Colors.WHITE}): "
101
+ f"{Colors.RED}{{percentage:.2f}}% {Colors.MAGENTA}{{bar}} {Colors.WHITE}[ "
102
+ f"{Colors.YELLOW}{{n_fmt}}{Colors.WHITE} / {Colors.RED}{{total_fmt}} {Colors.WHITE}] "
103
+ f"{Colors.YELLOW}{{elapsed}} {Colors.WHITE}< {Colors.CYAN}{{remaining}} {Colors.WHITE}| "
104
+ f"{Colors.YELLOW}{{rate_fmt}}{{postfix}} {Colors.WHITE}]",
105
+ unit='iB',
106
+ unit_scale=True,
107
+ desc='Downloading',
108
+ mininterval=0.05
109
+ )
110
+
111
+ # Ensure directory exists
112
+ os.makedirs(os.path.dirname(path), exist_ok=True)
113
+
114
+ # Download file
115
+ with open(path, 'wb') as file, progress_bar as bar:
116
+ downloaded = 0
117
+ for chunk in response.iter_bytes(chunk_size=1024):
118
+ if chunk:
119
+ size = file.write(chunk)
120
+ downloaded += size
121
+ bar.update(size)
122
+
123
+ # Optional: Add a check to stop download if needed
124
+ # if downloaded > MAX_DOWNLOAD_SIZE:
125
+ # break
126
+
127
+ # Post-download processing
128
+ if os.path.exists(path) and os.path.getsize(path) > 0:
129
+ console.print(Panel(
130
+ f"[bold green]Download completed![/bold green]\n"
131
+ f"[cyan]File size: [bold red]{internet_manager.format_file_size(os.path.getsize(path))}[/bold red]\n"
132
+ f"[cyan]Duration: [bold]{print_duration_table(path, description=False, return_string=True)}[/bold]",
133
+ title=f"{os.path.basename(path.replace('.mp4', ''))}",
134
+ border_style="green"
135
+ ))
136
+ return path
137
+
138
+ else:
139
+ console.print("[bold red]Download failed or file is empty.[/bold red]")
140
+ return None
141
+
142
+ except httpx.HTTPStatusError as http_err:
143
+ logging.error(f"HTTP error occurred: {http_err}")
144
+ console.print(f"[bold red]HTTP Error: {http_err}[/bold red]")
145
+ return None
146
+
147
+ except httpx.RequestError as req_err:
148
+ logging.error(f"Request error: {req_err}")
149
+ console.print(f"[bold red]Request Error: {req_err}[/bold red]")
150
+ return None
151
+
152
+ except Exception as e:
153
+ logging.error(f"Unexpected error during download: {e}")
154
+ console.print(f"[bold red]Unexpected Error: {e}[/bold red]")
155
+ return None
@@ -0,0 +1,296 @@
1
+ # 23.06.24
2
+
3
+ import os
4
+ import re
5
+ import sys
6
+ import time
7
+ import shutil
8
+ import psutil
9
+ import logging
10
+
11
+
12
+ # Internal utilities
13
+ from StreamingCommunity.Util.color import Colors
14
+ from StreamingCommunity.Util.os import internet_manager
15
+ from StreamingCommunity.Util.console import console
16
+ from StreamingCommunity.Util._jsonConfig import config_manager
17
+
18
+
19
+ # External libraries
20
+ from tqdm import tqdm
21
+ from qbittorrent import Client
22
+
23
+
24
+ # 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'])
29
+
30
+ # Config
31
+ TQDM_USE_LARGE_BAR = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_use_large_bar')
32
+ REQUEST_TIMEOUT = config_manager.get_float('REQUESTS', 'timeout')
33
+
34
+
35
+
36
+ class TOR_downloader:
37
+ def __init__(self):
38
+ """
39
+ Initializes the TorrentManager instance.
40
+
41
+ Parameters:
42
+ - host (str): IP address or hostname of the qBittorrent Web UI.
43
+ - port (int): Port number of the qBittorrent Web UI.
44
+ - username (str): Username for logging into qBittorrent.
45
+ - password (str): Password for logging into qBittorrent.
46
+ """
47
+ try:
48
+ console.print(f"[cyan]Connect to: [green]{HOST}:{PORT}")
49
+ self.qb = Client(f'http://{HOST}:{PORT}/')
50
+ except:
51
+ logging.error("Start qbitorrent first.")
52
+ sys.exit(0)
53
+
54
+ self.username = USERNAME
55
+ self.password = PASSWORD
56
+ self.latest_torrent_hash = None
57
+ self.output_file = None
58
+ self.file_name = None
59
+
60
+ self.login()
61
+
62
+ def login(self):
63
+ """
64
+ Logs into the qBittorrent Web UI.
65
+ """
66
+ try:
67
+ self.qb.login(self.username, self.password)
68
+ self.logged_in = True
69
+ logging.info("Successfully logged in to qBittorrent.")
70
+
71
+ except Exception as e:
72
+ logging.error(f"Failed to log in: {str(e)}")
73
+ self.logged_in = False
74
+
75
+ def delete_magnet(self, torrent_info):
76
+
77
+ if (int(torrent_info.get('dl_speed')) == 0 and
78
+ int(torrent_info.get('peers')) == 0 and
79
+ int(torrent_info.get('seeds')) == 0):
80
+
81
+ # Elimina il torrent appena aggiunto
82
+ console.print(f"[bold red]⚠️ Torrent non scaricabile. Rimozione in corso...[/bold red]")
83
+
84
+ try:
85
+ # Rimuovi il torrent
86
+ self.qb.delete_permanently(torrent_info['hash'])
87
+
88
+ except Exception as delete_error:
89
+ logging.error(f"Errore durante la rimozione del torrent: {delete_error}")
90
+
91
+ # Resetta l'ultimo hash
92
+ self.latest_torrent_hash = None
93
+
94
+ def add_magnet_link(self, magnet_link):
95
+ """
96
+ Aggiunge un magnet link e recupera le informazioni dettagliate.
97
+
98
+ Args:
99
+ magnet_link (str): Magnet link da aggiungere
100
+
101
+ Returns:
102
+ dict: Informazioni del torrent aggiunto, o None se fallisce
103
+ """
104
+
105
+ # Estrai l'hash dal magnet link
106
+ magnet_hash_match = re.search(r'urn:btih:([0-9a-fA-F]+)', magnet_link)
107
+
108
+ if not magnet_hash_match:
109
+ raise ValueError("Hash del magnet link non trovato")
110
+
111
+ magnet_hash = magnet_hash_match.group(1).lower()
112
+
113
+ # Estrai il nome del file dal magnet link (se presente)
114
+ name_match = re.search(r'dn=([^&]+)', magnet_link)
115
+ torrent_name = name_match.group(1).replace('+', ' ') if name_match else "Nome non disponibile"
116
+
117
+ # Salva il timestamp prima di aggiungere il torrent
118
+ before_add_time = time.time()
119
+
120
+ # Aggiungi il magnet link
121
+ console.print(f"[cyan]Aggiunta magnet link[/cyan]: [red]{magnet_link}")
122
+ self.qb.download_from_link(magnet_link)
123
+
124
+ # Aspetta un attimo per essere sicuri che il torrent sia stato aggiunto
125
+ time.sleep(1)
126
+
127
+ # Cerca il torrent
128
+ torrents = self.qb.torrents()
129
+ matching_torrents = [
130
+ t for t in torrents
131
+ if (t['hash'].lower() == magnet_hash) or (t.get('added_on', 0) > before_add_time)
132
+ ]
133
+
134
+ if not matching_torrents:
135
+ raise ValueError("Nessun torrent corrispondente trovato")
136
+
137
+ # Prendi il primo torrent corrispondente
138
+ torrent_info = matching_torrents[0]
139
+
140
+ # Formatta e stampa le informazioni
141
+ console.print("\n[bold green]🔗 Dettagli Torrent Aggiunto:[/bold green]")
142
+ console.print(f"[yellow]Nome:[/yellow] {torrent_info.get('name', torrent_name)}")
143
+ console.print(f"[yellow]Hash:[/yellow] {torrent_info['hash']}")
144
+ console.print(f"[yellow]Dimensione:[/yellow] {internet_manager.format_file_size(torrent_info.get('size'))}")
145
+ print()
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']
151
+
152
+ # Controlla che sia possibile il download
153
+ time.sleep(5)
154
+ self.delete_magnet(self.qb.get_torrent(self.latest_torrent_hash))
155
+
156
+ return torrent_info
157
+
158
+ def start_download(self):
159
+ """
160
+ Starts downloading the latest added torrent and monitors progress.
161
+ """
162
+ if self.latest_torrent_hash is not None:
163
+ try:
164
+
165
+ # Custom bar for mobile and pc
166
+ if TQDM_USE_LARGE_BAR:
167
+ bar_format = (
168
+ f"{Colors.YELLOW}[TOR] {Colors.WHITE}({Colors.CYAN}video{Colors.WHITE}): "
169
+ f"{Colors.RED}{{percentage:.2f}}% {Colors.MAGENTA}{{bar}} {Colors.WHITE}[ "
170
+ f"{Colors.YELLOW}{{elapsed}} {Colors.WHITE}< {Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
171
+ )
172
+ else:
173
+ bar_format = (
174
+ f"{Colors.YELLOW}Proc{Colors.WHITE}: "
175
+ f"{Colors.RED}{{percentage:.2f}}% {Colors.WHITE}| "
176
+ f"{Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
177
+ )
178
+
179
+ progress_bar = tqdm(
180
+ total=100,
181
+ ascii='░▒█',
182
+ bar_format=bar_format,
183
+ unit_scale=True,
184
+ unit_divisor=1024,
185
+ mininterval=0.05
186
+ )
187
+
188
+ with progress_bar as pbar:
189
+ while True:
190
+
191
+ # Get variable from qtorrent
192
+ torrent_info = self.qb.get_torrent(self.latest_torrent_hash)
193
+ self.save_path = torrent_info['save_path']
194
+ self.torrent_name = torrent_info['name']
195
+
196
+ # Fetch important variable
197
+ pieces_have = torrent_info['pieces_have']
198
+ pieces_num = torrent_info['pieces_num']
199
+ progress = (pieces_have / pieces_num) * 100 if pieces_num else 0
200
+ pbar.n = progress
201
+
202
+ download_speed = torrent_info['dl_speed']
203
+ total_size = torrent_info['total_size']
204
+ downloaded_size = torrent_info['total_downloaded']
205
+
206
+ # Format variable
207
+ downloaded_size_str = internet_manager.format_file_size(downloaded_size)
208
+ downloaded_size = downloaded_size_str.split(' ')[0]
209
+
210
+ total_size_str = internet_manager.format_file_size(total_size)
211
+ total_size = total_size_str.split(' ')[0]
212
+ total_size_unit = total_size_str.split(' ')[1]
213
+
214
+ average_internet_str = internet_manager.format_transfer_speed(download_speed)
215
+ average_internet = average_internet_str.split(' ')[0]
216
+ average_internet_unit = average_internet_str.split(' ')[1]
217
+
218
+ # Update the progress bar's postfix
219
+ if TQDM_USE_LARGE_BAR:
220
+ pbar.set_postfix_str(
221
+ f"{Colors.WHITE}[ {Colors.GREEN}{downloaded_size} {Colors.WHITE}< {Colors.GREEN}{total_size} {Colors.RED}{total_size_unit} "
222
+ f"{Colors.WHITE}| {Colors.CYAN}{average_internet} {Colors.RED}{average_internet_unit}"
223
+ )
224
+ else:
225
+ pbar.set_postfix_str(
226
+ f"{Colors.WHITE}[ {Colors.GREEN}{downloaded_size}{Colors.RED} {total_size} "
227
+ f"{Colors.WHITE}| {Colors.CYAN}{average_internet} {Colors.RED}{average_internet_unit}"
228
+ )
229
+
230
+ pbar.refresh()
231
+ time.sleep(0.2)
232
+
233
+ # Break at the end
234
+ if int(progress) == 100:
235
+ break
236
+
237
+ except KeyboardInterrupt:
238
+ logging.info("Download process interrupted.")
239
+
240
+ def is_file_in_use(self, file_path: str) -> bool:
241
+ """Check if a file is in use by any process."""
242
+ for proc in psutil.process_iter(['open_files']):
243
+ try:
244
+ if any(file_path == f.path for f in proc.info['open_files'] or []):
245
+ return True
246
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
247
+ continue
248
+
249
+ return False
250
+
251
+ def move_downloaded_files(self, destination: str):
252
+ """
253
+ Moves downloaded files of the latest torrent to another location.
254
+
255
+ Parameters:
256
+ - destination (str): Destination directory to move files.
257
+
258
+ Returns:
259
+ - bool: True if files are moved successfully, False otherwise.
260
+ """
261
+ console.print(f"[cyan]Destination folder: [red]{destination}")
262
+
263
+ try:
264
+
265
+ # Ensure the file is not in use
266
+ timeout = 3
267
+ elapsed = 0
268
+ while self.is_file_in_use(self.output_file) and elapsed < timeout:
269
+ time.sleep(1)
270
+ elapsed += 1
271
+
272
+ if elapsed == timeout:
273
+ raise Exception(f"File '{self.output_file}' is in use and could not be moved.")
274
+
275
+ # Ensure destination directory exists
276
+ os.makedirs(destination, exist_ok=True)
277
+
278
+ # Perform the move operation
279
+ try:
280
+ shutil.move(self.output_file, destination)
281
+
282
+ except OSError as e:
283
+ if e.errno == 17: # Cross-disk move error
284
+ # Perform copy and delete manually
285
+ shutil.copy2(self.output_file, destination)
286
+ os.remove(self.output_file)
287
+ else:
288
+ raise
289
+
290
+ # Delete the torrent data
291
+ #self.qb.delete_permanently(self.qb.torrents()[-1]['hash'])
292
+ return True
293
+
294
+ except Exception as e:
295
+ print(f"Error moving file: {e}")
296
+ return False
@@ -0,0 +1,5 @@
1
+ # 23.06.24
2
+
3
+ from .HLS.downloader import HLS_Downloader
4
+ from .MP4.downloader import MP4_downloader
5
+ from .TOR.downloader import TOR_downloader
@@ -0,0 +1,4 @@
1
+ # 18.04.24
2
+
3
+ from .command import join_video, join_audios, join_subtitle
4
+ from .util import print_duration_table, get_video_duration
@@ -0,0 +1,170 @@
1
+ # 16.04.24
2
+
3
+ import re
4
+ import logging
5
+ import threading
6
+ import subprocess
7
+
8
+
9
+ # Internal utilities
10
+ from StreamingCommunity.Util.console import console
11
+ from StreamingCommunity.Util.os import internet_manager
12
+
13
+
14
+ # Variable
15
+ terminate_flag = threading.Event()
16
+
17
+
18
+ def capture_output(process: subprocess.Popen, description: str) -> None:
19
+ """
20
+ Function to capture and print output from a subprocess.
21
+
22
+ Parameters:
23
+ - process (subprocess.Popen): The subprocess whose output is captured.
24
+ - description (str): Description of the command being executed.
25
+ """
26
+ try:
27
+
28
+ # Variable to store the length of the longest progress string
29
+ max_length = 0
30
+
31
+ for line in iter(process.stdout.readline, ''):
32
+ try:
33
+ line = line.strip()
34
+ if not line:
35
+ continue
36
+
37
+ logging.info(f"FFMPEG line: {line}")
38
+
39
+ # Capture only error
40
+ if "rror" in str(line):
41
+ console.log(f"[red]FFMPEG: {str(line).strip()}")
42
+
43
+ # Check if termination is requested
44
+ if terminate_flag.is_set():
45
+ break
46
+
47
+ if "size=" in line:
48
+ try:
49
+
50
+ # Parse the output line to extract relevant information
51
+ data = parse_output_line(line)
52
+
53
+ if 'q' in data:
54
+ is_end = (float(data.get('q', -1.0)) == -1.0)
55
+ size_key = 'Lsize' if is_end else 'size'
56
+ byte_size = int(re.findall(r'\d+', data.get(size_key, '0'))[0]) * 1000
57
+ else:
58
+ byte_size = int(re.findall(r'\d+', data.get('size', '0'))[0]) * 1000
59
+
60
+
61
+ # Construct the progress string with formatted output information
62
+ progress_string = (f"[yellow][FFmpeg] [white][{description}[white]]: "
63
+ f"([green]'speed': [yellow]{data.get('speed', 'N/A')}[white], "
64
+ f"[green]'size': [yellow]{internet_manager.format_file_size(byte_size)}[white])")
65
+ max_length = max(max_length, len(progress_string))
66
+
67
+ # Print the progress string to the console, overwriting the previous line
68
+ console.print(progress_string.ljust(max_length), end="\r")
69
+
70
+ except Exception as e:
71
+ logging.error(f"Error parsing output line: {line} - {e}")
72
+
73
+ except Exception as e:
74
+ logging.error(f"Error processing line from subprocess: {e}")
75
+
76
+ except Exception as e:
77
+ logging.error(f"Error in capture_output: {e}")
78
+
79
+ finally:
80
+ try:
81
+ terminate_process(process)
82
+ except Exception as e:
83
+ logging.error(f"Error terminating process: {e}")
84
+
85
+
86
+ def parse_output_line(line: str) -> dict:
87
+ """
88
+ Function to parse the output line and extract relevant information.
89
+
90
+ Parameters:
91
+ - line (str): The output line to parse.
92
+
93
+ Returns:
94
+ dict: A dictionary containing parsed information.
95
+ """
96
+ try:
97
+
98
+ data = {}
99
+
100
+ # Split the line by whitespace and extract key-value pairs
101
+ parts = line.replace(" ", "").replace("= ", "=").split()
102
+
103
+ for part in parts:
104
+ key_value = part.split('=')
105
+
106
+ if len(key_value) == 2:
107
+ key = key_value[0]
108
+ value = key_value[1]
109
+ data[key] = value
110
+
111
+ return data
112
+
113
+ except Exception as e:
114
+ logging.error(f"Error parsing line: {line} - {e}")
115
+ return {}
116
+
117
+
118
+ def terminate_process(process):
119
+ """
120
+ Function to terminate a subprocess if it's still running.
121
+
122
+ Parameters:
123
+ - process (subprocess.Popen): The subprocess to terminate.
124
+ """
125
+ try:
126
+ if process.poll() is None: # Check if the process is still running
127
+ process.kill()
128
+ except Exception as e:
129
+ logging.error(f"Failed to terminate process: {e}")
130
+
131
+
132
+ def capture_ffmpeg_real_time(ffmpeg_command: list, description: str) -> None:
133
+ """
134
+ Function to capture real-time output from ffmpeg process.
135
+
136
+ Parameters:
137
+ - ffmpeg_command (list): The command to execute ffmpeg.
138
+ - description (str): Description of the command being executed.
139
+ """
140
+
141
+ global terminate_flag
142
+
143
+ # Clear the terminate_flag before starting a new capture
144
+ terminate_flag.clear()
145
+
146
+ try:
147
+
148
+ # Start the ffmpeg process with subprocess.Popen
149
+ process = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
150
+
151
+ # Start a thread to capture and print output
152
+ output_thread = threading.Thread(target=capture_output, args=(process, description))
153
+ output_thread.start()
154
+
155
+ try:
156
+ # Wait for ffmpeg process to complete
157
+ process.wait()
158
+
159
+ except KeyboardInterrupt:
160
+ logging.error("Terminating ffmpeg process...")
161
+
162
+ except Exception as e:
163
+ logging.error(f"Error in ffmpeg process: {e}")
164
+
165
+ finally:
166
+ terminate_flag.set() # Signal the output capture thread to terminate
167
+ output_thread.join() # Wait for the output capture thread to complete
168
+
169
+ except Exception as e:
170
+ logging.error(f"Failed to start ffmpeg process: {e}")