StreamingCommunity 2.6.1__py3-none-any.whl → 2.8.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.
- StreamingCommunity/Api/Player/ddl.py +4 -4
- StreamingCommunity/Api/Player/maxstream.py +10 -16
- StreamingCommunity/Api/Player/supervideo.py +9 -35
- StreamingCommunity/Api/Player/vixcloud.py +18 -92
- StreamingCommunity/Api/Site/1337xx/__init__.py +8 -1
- StreamingCommunity/Api/Site/1337xx/site.py +16 -15
- StreamingCommunity/Api/Site/1337xx/title.py +7 -5
- StreamingCommunity/Api/Site/animeunity/__init__.py +9 -2
- StreamingCommunity/Api/Site/animeunity/film_serie.py +12 -5
- StreamingCommunity/Api/Site/animeunity/site.py +14 -10
- StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py +9 -10
- StreamingCommunity/Api/Site/cb01new/__init__.py +8 -1
- StreamingCommunity/Api/Site/cb01new/film.py +7 -1
- StreamingCommunity/Api/Site/cb01new/site.py +24 -15
- StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +9 -2
- StreamingCommunity/Api/Site/ddlstreamitaly/series.py +7 -1
- StreamingCommunity/Api/Site/ddlstreamitaly/site.py +16 -15
- StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +3 -3
- StreamingCommunity/Api/Site/guardaserie/__init__.py +9 -2
- StreamingCommunity/Api/Site/guardaserie/series.py +9 -1
- StreamingCommunity/Api/Site/guardaserie/site.py +23 -22
- StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +5 -4
- StreamingCommunity/Api/Site/mostraguarda/__init__.py +6 -2
- StreamingCommunity/Api/Site/mostraguarda/film.py +10 -6
- StreamingCommunity/Api/Site/streamingcommunity/__init__.py +9 -2
- StreamingCommunity/Api/Site/streamingcommunity/film.py +9 -2
- StreamingCommunity/Api/Site/streamingcommunity/series.py +15 -6
- StreamingCommunity/Api/Site/streamingcommunity/site.py +16 -14
- StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +10 -11
- StreamingCommunity/Api/Template/Util/__init__.py +0 -1
- StreamingCommunity/Api/Template/Util/get_domain.py +31 -134
- StreamingCommunity/Api/Template/Util/manage_ep.py +10 -5
- StreamingCommunity/Api/Template/config_loader.py +14 -10
- StreamingCommunity/Api/Template/site.py +3 -6
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +12 -15
- StreamingCommunity/Lib/Downloader/HLS/segments.py +14 -34
- StreamingCommunity/Lib/Downloader/MP4/downloader.py +14 -11
- StreamingCommunity/Lib/Downloader/TOR/downloader.py +109 -101
- StreamingCommunity/Lib/FFmpeg/__init__.py +1 -1
- StreamingCommunity/Lib/FFmpeg/capture.py +10 -12
- StreamingCommunity/Lib/FFmpeg/command.py +15 -14
- StreamingCommunity/Lib/FFmpeg/util.py +9 -38
- StreamingCommunity/Lib/M3U8/decryptor.py +72 -146
- StreamingCommunity/Lib/M3U8/estimator.py +8 -16
- StreamingCommunity/Lib/M3U8/parser.py +1 -17
- StreamingCommunity/Lib/M3U8/url_fixer.py +1 -4
- StreamingCommunity/Lib/TMBD/__init__.py +2 -0
- StreamingCommunity/Lib/TMBD/obj_tmbd.py +3 -17
- StreamingCommunity/Lib/TMBD/tmdb.py +4 -9
- StreamingCommunity/TelegramHelp/telegram_bot.py +50 -50
- StreamingCommunity/Upload/update.py +6 -5
- StreamingCommunity/Upload/version.py +1 -1
- StreamingCommunity/Util/color.py +1 -1
- StreamingCommunity/Util/config_json.py +435 -0
- StreamingCommunity/Util/headers.py +7 -36
- StreamingCommunity/Util/logger.py +72 -42
- StreamingCommunity/Util/message.py +8 -3
- StreamingCommunity/Util/os.py +41 -93
- StreamingCommunity/Util/table.py +8 -17
- StreamingCommunity/run.py +39 -43
- {StreamingCommunity-2.6.1.dist-info → StreamingCommunity-2.8.0.dist-info}/METADATA +203 -114
- StreamingCommunity-2.8.0.dist-info/RECORD +75 -0
- StreamingCommunity/Api/Site/ilcorsaronero/__init__.py +0 -53
- StreamingCommunity/Api/Site/ilcorsaronero/site.py +0 -64
- StreamingCommunity/Api/Site/ilcorsaronero/title.py +0 -42
- StreamingCommunity/Api/Site/ilcorsaronero/util/ilCorsarScraper.py +0 -149
- StreamingCommunity/Api/Template/Util/recall_search.py +0 -37
- StreamingCommunity/Lib/Downloader/HLS/proxyes.py +0 -110
- StreamingCommunity/Util/_jsonConfig.py +0 -241
- StreamingCommunity/Util/call_stack.py +0 -42
- StreamingCommunity/Util/console.py +0 -12
- StreamingCommunity-2.6.1.dist-info/RECORD +0 -83
- {StreamingCommunity-2.6.1.dist-info → StreamingCommunity-2.8.0.dist-info}/LICENSE +0 -0
- {StreamingCommunity-2.6.1.dist-info → StreamingCommunity-2.8.0.dist-info}/WHEEL +0 -0
- {StreamingCommunity-2.6.1.dist-info → StreamingCommunity-2.8.0.dist-info}/entry_points.txt +0 -0
- {StreamingCommunity-2.6.1.dist-info → StreamingCommunity-2.8.0.dist-info}/top_level.txt +0 -0
|
@@ -17,14 +17,13 @@ from typing import Dict
|
|
|
17
17
|
# External libraries
|
|
18
18
|
import httpx
|
|
19
19
|
from tqdm import tqdm
|
|
20
|
+
from rich.console import Console
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
# Internal utilities
|
|
23
24
|
from StreamingCommunity.Util.color import Colors
|
|
24
|
-
from StreamingCommunity.Util.
|
|
25
|
-
from StreamingCommunity.Util.
|
|
26
|
-
from StreamingCommunity.Util._jsonConfig import config_manager
|
|
27
|
-
from StreamingCommunity.Util.os import os_manager
|
|
25
|
+
from StreamingCommunity.Util.headers import get_userAgent
|
|
26
|
+
from StreamingCommunity.Util.config_json import config_manager, get_use_large_bar
|
|
28
27
|
|
|
29
28
|
|
|
30
29
|
# Logic class
|
|
@@ -34,16 +33,11 @@ from ...M3U8 import (
|
|
|
34
33
|
M3U8_Parser,
|
|
35
34
|
M3U8_UrlFix
|
|
36
35
|
)
|
|
37
|
-
from .proxyes import main_test_proxy
|
|
38
36
|
|
|
39
37
|
# Config
|
|
40
38
|
TQDM_DELAY_WORKER = config_manager.get_float('M3U8_DOWNLOAD', 'tqdm_delay')
|
|
41
|
-
USE_LARGE_BAR = not ("android" in sys.platform or "ios" in sys.platform)
|
|
42
39
|
REQUEST_MAX_RETRY = config_manager.get_int('REQUESTS', 'max_retry')
|
|
43
|
-
REQUEST_VERIFY =
|
|
44
|
-
THERE_IS_PROXY_LIST = os_manager.check_file("list_proxy.txt")
|
|
45
|
-
PROXY_START_MIN = config_manager.get_float('REQUESTS', 'proxy_start_min')
|
|
46
|
-
PROXY_START_MAX = config_manager.get_float('REQUESTS', 'proxy_start_max')
|
|
40
|
+
REQUEST_VERIFY = config_manager.get_int('REQUESTS', 'verify')
|
|
47
41
|
DEFAULT_VIDEO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_video_workser')
|
|
48
42
|
DEFAULT_AUDIO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_audio_workser')
|
|
49
43
|
MAX_TIMEOOUT = config_manager.get_int("REQUESTS", "timeout")
|
|
@@ -52,6 +46,9 @@ SEGMENT_MAX_TIMEOUT = config_manager.get_int("M3U8_DOWNLOAD", "segment_timeout")
|
|
|
52
46
|
TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
|
|
53
47
|
|
|
54
48
|
|
|
49
|
+
# Variable
|
|
50
|
+
console = Console()
|
|
51
|
+
|
|
55
52
|
|
|
56
53
|
class M3U8_Segments:
|
|
57
54
|
def __init__(self, url: str, tmp_folder: str, is_index_url: bool = True):
|
|
@@ -102,7 +99,7 @@ class M3U8_Segments:
|
|
|
102
99
|
self.key_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}/"
|
|
103
100
|
|
|
104
101
|
try:
|
|
105
|
-
client_params = {'headers': {'User-Agent':
|
|
102
|
+
client_params = {'headers': {'User-Agent': get_userAgent()}, 'timeout': MAX_TIMEOOUT}
|
|
106
103
|
response = httpx.get(url=key_uri, **client_params)
|
|
107
104
|
response.raise_for_status()
|
|
108
105
|
|
|
@@ -133,19 +130,10 @@ class M3U8_Segments:
|
|
|
133
130
|
]
|
|
134
131
|
self.class_ts_estimator.total_segments = len(self.segments)
|
|
135
132
|
|
|
136
|
-
# Proxy
|
|
137
|
-
if THERE_IS_PROXY_LIST:
|
|
138
|
-
console.log("[red]Start validation proxy.")
|
|
139
|
-
self.valid_proxy = main_test_proxy(self.segments[0])
|
|
140
|
-
console.log(f"[cyan]N. Valid ip: [red]{len(self.valid_proxy)}")
|
|
141
|
-
|
|
142
|
-
if len(self.valid_proxy) == 0:
|
|
143
|
-
sys.exit(0)
|
|
144
|
-
|
|
145
133
|
def get_info(self) -> None:
|
|
146
134
|
if self.is_index_url:
|
|
147
135
|
try:
|
|
148
|
-
client_params = {'headers': {'User-Agent':
|
|
136
|
+
client_params = {'headers': {'User-Agent': get_userAgent()}, 'timeout': MAX_TIMEOOUT}
|
|
149
137
|
response = httpx.get(self.url, **client_params)
|
|
150
138
|
response.raise_for_status()
|
|
151
139
|
|
|
@@ -184,18 +172,13 @@ class M3U8_Segments:
|
|
|
184
172
|
else:
|
|
185
173
|
print("Signal handler must be set in the main thread")
|
|
186
174
|
|
|
187
|
-
def _get_http_client(self
|
|
175
|
+
def _get_http_client(self):
|
|
188
176
|
client_params = {
|
|
189
|
-
|
|
190
|
-
'headers': {'User-Agent': get_headers()},
|
|
177
|
+
'headers': {'User-Agent': get_userAgent()},
|
|
191
178
|
'timeout': SEGMENT_MAX_TIMEOUT,
|
|
192
179
|
'follow_redirects': True,
|
|
193
180
|
'http2': False
|
|
194
181
|
}
|
|
195
|
-
|
|
196
|
-
if THERE_IS_PROXY_LIST and index is not None and hasattr(self, 'valid_proxy'):
|
|
197
|
-
client_params['proxies'] = self.valid_proxy[index % len(self.valid_proxy)]
|
|
198
|
-
|
|
199
182
|
return httpx.Client(**client_params)
|
|
200
183
|
|
|
201
184
|
def download_segment(self, ts_url: str, index: int, progress_bar: tqdm, backoff_factor: float = 1.1) -> None:
|
|
@@ -213,7 +196,7 @@ class M3U8_Segments:
|
|
|
213
196
|
return
|
|
214
197
|
|
|
215
198
|
try:
|
|
216
|
-
with self._get_http_client(
|
|
199
|
+
with self._get_http_client() as client:
|
|
217
200
|
start_time = time.time()
|
|
218
201
|
response = client.get(ts_url)
|
|
219
202
|
|
|
@@ -350,7 +333,6 @@ class M3U8_Segments:
|
|
|
350
333
|
|
|
351
334
|
# Configure workers and delay
|
|
352
335
|
max_workers = self._get_worker_count(type)
|
|
353
|
-
delay = max(PROXY_START_MIN, min(PROXY_START_MAX, 1 / (len(self.valid_proxy) + 1))) if THERE_IS_PROXY_LIST else TQDM_DELAY_WORKER
|
|
354
336
|
|
|
355
337
|
# Download segments with completion verification
|
|
356
338
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
@@ -361,7 +343,7 @@ class M3U8_Segments:
|
|
|
361
343
|
if self.interrupt_flag.is_set():
|
|
362
344
|
break
|
|
363
345
|
|
|
364
|
-
time.sleep(
|
|
346
|
+
time.sleep(TQDM_DELAY_WORKER)
|
|
365
347
|
futures.append(executor.submit(self.download_segment, segment_url, index, progress_bar))
|
|
366
348
|
|
|
367
349
|
# Wait for futures with interrupt handling
|
|
@@ -405,7 +387,7 @@ class M3U8_Segments:
|
|
|
405
387
|
"""
|
|
406
388
|
Generate platform-appropriate progress bar format.
|
|
407
389
|
"""
|
|
408
|
-
if not
|
|
390
|
+
if not get_use_large_bar():
|
|
409
391
|
return (
|
|
410
392
|
f"{Colors.YELLOW}Proc{Colors.WHITE}: "
|
|
411
393
|
f"{Colors.RED}{{percentage:.2f}}% "
|
|
@@ -429,8 +411,6 @@ class M3U8_Segments:
|
|
|
429
411
|
'audio': DEFAULT_AUDIO_WORKERS
|
|
430
412
|
}.get(stream_type.lower(), 1)
|
|
431
413
|
|
|
432
|
-
if THERE_IS_PROXY_LIST:
|
|
433
|
-
return min(len(self.valid_proxy), base_workers * 2)
|
|
434
414
|
return base_workers
|
|
435
415
|
|
|
436
416
|
def _generate_results(self, stream_type: str) -> Dict:
|
|
@@ -12,13 +12,15 @@ from functools import partial
|
|
|
12
12
|
# External libraries
|
|
13
13
|
import httpx
|
|
14
14
|
from tqdm import tqdm
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.prompt import Prompt
|
|
17
|
+
from rich.panel import Panel
|
|
15
18
|
|
|
16
19
|
|
|
17
20
|
# Internal utilities
|
|
18
|
-
from StreamingCommunity.Util.headers import
|
|
21
|
+
from StreamingCommunity.Util.headers import get_userAgent
|
|
19
22
|
from StreamingCommunity.Util.color import Colors
|
|
20
|
-
from StreamingCommunity.Util.
|
|
21
|
-
from StreamingCommunity.Util._jsonConfig import config_manager
|
|
23
|
+
from StreamingCommunity.Util.config_json import config_manager
|
|
22
24
|
from StreamingCommunity.Util.os import internet_manager
|
|
23
25
|
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
|
|
24
26
|
|
|
@@ -27,18 +29,17 @@ from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
|
|
|
27
29
|
from ...FFmpeg import print_duration_table
|
|
28
30
|
|
|
29
31
|
|
|
30
|
-
# Suppress SSL warnings
|
|
31
|
-
import urllib3
|
|
32
|
-
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
33
|
-
|
|
34
|
-
|
|
35
32
|
# Config
|
|
33
|
+
REQUEST_VERIFY = config_manager.get_int('REQUESTS', 'verify')
|
|
36
34
|
GET_ONLY_LINK = config_manager.get_bool('M3U8_PARSER', 'get_only_link')
|
|
37
35
|
REQUEST_TIMEOUT = config_manager.get_float('REQUESTS', 'timeout')
|
|
38
|
-
|
|
39
36
|
TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
|
|
40
37
|
|
|
41
38
|
|
|
39
|
+
# Variable
|
|
40
|
+
msg = Prompt()
|
|
41
|
+
console = Console()
|
|
42
|
+
|
|
42
43
|
|
|
43
44
|
class InterruptHandler:
|
|
44
45
|
def __init__(self):
|
|
@@ -47,6 +48,7 @@ class InterruptHandler:
|
|
|
47
48
|
self.kill_download = False
|
|
48
49
|
self.force_quit = False
|
|
49
50
|
|
|
51
|
+
|
|
50
52
|
def signal_handler(signum, frame, interrupt_handler, original_handler):
|
|
51
53
|
"""Enhanced signal handler for multiple interrupt scenarios"""
|
|
52
54
|
current_time = time.time()
|
|
@@ -67,6 +69,7 @@ def signal_handler(signum, frame, interrupt_handler, original_handler):
|
|
|
67
69
|
console.print("\n[bold red]Force quit activated. Saving partial download...[/bold red]")
|
|
68
70
|
signal.signal(signum, original_handler)
|
|
69
71
|
|
|
72
|
+
|
|
70
73
|
def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = None):
|
|
71
74
|
"""
|
|
72
75
|
Downloads an MP4 video with enhanced interrupt handling.
|
|
@@ -99,7 +102,7 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No
|
|
|
99
102
|
if headers_:
|
|
100
103
|
headers.update(headers_)
|
|
101
104
|
else:
|
|
102
|
-
headers['User-Agent'] =
|
|
105
|
+
headers['User-Agent'] = get_userAgent()
|
|
103
106
|
|
|
104
107
|
except Exception as header_err:
|
|
105
108
|
logging.error(f"Error preparing headers: {header_err}")
|
|
@@ -111,7 +114,7 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No
|
|
|
111
114
|
original_handler = signal.signal(signal.SIGINT, partial(signal_handler, interrupt_handler=interrupt_handler, original_handler=signal.getsignal(signal.SIGINT)))
|
|
112
115
|
|
|
113
116
|
try:
|
|
114
|
-
transport = httpx.HTTPTransport(verify=
|
|
117
|
+
transport = httpx.HTTPTransport(verify=REQUEST_VERIFY, http2=True)
|
|
115
118
|
|
|
116
119
|
with httpx.Client(transport=transport, timeout=httpx.Timeout(60)) as client:
|
|
117
120
|
with client.stream("GET", url, headers=headers, timeout=REQUEST_TIMEOUT) as response:
|
|
@@ -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.
|
|
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
|
-
|
|
24
|
+
import qbittorrentapi
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
# Tor config
|
|
25
|
-
HOST =
|
|
26
|
-
PORT =
|
|
27
|
-
USERNAME =
|
|
28
|
-
PASSWORD =
|
|
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
|
-
#
|
|
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
|
|
45
|
-
- username (str): Username for
|
|
46
|
-
- password (str): Password for
|
|
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(
|
|
51
|
-
|
|
52
|
-
|
|
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.
|
|
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
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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"
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
magnet_link (str): Magnet link
|
|
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:
|
|
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("
|
|
115
|
+
raise ValueError("Magnet link hash not found")
|
|
111
116
|
|
|
112
117
|
magnet_hash = magnet_hash_match.group(1).lower()
|
|
113
118
|
|
|
114
|
-
#
|
|
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 "
|
|
121
|
+
torrent_name = name_match.group(1).replace('+', ' ') if name_match else "Name not available"
|
|
117
122
|
|
|
118
|
-
#
|
|
123
|
+
# Save the timestamp before adding the torrent
|
|
119
124
|
before_add_time = time.time()
|
|
120
125
|
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
|
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("
|
|
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
|
-
|
|
142
|
-
console.print("
|
|
143
|
-
console.print(f"[yellow]
|
|
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
|
-
|
|
148
|
-
self.
|
|
149
|
-
self.
|
|
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
|
-
#
|
|
151
|
+
# Wait and verify if the download is possible
|
|
153
152
|
time.sleep(5)
|
|
154
|
-
self.delete_magnet(self.qb.
|
|
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
|
|
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
|
|
166
|
-
if
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
self.
|
|
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
|
-
|
|
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
|
|
204
|
-
total_size = torrent_info
|
|
205
|
-
downloaded_size = torrent_info
|
|
198
|
+
download_speed = torrent_info.dlspeed
|
|
199
|
+
total_size = torrent_info.size
|
|
200
|
+
downloaded_size = torrent_info.downloaded
|
|
206
201
|
|
|
207
|
-
# Format
|
|
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
|
-
|
|
213
|
-
|
|
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
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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
|
-
"""
|
|
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
|
|
255
|
-
|
|
267
|
+
Moves the downloaded files of the most recent torrent to a new location.
|
|
268
|
+
|
|
256
269
|
Parameters:
|
|
257
|
-
- destination (str): Destination
|
|
258
|
-
|
|
270
|
+
- destination (str): Destination folder.
|
|
271
|
+
|
|
259
272
|
Returns:
|
|
260
|
-
- bool: True if
|
|
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: #
|
|
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.
|
|
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:
|
|
@@ -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"
|
|
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:
|
|
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()
|
|
167
|
-
output_thread.join()
|
|
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}")
|