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.
- StreamingCommunity/Api/Player/ddl.py +2 -2
- StreamingCommunity/Api/Player/maxstream.py +7 -13
- StreamingCommunity/Api/Player/supervideo.py +7 -33
- StreamingCommunity/Api/Player/vixcloud.py +8 -80
- StreamingCommunity/Api/Site/1337xx/__init__.py +8 -1
- StreamingCommunity/Api/Site/1337xx/site.py +10 -16
- StreamingCommunity/Api/Site/1337xx/title.py +4 -1
- StreamingCommunity/Api/Site/animeunity/__init__.py +9 -2
- StreamingCommunity/Api/Site/animeunity/film_serie.py +7 -1
- StreamingCommunity/Api/Site/animeunity/site.py +8 -10
- StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py +1 -1
- StreamingCommunity/Api/Site/cb01new/__init__.py +8 -1
- StreamingCommunity/Api/Site/cb01new/film.py +10 -6
- StreamingCommunity/Api/Site/cb01new/site.py +16 -15
- StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +9 -2
- StreamingCommunity/Api/Site/ddlstreamitaly/series.py +7 -1
- StreamingCommunity/Api/Site/ddlstreamitaly/site.py +10 -15
- StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py +1 -1
- StreamingCommunity/Api/Site/guardaserie/__init__.py +9 -2
- StreamingCommunity/Api/Site/guardaserie/series.py +13 -8
- StreamingCommunity/Api/Site/guardaserie/site.py +12 -17
- StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +1 -1
- StreamingCommunity/Api/Site/mostraguarda/__init__.py +6 -2
- StreamingCommunity/Api/Site/mostraguarda/film.py +10 -8
- StreamingCommunity/Api/Site/streamingcommunity/__init__.py +9 -2
- StreamingCommunity/Api/Site/streamingcommunity/film.py +11 -6
- StreamingCommunity/Api/Site/streamingcommunity/series.py +17 -10
- StreamingCommunity/Api/Site/streamingcommunity/site.py +10 -15
- StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +2 -2
- StreamingCommunity/Api/Template/Util/__init__.py +0 -1
- StreamingCommunity/Api/Template/Util/get_domain.py +24 -66
- StreamingCommunity/Api/Template/Util/manage_ep.py +10 -5
- StreamingCommunity/Api/Template/config_loader.py +8 -8
- StreamingCommunity/Api/Template/site.py +3 -6
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +15 -14
- StreamingCommunity/Lib/Downloader/HLS/segments.py +11 -31
- StreamingCommunity/Lib/Downloader/MP4/downloader.py +12 -9
- 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 +25 -27
- 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 +3 -2
- StreamingCommunity/Upload/version.py +1 -1
- StreamingCommunity/Util/color.py +1 -1
- StreamingCommunity/Util/{_jsonConfig.py → config_json.py} +148 -54
- StreamingCommunity/Util/headers.py +2 -38
- 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 +26 -34
- {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.9.0.dist-info}/METADATA +165 -92
- StreamingCommunity-2.9.0.dist-info/RECORD +75 -0
- StreamingCommunity/Api/Template/Util/recall_search.py +0 -37
- StreamingCommunity/Lib/Downloader/HLS/proxyes.py +0 -110
- StreamingCommunity/Util/call_stack.py +0 -42
- StreamingCommunity/Util/console.py +0 -12
- StreamingCommunity-2.7.0.dist-info/RECORD +0 -79
- {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.9.0.dist-info}/LICENSE +0 -0
- {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.9.0.dist-info}/WHEEL +0 -0
- {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.9.0.dist-info}/entry_points.txt +0 -0
- {StreamingCommunity-2.7.0.dist-info → StreamingCommunity-2.9.0.dist-info}/top_level.txt +0 -0
|
@@ -3,163 +3,89 @@
|
|
|
3
3
|
import sys
|
|
4
4
|
import time
|
|
5
5
|
import logging
|
|
6
|
-
import subprocess
|
|
7
6
|
import importlib.util
|
|
8
7
|
|
|
9
8
|
|
|
10
|
-
#
|
|
11
|
-
from
|
|
9
|
+
# External library
|
|
10
|
+
from rich.console import Console
|
|
12
11
|
|
|
13
12
|
|
|
14
|
-
# Check if
|
|
13
|
+
# Check if Cryptodome module is installed
|
|
14
|
+
console = Console()
|
|
15
15
|
crypto_spec = importlib.util.find_spec("Cryptodome")
|
|
16
16
|
crypto_installed = crypto_spec is not None
|
|
17
17
|
|
|
18
|
+
if not crypto_installed:
|
|
19
|
+
console.log("[red]pycryptodomex non è installato. Per favore installalo. Leggi readme.md [Requirement].")
|
|
20
|
+
sys.exit(0)
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
from Cryptodome.Util.Padding import unpad
|
|
22
|
+
logging.info("[cyan]Decrypy use: Cryptodomex")
|
|
23
|
+
from Cryptodome.Cipher import AES
|
|
24
|
+
from Cryptodome.Util.Padding import unpad
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class M3U8_Decryption:
|
|
29
|
+
"""
|
|
30
|
+
Class for decrypting M3U8 playlist content using AES with pycryptodomex.
|
|
31
|
+
"""
|
|
32
|
+
def __init__(self, key: bytes, iv: bytes, method: str) -> None:
|
|
25
33
|
"""
|
|
26
|
-
|
|
34
|
+
Initialize the M3U8_Decryption object.
|
|
35
|
+
|
|
36
|
+
Parameters:
|
|
37
|
+
key (bytes): The encryption key.
|
|
38
|
+
iv (bytes): The initialization vector (IV).
|
|
39
|
+
method (str): The encryption method.
|
|
27
40
|
"""
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
self.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
""
|
|
63
|
-
|
|
64
|
-
#logging.info(f"Ciphertext: {ciphertext}")
|
|
65
|
-
|
|
66
|
-
# Decrypt based on encryption method
|
|
67
|
-
if self.method in {"AES", "AES-128"}:
|
|
68
|
-
decrypted_data = self.cipher.decrypt(ciphertext)
|
|
69
|
-
decrypted_content = unpad(decrypted_data, AES.block_size)
|
|
70
|
-
|
|
71
|
-
elif self.method == "AES-128-CTR":
|
|
72
|
-
decrypted_content = self.cipher.decrypt(ciphertext)
|
|
73
|
-
else:
|
|
74
|
-
raise ValueError("Invalid or unsupported method")
|
|
75
|
-
|
|
76
|
-
end = time.perf_counter_ns()
|
|
77
|
-
|
|
78
|
-
# Calculate elapsed time with high precision
|
|
79
|
-
elapsed_nanoseconds = end - start
|
|
80
|
-
elapsed_milliseconds = elapsed_nanoseconds / 1_000_000
|
|
81
|
-
elapsed_seconds = elapsed_nanoseconds / 1_000_000_000
|
|
82
|
-
|
|
83
|
-
# Print performance metrics
|
|
84
|
-
logging.info(f"[Crypto Decryption Performance]")
|
|
85
|
-
logging.info(f"Method: {self.method}")
|
|
86
|
-
logging.info(f"Decryption Time: {elapsed_milliseconds:.4f} ms ({elapsed_seconds:.6f} s)")
|
|
87
|
-
logging.info(f"Decrypted Content Length: {len(decrypted_content)} bytes")
|
|
88
|
-
|
|
89
|
-
return decrypted_content
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
else:
|
|
93
|
-
|
|
94
|
-
# Check if openssl command is available
|
|
95
|
-
try:
|
|
96
|
-
openssl_available = subprocess.run(["openssl", "version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0
|
|
97
|
-
logging.info("[cyan]Decrypy use: OPENSSL")
|
|
98
|
-
except:
|
|
99
|
-
openssl_available = False
|
|
100
|
-
|
|
101
|
-
if not openssl_available:
|
|
102
|
-
console.log("[red]Neither python library: pycryptodomex nor openssl software is installed. Please install either one of them. Read readme.md [Requirement].")
|
|
103
|
-
sys.exit(0)
|
|
104
|
-
|
|
105
|
-
class M3U8_Decryption:
|
|
41
|
+
self.key = key
|
|
42
|
+
self.iv = iv
|
|
43
|
+
if "0x" in str(iv):
|
|
44
|
+
self.iv = bytes.fromhex(iv.replace("0x", ""))
|
|
45
|
+
self.method = method
|
|
46
|
+
|
|
47
|
+
# Pre-create the cipher based on the encryption method
|
|
48
|
+
if self.method == "AES":
|
|
49
|
+
self.cipher = AES.new(self.key, AES.MODE_ECB)
|
|
50
|
+
elif self.method == "AES-128":
|
|
51
|
+
self.cipher = AES.new(self.key[:16], AES.MODE_CBC, iv=self.iv)
|
|
52
|
+
elif self.method == "AES-128-CTR":
|
|
53
|
+
self.cipher = AES.new(self.key[:16], AES.MODE_CTR, nonce=self.iv)
|
|
54
|
+
else:
|
|
55
|
+
raise ValueError("Invalid or unsupported method")
|
|
56
|
+
|
|
57
|
+
def decrypt(self, ciphertext: bytes) -> bytes:
|
|
58
|
+
"""
|
|
59
|
+
Decrypt the ciphertext using the specified encryption method.
|
|
60
|
+
|
|
61
|
+
Parameters:
|
|
62
|
+
ciphertext (bytes): The encrypted content to decrypt.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
bytes: The decrypted content.
|
|
66
|
+
"""
|
|
67
|
+
#start = time.perf_counter_ns()
|
|
68
|
+
|
|
69
|
+
if self.method in {"AES", "AES-128"}:
|
|
70
|
+
decrypted_data = self.cipher.decrypt(ciphertext)
|
|
71
|
+
decrypted_content = unpad(decrypted_data, AES.block_size)
|
|
72
|
+
elif self.method == "AES-128-CTR":
|
|
73
|
+
decrypted_content = self.cipher.decrypt(ciphertext)
|
|
74
|
+
else:
|
|
75
|
+
raise ValueError("Invalid or unsupported method")
|
|
76
|
+
|
|
106
77
|
"""
|
|
107
|
-
|
|
78
|
+
end = time.perf_counter_ns()
|
|
79
|
+
|
|
80
|
+
# Calculate the elapsed time with high precision
|
|
81
|
+
elapsed_nanoseconds = end - start
|
|
82
|
+
elapsed_milliseconds = elapsed_nanoseconds / 1_000_000
|
|
83
|
+
elapsed_seconds = elapsed_nanoseconds / 1_000_000_000
|
|
84
|
+
|
|
85
|
+
# Log performance metrics
|
|
86
|
+
logging.info("[Crypto Decryption Performance]")
|
|
87
|
+
logging.info(f"Method: {self.method}")
|
|
88
|
+
logging.info(f"Decryption Time: {elapsed_milliseconds:.4f} ms ({elapsed_seconds:.6f} s)")
|
|
89
|
+
logging.info(f"Decrypted Content Length: {len(decrypted_content)} bytes")
|
|
108
90
|
"""
|
|
109
|
-
|
|
110
|
-
"""
|
|
111
|
-
Initialize the M3U8_Decryption object.
|
|
112
|
-
|
|
113
|
-
Parameters:
|
|
114
|
-
- key (bytes): The encryption key.
|
|
115
|
-
- iv (bytes): The initialization vector (IV).
|
|
116
|
-
- method (str): The encryption method.
|
|
117
|
-
"""
|
|
118
|
-
self.key = key
|
|
119
|
-
self.iv = iv
|
|
120
|
-
if "0x" in str(iv):
|
|
121
|
-
self.iv = bytes.fromhex(iv.replace("0x", ""))
|
|
122
|
-
self.method = method
|
|
123
|
-
logging.info(f"Decrypt add: ('key': {self.key}, 'iv': {self.iv}, 'method': {self.method})")
|
|
124
|
-
|
|
125
|
-
def decrypt(self, ciphertext: bytes) -> bytes:
|
|
126
|
-
"""
|
|
127
|
-
Decrypt the ciphertext using the specified encryption method.
|
|
128
|
-
|
|
129
|
-
Parameters:
|
|
130
|
-
- ciphertext (bytes): The encrypted content to decrypt.
|
|
131
|
-
|
|
132
|
-
Returns:
|
|
133
|
-
bytes: The decrypted content.
|
|
134
|
-
"""
|
|
135
|
-
start = time.perf_counter_ns()
|
|
136
|
-
|
|
137
|
-
# Construct OpenSSL command based on encryption method
|
|
138
|
-
if self.method == "AES":
|
|
139
|
-
openssl_cmd = f'openssl enc -d -aes-256-ecb -K {self.key.hex()} -nosalt'
|
|
140
|
-
elif self.method == "AES-128":
|
|
141
|
-
openssl_cmd = f'openssl enc -d -aes-128-cbc -K {self.key[:16].hex()} -iv {self.iv.hex()}'
|
|
142
|
-
elif self.method == "AES-128-CTR":
|
|
143
|
-
openssl_cmd = f'openssl enc -d -aes-128-ctr -K {self.key[:16].hex()} -iv {self.iv.hex()}'
|
|
144
|
-
else:
|
|
145
|
-
raise ValueError("Invalid or unsupported method")
|
|
146
|
-
|
|
147
|
-
try:
|
|
148
|
-
decrypted_content = subprocess.check_output(openssl_cmd.split(), input=ciphertext, stderr=subprocess.STDOUT)
|
|
149
|
-
except subprocess.CalledProcessError as e:
|
|
150
|
-
raise ValueError(f"Decryption failed: {e.output.decode()}")
|
|
151
|
-
|
|
152
|
-
end = time.perf_counter_ns()
|
|
153
|
-
|
|
154
|
-
# Calculate elapsed time with high precision
|
|
155
|
-
elapsed_nanoseconds = end - start
|
|
156
|
-
elapsed_milliseconds = elapsed_nanoseconds / 1_000_000
|
|
157
|
-
elapsed_seconds = elapsed_nanoseconds / 1_000_000_000
|
|
158
|
-
|
|
159
|
-
# Print performance metrics
|
|
160
|
-
logging.info(f"[OpenSSL Decryption Performance]")
|
|
161
|
-
logging.info(f"Method: {self.method}")
|
|
162
|
-
logging.info(f"Decryption Time: {elapsed_milliseconds:.4f} ms ({elapsed_seconds:.6f} s)")
|
|
163
|
-
logging.info(f"Decrypted Content Length: {len(decrypted_content)} bytes")
|
|
164
|
-
|
|
165
|
-
return decrypted_content
|
|
91
|
+
return decrypted_content
|
|
@@ -14,13 +14,10 @@ from tqdm import tqdm
|
|
|
14
14
|
|
|
15
15
|
# Internal utilities
|
|
16
16
|
from StreamingCommunity.Util.color import Colors
|
|
17
|
+
from StreamingCommunity.Util.config_json import get_use_large_bar
|
|
17
18
|
from StreamingCommunity.Util.os import internet_manager
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
# Variable
|
|
21
|
-
USE_LARGE_BAR = not ("android" in sys.platform or "ios" in sys.platform)
|
|
22
|
-
|
|
23
|
-
|
|
24
21
|
class M3U8_Ts_Estimator:
|
|
25
22
|
def __init__(self, total_segments: int, segments_instance=None):
|
|
26
23
|
"""
|
|
@@ -36,7 +33,7 @@ class M3U8_Ts_Estimator:
|
|
|
36
33
|
self.lock = threading.Lock()
|
|
37
34
|
self.speed = {"upload": "N/A", "download": "N/A"}
|
|
38
35
|
|
|
39
|
-
if
|
|
36
|
+
if get_use_large_bar():
|
|
40
37
|
logging.debug("USE_LARGE_BAR is True, starting speed capture thread")
|
|
41
38
|
self.speed_thread = threading.Thread(target=self.capture_speed)
|
|
42
39
|
self.speed_thread.daemon = True
|
|
@@ -106,15 +103,11 @@ class M3U8_Ts_Estimator:
|
|
|
106
103
|
try:
|
|
107
104
|
self.add_ts_file(total_downloaded * self.total_segments, total_downloaded, duration)
|
|
108
105
|
|
|
109
|
-
#downloaded_file_size_str = internet_manager.format_file_size(self.now_downloaded_size)
|
|
110
106
|
file_total_size = self.calculate_total_size()
|
|
111
|
-
|
|
112
|
-
#number_file_downloaded = downloaded_file_size_str.split(' ')[0]
|
|
113
107
|
number_file_total_size = file_total_size.split(' ')[0]
|
|
114
|
-
#units_file_downloaded = downloaded_file_size_str.split(' ')[1]
|
|
115
108
|
units_file_total_size = file_total_size.split(' ')[1]
|
|
116
109
|
|
|
117
|
-
if
|
|
110
|
+
if get_use_large_bar():
|
|
118
111
|
speed_data = self.speed['download'].split(" ")
|
|
119
112
|
|
|
120
113
|
if len(speed_data) >= 2:
|
|
@@ -126,17 +119,16 @@ class M3U8_Ts_Estimator:
|
|
|
126
119
|
|
|
127
120
|
retry_count = self.segments_instance.active_retries if self.segments_instance else 0
|
|
128
121
|
progress_str = (
|
|
129
|
-
|
|
130
|
-
f"{Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size}"
|
|
122
|
+
f"{Colors.GREEN}{number_file_total_size} {Colors.WHITE}< {Colors.RED}{units_file_total_size}"
|
|
131
123
|
f"{Colors.WHITE} {Colors.CYAN}{average_internet_speed} {Colors.RED}{average_internet_unit}"
|
|
132
|
-
f"{Colors.WHITE} {Colors.GREEN}CRR {Colors.RED}{retry_count} "
|
|
124
|
+
f"{Colors.WHITE}, {Colors.GREEN}CRR {Colors.RED}{retry_count} "
|
|
133
125
|
)
|
|
126
|
+
|
|
134
127
|
else:
|
|
135
128
|
retry_count = self.segments_instance.active_retries if self.segments_instance else 0
|
|
136
129
|
progress_str = (
|
|
137
|
-
|
|
138
|
-
f"{Colors.
|
|
139
|
-
f"{Colors.WHITE} {Colors.GREEN}CRR {Colors.RED}{retry_count} "
|
|
130
|
+
f"{Colors.GREEN}{number_file_total_size} {Colors.WHITE}< {Colors.RED}{units_file_total_size}"
|
|
131
|
+
f"{Colors.WHITE}, {Colors.GREEN}CRR {Colors.RED}{retry_count} "
|
|
140
132
|
)
|
|
141
133
|
|
|
142
134
|
progress_counter.set_postfix_str(progress_str)
|
|
@@ -9,10 +9,6 @@ from m3u8 import loads
|
|
|
9
9
|
from StreamingCommunity.Util.os import internet_manager
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
# External libraries
|
|
13
|
-
import httpx
|
|
14
|
-
|
|
15
|
-
|
|
16
12
|
# Costant
|
|
17
13
|
CODEC_MAPPINGS = {
|
|
18
14
|
"video": {
|
|
@@ -79,7 +75,6 @@ class M3U8_Codec:
|
|
|
79
75
|
Extracted codecs are set as attributes: audio_codec and video_codec.
|
|
80
76
|
"""
|
|
81
77
|
try:
|
|
82
|
-
# Split the codecs string by comma
|
|
83
78
|
codecs_list = self.codecs.split(',')
|
|
84
79
|
except Exception as e:
|
|
85
80
|
logging.error(f"Can't split codec list: {self.codecs} with error {e}")
|
|
@@ -407,10 +402,8 @@ class M3U8_Parser:
|
|
|
407
402
|
Parameters:
|
|
408
403
|
- m3u8_content (str): The content of the M3U8 file.
|
|
409
404
|
"""
|
|
410
|
-
|
|
411
|
-
# Get obj of the m3u8 text content download, dictionary with video, audio, segments, subtitles
|
|
412
405
|
m3u8_obj = loads(raw_content, uri)
|
|
413
|
-
|
|
406
|
+
|
|
414
407
|
self.__parse_video_info__(m3u8_obj)
|
|
415
408
|
self.__parse_subtitles_and_audio__(m3u8_obj)
|
|
416
409
|
self.__parse_segments__(m3u8_obj)
|
|
@@ -469,7 +462,6 @@ class M3U8_Parser:
|
|
|
469
462
|
Parameters:
|
|
470
463
|
- m3u8_obj: The M3U8 object containing video playlists.
|
|
471
464
|
"""
|
|
472
|
-
|
|
473
465
|
try:
|
|
474
466
|
for playlist in m3u8_obj.playlists:
|
|
475
467
|
|
|
@@ -511,25 +503,35 @@ class M3U8_Parser:
|
|
|
511
503
|
except Exception as e:
|
|
512
504
|
logging.error(f"Error parsing video info: {e}")
|
|
513
505
|
|
|
514
|
-
def __parse_encryption_keys__(self,
|
|
506
|
+
def __parse_encryption_keys__(self, obj) -> None:
|
|
515
507
|
"""
|
|
516
|
-
Extracts encryption keys from the M3U8 object.
|
|
508
|
+
Extracts encryption keys either from the M3U8 object or from individual segments.
|
|
517
509
|
|
|
518
510
|
Parameters:
|
|
519
|
-
-
|
|
511
|
+
- obj: Either the main M3U8 object or an individual segment.
|
|
520
512
|
"""
|
|
521
513
|
try:
|
|
522
|
-
if
|
|
514
|
+
if hasattr(obj, 'key') and obj.key is not None:
|
|
515
|
+
key_info = {
|
|
516
|
+
'method': obj.key.method,
|
|
517
|
+
'iv': obj.key.iv,
|
|
518
|
+
'uri': obj.key.uri
|
|
519
|
+
}
|
|
520
|
+
|
|
523
521
|
if self.keys is None:
|
|
524
|
-
self.keys =
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
522
|
+
self.keys = key_info
|
|
523
|
+
|
|
524
|
+
"""
|
|
525
|
+
elif obj.key.uri not in self.keys:
|
|
526
|
+
if isinstance(self.keys, dict):
|
|
527
|
+
self.keys[obj.key.uri] = key_info
|
|
528
|
+
else:
|
|
529
|
+
old_key = self.keys
|
|
530
|
+
self.keys = {'default': old_key, obj.key.uri: key_info}
|
|
531
|
+
"""
|
|
529
532
|
|
|
530
533
|
except Exception as e:
|
|
531
534
|
logging.error(f"Error parsing encryption keys: {e}")
|
|
532
|
-
sys.exit(0)
|
|
533
535
|
pass
|
|
534
536
|
|
|
535
537
|
def __parse_subtitles_and_audio__(self, m3u8_obj) -> None:
|
|
@@ -569,7 +571,6 @@ class M3U8_Parser:
|
|
|
569
571
|
Parameters:
|
|
570
572
|
- m3u8_obj: The M3U8 object containing segment data.
|
|
571
573
|
"""
|
|
572
|
-
|
|
573
574
|
try:
|
|
574
575
|
for segment in m3u8_obj.segments:
|
|
575
576
|
|
|
@@ -583,6 +584,10 @@ class M3U8_Parser:
|
|
|
583
584
|
self.segments.append(segment.uri)
|
|
584
585
|
else:
|
|
585
586
|
self.subtitle.append(segment.uri)
|
|
587
|
+
|
|
588
|
+
# Second check if there is key in main m3u8 obj
|
|
589
|
+
if self.keys is None:
|
|
590
|
+
self.__parse_encryption_keys__(m3u8_obj)
|
|
586
591
|
|
|
587
592
|
except Exception as e:
|
|
588
593
|
logging.error(f"Error parsing segments: {e}")
|
|
@@ -606,13 +611,6 @@ class M3U8_Parser:
|
|
|
606
611
|
Returns:
|
|
607
612
|
- formatted_duration (str): Formatted duration string with hours, minutes, and seconds if return_string is True.
|
|
608
613
|
- duration_dict (dict): Dictionary with keys 'h', 'm', 's' representing hours, minutes, and seconds respectively if return_string is False.
|
|
609
|
-
|
|
610
|
-
Example usage:
|
|
611
|
-
>>> obj = YourClass(duration=3661)
|
|
612
|
-
>>> obj.get_duration()
|
|
613
|
-
'[yellow]1[red]h [yellow]1[red]m [yellow]1[red]s'
|
|
614
|
-
>>> obj.get_duration(return_string=False)
|
|
615
|
-
{'h': 1, 'm': 1, 's': 1}
|
|
616
614
|
"""
|
|
617
615
|
|
|
618
616
|
# Calculate hours, minutes, and remaining seconds
|
|
@@ -33,8 +33,6 @@ class M3U8_UrlFix:
|
|
|
33
33
|
Returns:
|
|
34
34
|
str: The full URL for the specified resource.
|
|
35
35
|
"""
|
|
36
|
-
|
|
37
|
-
# Check if m3u8 url playlist is present
|
|
38
36
|
if self.url_playlist == None:
|
|
39
37
|
logging.error("[M3U8_UrlFix] Cant generate full url, playlist not present")
|
|
40
38
|
raise
|
|
@@ -54,5 +52,4 @@ class M3U8_UrlFix:
|
|
|
54
52
|
"""
|
|
55
53
|
Reset the M3U8 playlist URL to its default state (None).
|
|
56
54
|
"""
|
|
57
|
-
self.url_playlist = None
|
|
58
|
-
|
|
55
|
+
self.url_playlist = None
|
|
@@ -5,35 +5,21 @@ from typing import Dict
|
|
|
5
5
|
|
|
6
6
|
class Json_film:
|
|
7
7
|
def __init__(self, data: Dict):
|
|
8
|
-
self.adult = data.get('adult', False)
|
|
9
|
-
self.backdrop_path = data.get('backdrop_path')
|
|
10
|
-
self.budget = data.get('budget', 0)
|
|
11
|
-
self.homepage = data.get('homepage')
|
|
12
8
|
self.id = data.get('id', 0)
|
|
13
9
|
self.imdb_id = data.get('imdb_id')
|
|
14
10
|
self.origin_country = data.get('origin_country', [])
|
|
15
11
|
self.original_language = data.get('original_language')
|
|
16
12
|
self.original_title = data.get('original_title')
|
|
17
|
-
self.overview = data.get('overview')
|
|
18
13
|
self.popularity = data.get('popularity', 0.0)
|
|
19
14
|
self.poster_path = data.get('poster_path')
|
|
20
15
|
self.release_date = data.get('release_date')
|
|
21
|
-
self.revenue = data.get('revenue', 0)
|
|
22
|
-
self.runtime = data.get('runtime', 0)
|
|
23
16
|
self.status = data.get('status')
|
|
24
|
-
self.tagline = data.get('tagline')
|
|
25
17
|
self.title = data.get('title')
|
|
26
|
-
self.video = data.get('video', False)
|
|
27
18
|
self.vote_average = data.get('vote_average', 0.0)
|
|
28
19
|
self.vote_count = data.get('vote_count', 0)
|
|
29
20
|
|
|
30
21
|
def __repr__(self):
|
|
31
|
-
return (f"
|
|
32
|
-
f"budget={self.budget}, "
|
|
33
|
-
f"homepage='{self.homepage}', id={self.id}, "
|
|
34
|
-
f"imdb_id='{self.imdb_id}', origin_country={self.origin_country}, "
|
|
22
|
+
return (f"Json_film(id={self.id}, imdb_id='{self.imdb_id}', origin_country={self.origin_country}, "
|
|
35
23
|
f"original_language='{self.original_language}', original_title='{self.original_title}', "
|
|
36
|
-
f"
|
|
37
|
-
f"
|
|
38
|
-
f"status='{self.status}', tagline='{self.tagline}', "
|
|
39
|
-
f"title='{self.title}', video={self.video}, vote_average={self.vote_average}, vote_count={self.vote_count})")
|
|
24
|
+
f"popularity={self.popularity}, poster_path='{self.poster_path}', release_date='{self.release_date}', "
|
|
25
|
+
f"status='{self.status}', title='{self.title}', vote_average={self.vote_average}, vote_count={self.vote_count})")
|
|
@@ -6,22 +6,22 @@ from typing import Dict
|
|
|
6
6
|
|
|
7
7
|
# External libraries
|
|
8
8
|
import httpx
|
|
9
|
+
from rich.console import Console
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
# Internal utilities
|
|
12
13
|
from .obj_tmbd import Json_film
|
|
13
|
-
from StreamingCommunity.Util.
|
|
14
|
-
from StreamingCommunity.Util._jsonConfig import config_manager
|
|
14
|
+
from StreamingCommunity.Util.config_json import config_manager
|
|
15
15
|
from StreamingCommunity.Util.table import TVShowManager
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
# Variable
|
|
19
|
+
console = Console()
|
|
19
20
|
table_show_manager = TVShowManager()
|
|
20
21
|
api_key = "a800ed6c93274fb857ea61bd9e7256c5"
|
|
21
22
|
MAX_TIMEOUT = config_manager.get_int("REQUESTS", "timeout")
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
|
|
25
25
|
def get_select_title(table_show_manager, generic_obj):
|
|
26
26
|
"""
|
|
27
27
|
Display a selection of titles and prompt the user to choose one.
|
|
@@ -29,11 +29,6 @@ def get_select_title(table_show_manager, generic_obj):
|
|
|
29
29
|
Returns:
|
|
30
30
|
dict: The selected media item.
|
|
31
31
|
"""
|
|
32
|
-
|
|
33
|
-
# Set up table for displaying titles
|
|
34
|
-
table_show_manager.set_slice_end(10)
|
|
35
|
-
|
|
36
|
-
# Check if the generic_obj list is empty
|
|
37
32
|
if not generic_obj:
|
|
38
33
|
console.print("\n[red]No media items available.")
|
|
39
34
|
return None
|
|
@@ -158,7 +153,7 @@ class TheMovieDB:
|
|
|
158
153
|
|
|
159
154
|
# Join with colored arrows and print with proper category label
|
|
160
155
|
console.print(
|
|
161
|
-
f"[bold purple]{category}:[/] {' [red]
|
|
156
|
+
f"[bold purple]{category}:[/] {' [red]->[/] '.join(colored_items)}"
|
|
162
157
|
)
|
|
163
158
|
|
|
164
159
|
def display_trending_tv_shows(self):
|