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
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
# 18.06.24
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import time
|
|
3
|
+
import certifi
|
|
5
4
|
from urllib.parse import urlparse, unquote
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
# External libraries
|
|
9
8
|
import httpx
|
|
9
|
+
from rich.console import Console
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
# Internal utilities
|
|
13
13
|
from StreamingCommunity.Util.headers import get_headers
|
|
14
|
-
from StreamingCommunity.Util.
|
|
15
|
-
from StreamingCommunity.Util._jsonConfig import config_manager
|
|
14
|
+
from StreamingCommunity.Util.config_json import config_manager
|
|
16
15
|
|
|
17
16
|
|
|
17
|
+
# Variable
|
|
18
|
+
console = Console()
|
|
19
|
+
VERIFY = config_manager.get("REQUESTS", "verify")
|
|
20
|
+
MAX_TIMEOUT = config_manager.get_int("REQUESTS", "timeout")
|
|
21
|
+
|
|
18
22
|
|
|
19
23
|
def get_tld(url_str):
|
|
20
24
|
"""Extract the TLD (Top-Level Domain) from the URL."""
|
|
@@ -50,16 +54,7 @@ def get_base_domain(url_str):
|
|
|
50
54
|
except Exception:
|
|
51
55
|
return None
|
|
52
56
|
|
|
53
|
-
def
|
|
54
|
-
"""Extract base URL including protocol and domain, removing path and query parameters."""
|
|
55
|
-
try:
|
|
56
|
-
parsed = urlparse(url_str)
|
|
57
|
-
return f"{parsed.scheme}://{parsed.netloc}"
|
|
58
|
-
|
|
59
|
-
except Exception:
|
|
60
|
-
return None
|
|
61
|
-
|
|
62
|
-
def validate_url(url, base_url, max_timeout, max_retries=2, sleep=1):
|
|
57
|
+
def validate_url(url, base_url):
|
|
63
58
|
"""Validate if URL is accessible and matches expected base domain."""
|
|
64
59
|
console.print(f"\n[cyan]Starting validation for URL[white]: [yellow]{url}")
|
|
65
60
|
|
|
@@ -71,71 +66,34 @@ def validate_url(url, base_url, max_timeout, max_retries=2, sleep=1):
|
|
|
71
66
|
console.print(f"[red]Domain structure mismatch: {url_domain} != {base_domain}")
|
|
72
67
|
return False, None
|
|
73
68
|
|
|
74
|
-
# Count dots to ensure we don't have extra subdomains
|
|
75
|
-
base_dots = base_url.count('.')
|
|
76
|
-
url_dots = url.count('.')
|
|
77
|
-
if url_dots > base_dots + 1:
|
|
78
|
-
console.print(f"[red]Too many subdomains in URL")
|
|
79
|
-
return False, None
|
|
80
|
-
|
|
81
69
|
client = httpx.Client(
|
|
82
|
-
verify=
|
|
70
|
+
verify=VERIFY,
|
|
83
71
|
headers=get_headers(),
|
|
84
|
-
timeout=
|
|
72
|
+
timeout=MAX_TIMEOUT
|
|
85
73
|
)
|
|
86
74
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if response.status_code >= 400:
|
|
98
|
-
console.print(f"[red]Check failed: HTTP {response.status_code}")
|
|
99
|
-
return False, None
|
|
100
|
-
|
|
101
|
-
# Follow redirects and verify final domain
|
|
102
|
-
final_response = client.get(url, follow_redirects=True)
|
|
103
|
-
final_domain = get_base_domain(str(final_response.url))
|
|
104
|
-
console.print(f"[cyan]Redirect url: [red]{final_response.url}")
|
|
105
|
-
|
|
106
|
-
if final_domain != base_domain:
|
|
107
|
-
console.print(f"[red]Final domain mismatch: {final_domain} != {base_domain}")
|
|
108
|
-
return False, None
|
|
109
|
-
|
|
110
|
-
new_tld = get_tld(str(final_response.url))
|
|
111
|
-
if new_tld != get_tld(url):
|
|
112
|
-
return True, new_tld
|
|
113
|
-
|
|
114
|
-
return True, None
|
|
115
|
-
|
|
116
|
-
except (httpx.RequestError, ssl.SSLError) as e:
|
|
117
|
-
console.print(f"[red]Connection error: {str(e)}")
|
|
118
|
-
time.sleep(sleep)
|
|
119
|
-
continue
|
|
120
|
-
|
|
121
|
-
return False, None
|
|
75
|
+
# Make request to web site
|
|
76
|
+
response = client.get(url, follow_redirects=False)
|
|
77
|
+
|
|
78
|
+
if response.status_code >= 400:
|
|
79
|
+
console.print(f"[red]Check failed: HTTP {response.status_code}")
|
|
80
|
+
console.print(f"[red]Response content: {response.text}")
|
|
81
|
+
return False, None
|
|
82
|
+
|
|
83
|
+
return True, base_domain
|
|
122
84
|
|
|
123
85
|
def search_domain(site_name: str, base_url: str, get_first: bool = False):
|
|
124
|
-
"""Search for valid domain matching site name and base URL."""
|
|
125
|
-
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|
126
|
-
|
|
86
|
+
"""Search for valid domain matching site name and base URL."""
|
|
127
87
|
try:
|
|
128
|
-
is_correct, redirect_tld = validate_url(base_url, base_url
|
|
88
|
+
is_correct, redirect_tld = validate_url(base_url, base_url)
|
|
129
89
|
|
|
130
90
|
if is_correct:
|
|
131
91
|
tld = redirect_tld or get_tld(base_url)
|
|
132
|
-
config_manager.configSite[site_name]['domain'] = tld
|
|
133
|
-
|
|
134
|
-
console.print(f"[green]Successfully validated initial URL")
|
|
135
92
|
return tld, base_url
|
|
136
93
|
|
|
137
94
|
else:
|
|
138
95
|
return None, None
|
|
139
96
|
|
|
140
97
|
except Exception as e:
|
|
141
|
-
console.print(f"[red]Error testing initial URL: {str(e)}")
|
|
98
|
+
console.print(f"[red]Error testing initial URL: {str(e)}")
|
|
99
|
+
return None, None
|
|
@@ -5,15 +5,21 @@ import logging
|
|
|
5
5
|
from typing import List
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
# External library
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.prompt import Prompt
|
|
11
|
+
|
|
12
|
+
|
|
8
13
|
# Internal utilities
|
|
9
|
-
from StreamingCommunity.Util.console import console, msg
|
|
10
14
|
from StreamingCommunity.Util.os import os_manager
|
|
11
|
-
from StreamingCommunity.Util.
|
|
15
|
+
from StreamingCommunity.Util.config_json import config_manager
|
|
12
16
|
from StreamingCommunity.Util.table import TVShowManager
|
|
13
17
|
|
|
14
18
|
|
|
15
|
-
#
|
|
16
|
-
|
|
19
|
+
# Variable
|
|
20
|
+
msg = Prompt()
|
|
21
|
+
console = Console()
|
|
22
|
+
MAP_EPISODE = config_manager.get('OUT_FOLDER', 'map_episode_name')
|
|
17
23
|
|
|
18
24
|
|
|
19
25
|
def dynamic_format_number(n: int) -> str:
|
|
@@ -195,7 +201,6 @@ def display_episodes_list(episodes_manager) -> str:
|
|
|
195
201
|
"""
|
|
196
202
|
# Set up table for displaying episodes
|
|
197
203
|
table_show_manager = TVShowManager()
|
|
198
|
-
table_show_manager.set_slice_end(10)
|
|
199
204
|
|
|
200
205
|
# Add columns to the table
|
|
201
206
|
column_info = {
|
|
@@ -5,7 +5,7 @@ import inspect
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
# Internal utilities
|
|
8
|
-
from StreamingCommunity.Util.
|
|
8
|
+
from StreamingCommunity.Util.config_json import config_manager
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def get_site_name_from_stack():
|
|
@@ -29,7 +29,7 @@ class SiteConstant:
|
|
|
29
29
|
|
|
30
30
|
@property
|
|
31
31
|
def ROOT_PATH(self):
|
|
32
|
-
return config_manager.get('
|
|
32
|
+
return config_manager.get('OUT_FOLDER', 'root_path')
|
|
33
33
|
|
|
34
34
|
@property
|
|
35
35
|
def DOMAIN_NOW(self):
|
|
@@ -42,23 +42,23 @@ class SiteConstant:
|
|
|
42
42
|
@property
|
|
43
43
|
def SERIES_FOLDER(self):
|
|
44
44
|
base_path = self.ROOT_PATH
|
|
45
|
-
if config_manager.get_bool("
|
|
45
|
+
if config_manager.get_bool("OUT_FOLDER", "add_siteName"):
|
|
46
46
|
base_path = os.path.join(base_path, self.SITE_NAME)
|
|
47
|
-
return os.path.join(base_path, config_manager.get('
|
|
47
|
+
return os.path.join(base_path, config_manager.get('OUT_FOLDER', 'serie_folder_name'))
|
|
48
48
|
|
|
49
49
|
@property
|
|
50
50
|
def MOVIE_FOLDER(self):
|
|
51
51
|
base_path = self.ROOT_PATH
|
|
52
|
-
if config_manager.get_bool("
|
|
52
|
+
if config_manager.get_bool("OUT_FOLDER", "add_siteName"):
|
|
53
53
|
base_path = os.path.join(base_path, self.SITE_NAME)
|
|
54
|
-
return os.path.join(base_path, config_manager.get('
|
|
54
|
+
return os.path.join(base_path, config_manager.get('OUT_FOLDER', 'movie_folder_name'))
|
|
55
55
|
|
|
56
56
|
@property
|
|
57
57
|
def ANIME_FOLDER(self):
|
|
58
58
|
base_path = self.ROOT_PATH
|
|
59
|
-
if config_manager.get_bool("
|
|
59
|
+
if config_manager.get_bool("OUT_FOLDER", "add_siteName"):
|
|
60
60
|
base_path = os.path.join(base_path, self.SITE_NAME)
|
|
61
|
-
return os.path.join(base_path, config_manager.get('
|
|
61
|
+
return os.path.join(base_path, config_manager.get('OUT_FOLDER', 'anime_folder_name'))
|
|
62
62
|
|
|
63
63
|
@property
|
|
64
64
|
def COOKIE(self):
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
import sys
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
#
|
|
7
|
-
from
|
|
6
|
+
# External library
|
|
7
|
+
from rich.console import Console
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
# Variable
|
|
11
|
+
console = Console()
|
|
11
12
|
available_colors = ['red', 'magenta', 'yellow', 'cyan', 'green', 'blue', 'white']
|
|
12
13
|
column_to_hide = ['Slug', 'Sub_ita', 'Last_air_date', 'Seasons_count', 'Url']
|
|
13
14
|
|
|
@@ -19,10 +20,6 @@ def get_select_title(table_show_manager, media_search_manager):
|
|
|
19
20
|
Returns:
|
|
20
21
|
MediaItem: The selected media item.
|
|
21
22
|
"""
|
|
22
|
-
|
|
23
|
-
# Set up table for displaying titles
|
|
24
|
-
table_show_manager.set_slice_end(10)
|
|
25
|
-
|
|
26
23
|
# Determine column_info dynamically for (search site)
|
|
27
24
|
if not media_search_manager.media_list:
|
|
28
25
|
console.print("\n[red]No media items available.")
|
|
@@ -10,17 +10,14 @@ from typing import Any, Dict, List, Optional
|
|
|
10
10
|
|
|
11
11
|
# External libraries
|
|
12
12
|
import httpx
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.panel import Panel
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
# Internal utilities
|
|
16
|
-
from StreamingCommunity.Util.
|
|
18
|
+
from StreamingCommunity.Util.config_json import config_manager
|
|
17
19
|
from StreamingCommunity.Util.headers import get_userAgent
|
|
18
|
-
from StreamingCommunity.Util.
|
|
19
|
-
from StreamingCommunity.Util.os import (
|
|
20
|
-
compute_sha1_hash,
|
|
21
|
-
os_manager,
|
|
22
|
-
internet_manager
|
|
23
|
-
)
|
|
20
|
+
from StreamingCommunity.Util.os import compute_sha1_hash, os_manager, internet_manager
|
|
24
21
|
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
|
|
25
22
|
|
|
26
23
|
|
|
@@ -47,9 +44,9 @@ FILTER_CUSTOM_REOLUTION = str(config_manager.get('M3U8_PARSER', 'force_resolutio
|
|
|
47
44
|
GET_ONLY_LINK = config_manager.get_bool('M3U8_PARSER', 'get_only_link')
|
|
48
45
|
RETRY_LIMIT = config_manager.get_int('REQUESTS', 'max_retry')
|
|
49
46
|
MAX_TIMEOUT = config_manager.get_int("REQUESTS", "timeout")
|
|
50
|
-
|
|
51
47
|
TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
|
|
52
48
|
|
|
49
|
+
console = Console()
|
|
53
50
|
|
|
54
51
|
|
|
55
52
|
class HLSClient:
|
|
@@ -100,7 +97,7 @@ class PathManager:
|
|
|
100
97
|
Creates a hash-based filename if no path is provided.
|
|
101
98
|
"""
|
|
102
99
|
if not path:
|
|
103
|
-
root = config_manager.get('
|
|
100
|
+
root = config_manager.get('OUT_FOLDER', 'root_path')
|
|
104
101
|
hash_name = compute_sha1_hash(self.m3u8_url) + ".mp4"
|
|
105
102
|
return os.path.join(root, "undefined", hash_name)
|
|
106
103
|
|
|
@@ -193,7 +190,7 @@ class M3U8Manager:
|
|
|
193
190
|
list_available_resolution = [f"{r[0]}x{r[1]}" for r in tuple_available_resolution]
|
|
194
191
|
|
|
195
192
|
console.print(
|
|
196
|
-
f"[cyan bold]Video
|
|
193
|
+
f"[cyan bold]Video [/cyan bold] [green]Available:[/green] [purple]{', '.join(list_available_resolution)}[/purple] | "
|
|
197
194
|
f"[red]Set:[/red] [purple]{FILTER_CUSTOM_REOLUTION}[/purple] | "
|
|
198
195
|
f"[yellow]Downloadable:[/yellow] [purple]{self.video_res[0]}x{self.video_res[1]}[/purple]"
|
|
199
196
|
)
|
|
@@ -208,7 +205,7 @@ class M3U8Manager:
|
|
|
208
205
|
set_codec_info = available_codec_info if config_manager.get_bool("M3U8_CONVERSION", "use_codec") else "[purple]copy[/purple]"
|
|
209
206
|
|
|
210
207
|
console.print(
|
|
211
|
-
f"[bold cyan]Codec
|
|
208
|
+
f"[bold cyan]Codec [/bold cyan] [green]Available:[/green] {available_codec_info} | "
|
|
212
209
|
f"[red]Set:[/red] {set_codec_info}"
|
|
213
210
|
)
|
|
214
211
|
|
|
@@ -217,7 +214,7 @@ class M3U8Manager:
|
|
|
217
214
|
downloadable_sub_languages = list(set(available_sub_languages) & set(DOWNLOAD_SPECIFIC_SUBTITLE))
|
|
218
215
|
if available_sub_languages:
|
|
219
216
|
console.print(
|
|
220
|
-
f"[cyan bold]Subtitle
|
|
217
|
+
f"[cyan bold]Subtitle [/cyan bold] [green]Available:[/green] [purple]{', '.join(available_sub_languages)}[/purple] | "
|
|
221
218
|
f"[red]Set:[/red] [purple]{', '.join(DOWNLOAD_SPECIFIC_SUBTITLE)}[/purple] | "
|
|
222
219
|
f"[yellow]Downloadable:[/yellow] [purple]{', '.join(downloadable_sub_languages)}[/purple]"
|
|
223
220
|
)
|
|
@@ -227,7 +224,7 @@ class M3U8Manager:
|
|
|
227
224
|
downloadable_audio_languages = list(set(available_audio_languages) & set(DOWNLOAD_SPECIFIC_AUDIO))
|
|
228
225
|
if available_audio_languages:
|
|
229
226
|
console.print(
|
|
230
|
-
f"[cyan bold]Audio
|
|
227
|
+
f"[cyan bold]Audio [/cyan bold] [green]Available:[/green] [purple]{', '.join(available_audio_languages)}[/purple] | "
|
|
231
228
|
f"[red]Set:[/red] [purple]{', '.join(DOWNLOAD_SPECIFIC_AUDIO)}[/purple] | "
|
|
232
229
|
f"[yellow]Downloadable:[/yellow] [purple]{', '.join(downloadable_audio_languages)}[/purple]"
|
|
233
230
|
)
|
|
@@ -432,7 +429,8 @@ class HLS_Downloader:
|
|
|
432
429
|
'path': self.path_manager.output_path,
|
|
433
430
|
'url': self.m3u8_url,
|
|
434
431
|
'is_master': False,
|
|
435
|
-
'
|
|
432
|
+
'msg': 'File already exists',
|
|
433
|
+
'error': None,
|
|
436
434
|
'stopped': False
|
|
437
435
|
}
|
|
438
436
|
if TELEGRAM_BOT:
|
|
@@ -476,6 +474,8 @@ class HLS_Downloader:
|
|
|
476
474
|
'path': self.path_manager.output_path,
|
|
477
475
|
'url': self.m3u8_url,
|
|
478
476
|
'is_master': self.m3u8_manager.is_master,
|
|
477
|
+
'msg': None,
|
|
478
|
+
'error': None,
|
|
479
479
|
'stopped': download_stopped
|
|
480
480
|
}
|
|
481
481
|
|
|
@@ -488,6 +488,7 @@ class HLS_Downloader:
|
|
|
488
488
|
'path': None,
|
|
489
489
|
'url': self.m3u8_url,
|
|
490
490
|
'is_master': getattr(self.m3u8_manager, 'is_master', None),
|
|
491
|
+
'msg': None,
|
|
491
492
|
'error': error_msg,
|
|
492
493
|
'stopped': False
|
|
493
494
|
}
|
|
@@ -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):
|
|
@@ -133,15 +130,6 @@ 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:
|
|
@@ -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
|
-
#'headers': random_headers(self.key_base_url) if hasattr(self, 'key_base_url') else {'User-Agent': get_userAgent()},
|
|
190
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
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.
|
|
@@ -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:
|