StreamingCommunity 2.0.0__py3-none-any.whl → 2.0.5__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/Site/1337xx/__init__.py +1 -1
- StreamingCommunity/Api/Site/altadefinizione/__init__.py +1 -1
- StreamingCommunity/Api/Site/animeunity/__init__.py +1 -1
- StreamingCommunity/Api/Site/cb01new/__init__.py +1 -1
- StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +1 -3
- StreamingCommunity/Api/Site/ddlstreamitaly/series.py +1 -1
- StreamingCommunity/Api/Site/guardaserie/__init__.py +1 -3
- StreamingCommunity/Api/Site/guardaserie/series.py +2 -2
- StreamingCommunity/Api/Site/ilcorsaronero/__init__.py +1 -1
- StreamingCommunity/Api/Site/ilcorsaronero/util/ilCorsarScraper.py +14 -6
- StreamingCommunity/Api/Site/mostraguarda/__init__.py +1 -1
- StreamingCommunity/Api/Site/mostraguarda/film.py +1 -3
- StreamingCommunity/Api/Site/streamingcommunity/__init__.py +1 -1
- StreamingCommunity/Api/Site/streamingcommunity/film.py +1 -1
- StreamingCommunity/Api/Site/streamingcommunity/series.py +1 -1
- StreamingCommunity/Api/Template/Util/get_domain.py +12 -11
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +42 -34
- StreamingCommunity/Lib/Downloader/HLS/segments.py +10 -11
- StreamingCommunity/Lib/FFmpeg/capture.py +1 -1
- StreamingCommunity/Lib/FFmpeg/util.py +1 -1
- StreamingCommunity/Lib/M3U8/decryptor.py +2 -2
- StreamingCommunity/Lib/M3U8/estimator.py +53 -25
- StreamingCommunity/Upload/update.py +2 -3
- StreamingCommunity/Upload/version.py +1 -1
- StreamingCommunity/Util/ffmpeg_installer.py +65 -28
- StreamingCommunity/Util/os.py +16 -25
- StreamingCommunity/run.py +0 -1
- {StreamingCommunity-2.0.0.dist-info → StreamingCommunity-2.0.5.dist-info}/METADATA +9 -9
- {StreamingCommunity-2.0.0.dist-info → StreamingCommunity-2.0.5.dist-info}/RECORD +33 -33
- {StreamingCommunity-2.0.0.dist-info → StreamingCommunity-2.0.5.dist-info}/WHEEL +1 -1
- {StreamingCommunity-2.0.0.dist-info → StreamingCommunity-2.0.5.dist-info}/entry_points.txt +1 -0
- {StreamingCommunity-2.0.0.dist-info → StreamingCommunity-2.0.5.dist-info}/LICENSE +0 -0
- {StreamingCommunity-2.0.0.dist-info → StreamingCommunity-2.0.5.dist-info}/top_level.txt +0 -0
|
@@ -27,7 +27,7 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
|
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
if string_to_search is None:
|
|
30
|
-
string_to_search = msg.ask(f"\n[purple]Insert word to search in [
|
|
30
|
+
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
|
|
31
31
|
|
|
32
32
|
# Search on database
|
|
33
33
|
len_database = title_search(quote_plus(string_to_search))
|
|
@@ -27,7 +27,7 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
|
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
if string_to_search is None:
|
|
30
|
-
string_to_search = msg.ask(f"\n[purple]Insert word to search in [
|
|
30
|
+
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
|
|
31
31
|
|
|
32
32
|
# Search on database
|
|
33
33
|
len_database = title_search(quote_plus(string_to_search))
|
|
@@ -24,7 +24,7 @@ from .costant import SITE_NAME
|
|
|
24
24
|
def search(string_to_search: str = None, get_onylDatabase: bool = False):
|
|
25
25
|
|
|
26
26
|
if string_to_search is None:
|
|
27
|
-
string_to_search = msg.ask(f"\n[purple]Insert word to search in [
|
|
27
|
+
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
|
|
28
28
|
|
|
29
29
|
# Search on database
|
|
30
30
|
len_database = title_search(quote_plus(string_to_search))
|
|
@@ -27,7 +27,7 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
|
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
if string_to_search is None:
|
|
30
|
-
string_to_search = msg.ask(f"\n[purple]Insert word to search in [
|
|
30
|
+
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
|
|
31
31
|
|
|
32
32
|
# Search on database
|
|
33
33
|
len_database = title_search(quote_plus(string_to_search))
|
|
@@ -28,9 +28,7 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
|
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
30
|
if string_to_search is None:
|
|
31
|
-
|
|
32
|
-
# Make request to site to get content that corrsisponde to that string
|
|
33
|
-
string_to_search = msg.ask(f"\n[purple]Insert word to search in [red]{SITE_NAME}").strip()
|
|
31
|
+
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
|
|
34
32
|
|
|
35
33
|
# Search on database
|
|
36
34
|
len_database = title_search(quote_plus(string_to_search))
|
|
@@ -25,7 +25,6 @@ from StreamingCommunity.Api.Player.ddl import VideoSource
|
|
|
25
25
|
|
|
26
26
|
# Variable
|
|
27
27
|
from .costant import ROOT_PATH, SERIES_FOLDER
|
|
28
|
-
table_show_manager = TVShowManager()
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
|
|
@@ -120,6 +119,7 @@ def display_episodes_list(obj_episode_manager) -> str:
|
|
|
120
119
|
"""
|
|
121
120
|
|
|
122
121
|
# Set up table for displaying episodes
|
|
122
|
+
table_show_manager = TVShowManager()
|
|
123
123
|
table_show_manager.set_slice_end(10)
|
|
124
124
|
|
|
125
125
|
# Add columns to the table
|
|
@@ -27,9 +27,7 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
|
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
if string_to_search is None:
|
|
30
|
-
|
|
31
|
-
# Make request to site to get content that corrsisponde to that string
|
|
32
|
-
string_to_search = msg.ask(f"\n[purple]Insert word to search in [red]{SITE_NAME}").strip()
|
|
30
|
+
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
|
|
33
31
|
|
|
34
32
|
# Search on database
|
|
35
33
|
len_database = title_search(quote_plus(string_to_search))
|
|
@@ -25,7 +25,6 @@ from StreamingCommunity.Api.Player.supervideo import VideoSource
|
|
|
25
25
|
|
|
26
26
|
# Variable
|
|
27
27
|
from .costant import ROOT_PATH, SERIES_FOLDER
|
|
28
|
-
table_show_manager = TVShowManager()
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
|
|
@@ -171,8 +170,9 @@ def display_episodes_list(obj_episode_manager) -> str:
|
|
|
171
170
|
Returns:
|
|
172
171
|
last_command (str): Last command entered by the user.
|
|
173
172
|
"""
|
|
174
|
-
|
|
173
|
+
|
|
175
174
|
# Set up table for displaying episodes
|
|
175
|
+
table_show_manager = TVShowManager()
|
|
176
176
|
table_show_manager.set_slice_end(10)
|
|
177
177
|
|
|
178
178
|
# Add columns to the table
|
|
@@ -28,7 +28,7 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
|
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
30
|
if string_to_search is None:
|
|
31
|
-
string_to_search = msg.ask(f"\n[purple]Insert word to search in [
|
|
31
|
+
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
|
|
32
32
|
|
|
33
33
|
# Search on database
|
|
34
34
|
len_database = asyncio.run(title_search(quote_plus(string_to_search)))
|
|
@@ -25,13 +25,21 @@ class IlCorsaroNeroScraper:
|
|
|
25
25
|
self.base_url = base_url
|
|
26
26
|
self.max_page = max_page
|
|
27
27
|
self.headers = {
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
28
|
+
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
|
29
|
+
'accept-language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7',
|
|
30
|
+
'cache-control': 'max-age=0',
|
|
31
|
+
'priority': 'u=0, i',
|
|
32
|
+
'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
|
|
33
|
+
'sec-ch-ua-mobile': '?0',
|
|
34
|
+
'sec-ch-ua-platform': '"Windows"',
|
|
35
|
+
'sec-fetch-dest': 'document',
|
|
36
|
+
'sec-fetch-mode': 'navigate',
|
|
37
|
+
'sec-fetch-site': 'same-origin',
|
|
38
|
+
'sec-fetch-user': '?1',
|
|
39
|
+
'upgrade-insecure-requests': '1',
|
|
40
|
+
'user-agent': get_headers()
|
|
33
41
|
}
|
|
34
|
-
|
|
42
|
+
|
|
35
43
|
async def fetch_url(self, url: str) -> Optional[str]:
|
|
36
44
|
"""
|
|
37
45
|
Fetch the HTML content of a given URL.
|
|
@@ -27,7 +27,7 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
|
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
if string_to_search is None:
|
|
30
|
-
string_to_search = msg.ask(f"\n[purple]Insert word to search in [
|
|
30
|
+
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
|
|
31
31
|
|
|
32
32
|
# Not available for the moment
|
|
33
33
|
if get_onylDatabase:
|
|
@@ -70,10 +70,8 @@ def download_film(movie_details: Json_film) -> str:
|
|
|
70
70
|
player_links = soup.find("ul", class_ = "_player-mirrors").find_all("li")
|
|
71
71
|
supervideo_url = "https:" + player_links[0].get("data-link")
|
|
72
72
|
|
|
73
|
-
|
|
74
73
|
# Set domain and media ID for the video source
|
|
75
|
-
video_source = VideoSource()
|
|
76
|
-
video_source.setup(supervideo_url)
|
|
74
|
+
video_source = VideoSource(url=supervideo_url)
|
|
77
75
|
|
|
78
76
|
# Define output path
|
|
79
77
|
title_name = os_manager.get_sanitize_file(movie_details.title) + ".mp4"
|
|
@@ -28,7 +28,7 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
|
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
30
|
if string_to_search is None:
|
|
31
|
-
string_to_search = msg.ask(f"\n[purple]Insert word to search in [
|
|
31
|
+
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
|
|
32
32
|
|
|
33
33
|
# Get site domain and version and get result of the search
|
|
34
34
|
site_version, domain = get_version_and_domain()
|
|
@@ -39,7 +39,7 @@ def download_film(select_title: MediaItem) -> str:
|
|
|
39
39
|
|
|
40
40
|
# Start message and display film information
|
|
41
41
|
start_message()
|
|
42
|
-
console.print(f"[yellow]Download:
|
|
42
|
+
console.print(f"[yellow]Download: [red]{select_title.name} \n")
|
|
43
43
|
|
|
44
44
|
# Init class
|
|
45
45
|
video_source = VideoSource(SITE_NAME, False)
|
|
@@ -25,7 +25,6 @@ from StreamingCommunity.Api.Player.vixcloud import VideoSource
|
|
|
25
25
|
|
|
26
26
|
# Variable
|
|
27
27
|
from .costant import ROOT_PATH, SITE_NAME, SERIES_FOLDER
|
|
28
|
-
table_show_manager = TVShowManager()
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
|
|
@@ -178,6 +177,7 @@ def display_episodes_list(scrape_serie) -> str:
|
|
|
178
177
|
"""
|
|
179
178
|
|
|
180
179
|
# Set up table for displaying episodes
|
|
180
|
+
table_show_manager = TVShowManager()
|
|
181
181
|
table_show_manager.set_slice_end(10)
|
|
182
182
|
|
|
183
183
|
# Add columns to the table
|
|
@@ -76,17 +76,19 @@ def get_final_redirect_url(initial_url, max_timeout):
|
|
|
76
76
|
console.print(f"\n[cyan]Test url[white]: [red]{initial_url}, [cyan]error[white]: [red]{e}")
|
|
77
77
|
return None
|
|
78
78
|
|
|
79
|
-
def search_domain(site_name: str, base_url: str):
|
|
79
|
+
def search_domain(site_name: str, base_url: str, get_first: bool = False):
|
|
80
80
|
"""
|
|
81
81
|
Search for a valid domain for the given site name and base URL.
|
|
82
82
|
|
|
83
83
|
Parameters:
|
|
84
84
|
- site_name (str): The name of the site to search the domain for.
|
|
85
85
|
- base_url (str): The base URL to construct complete URLs.
|
|
86
|
+
- get_first (bool): If True, automatically update to the first valid match without user confirmation.
|
|
86
87
|
|
|
87
88
|
Returns:
|
|
88
89
|
tuple: The found domain and the complete URL.
|
|
89
90
|
"""
|
|
91
|
+
|
|
90
92
|
# Extract config domain
|
|
91
93
|
max_timeout = config_manager.get_int("REQUESTS", "timeout")
|
|
92
94
|
domain = str(config_manager.get_dict("SITE", site_name)['domain'])
|
|
@@ -107,10 +109,10 @@ def search_domain(site_name: str, base_url: str):
|
|
|
107
109
|
|
|
108
110
|
except Exception as e:
|
|
109
111
|
query = base_url.split("/")[-1]
|
|
110
|
-
|
|
112
|
+
|
|
111
113
|
# Perform a Google search with multiple results
|
|
112
114
|
search_results = list(search(query, num_results=10, lang="it"))
|
|
113
|
-
console.print(f"\nGoogle search results: {search_results}")
|
|
115
|
+
#console.print(f"\nGoogle search results: {search_results}")
|
|
114
116
|
|
|
115
117
|
def normalize_for_comparison(url):
|
|
116
118
|
"""Normalize URL by removing protocol, www, and trailing slashes"""
|
|
@@ -121,15 +123,15 @@ def search_domain(site_name: str, base_url: str):
|
|
|
121
123
|
|
|
122
124
|
# Normalize the base_url we're looking for
|
|
123
125
|
target_url = normalize_for_comparison(base_url)
|
|
124
|
-
|
|
126
|
+
|
|
125
127
|
# Iterate through search results
|
|
126
128
|
for first_url in search_results:
|
|
127
129
|
console.print(f"[green]Checking url[white]: [red]{first_url}")
|
|
128
|
-
|
|
130
|
+
|
|
129
131
|
# Get just the domain part of the search result
|
|
130
132
|
parsed_result = urlparse(first_url)
|
|
131
133
|
result_domain = normalize_for_comparison(parsed_result.netloc)
|
|
132
|
-
|
|
134
|
+
|
|
133
135
|
# Compare with our target URL (without the protocol part)
|
|
134
136
|
if result_domain.startswith(target_url.split("/")[-1]):
|
|
135
137
|
try:
|
|
@@ -143,21 +145,20 @@ def search_domain(site_name: str, base_url: str):
|
|
|
143
145
|
|
|
144
146
|
new_domain_extract = extract_domain(str(final_url))
|
|
145
147
|
|
|
146
|
-
if msg.ask(f"\n[cyan]Do you want to auto update site[white] [red]'{site_name}'[cyan] with domain[white] [red]'{new_domain_extract}'.", choices=["y", "n"], default="y").lower() == "y":
|
|
147
|
-
|
|
148
|
+
if get_first or msg.ask(f"\n[cyan]Do you want to auto update site[white] [red]'{site_name}'[cyan] with domain[white] [red]'{new_domain_extract}'.", choices=["y", "n"], default="y").lower() == "y":
|
|
148
149
|
# Update domain in config.json
|
|
149
150
|
config_manager.config['SITE'][site_name]['domain'] = new_domain_extract
|
|
150
151
|
config_manager.write_config()
|
|
151
152
|
|
|
152
153
|
return new_domain_extract, f"{base_url}.{new_domain_extract}"
|
|
153
|
-
|
|
154
|
+
|
|
154
155
|
except Exception as redirect_error:
|
|
155
156
|
console.print(f"[red]Error following redirect for {first_url}: {redirect_error}")
|
|
156
157
|
continue
|
|
157
158
|
|
|
158
|
-
# If no matching URL is found
|
|
159
|
+
# If no matching URL is found return base domain
|
|
159
160
|
console.print("[bold red]No valid URL found matching the base URL.[/bold red]")
|
|
160
|
-
|
|
161
|
+
return domain, f"{base_url}.{domain}"
|
|
161
162
|
|
|
162
163
|
# Handle successful initial domain check
|
|
163
164
|
parsed_url = urlparse(str(response_follow.url))
|
|
@@ -176,9 +176,11 @@ class ContentExtractor:
|
|
|
176
176
|
set_language = DOWNLOAD_SPECIFIC_AUDIO
|
|
177
177
|
downloadable_languages = list(set(available_languages) & set(set_language))
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
# Only show if there is something available
|
|
180
|
+
if len(available_languages) > 0:
|
|
181
|
+
console.print(f"[cyan bold]Audio →[/cyan bold] [green]Available:[/green] [purple]{', '.join(available_languages)}[/purple] | "
|
|
182
|
+
f"[red]Set:[/red] [purple]{', '.join(set_language)}[/purple] | "
|
|
183
|
+
f"[yellow]Downloadable:[/yellow] [purple]{', '.join(downloadable_languages)}[/purple]")
|
|
182
184
|
|
|
183
185
|
else:
|
|
184
186
|
console.log("[red]Can't find a list of audios")
|
|
@@ -200,9 +202,11 @@ class ContentExtractor:
|
|
|
200
202
|
set_language = DOWNLOAD_SPECIFIC_SUBTITLE
|
|
201
203
|
downloadable_languages = list(set(available_languages) & set(set_language))
|
|
202
204
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
205
|
+
# Only show if there is something available
|
|
206
|
+
if len(available_languages) > 0:
|
|
207
|
+
console.print(f"[cyan bold]Subtitle →[/cyan bold] [green]Available:[/green] [purple]{', '.join(available_languages)}[/purple] | "
|
|
208
|
+
f"[red]Set:[/red] [purple]{', '.join(set_language)}[/purple] | "
|
|
209
|
+
f"[yellow]Downloadable:[/yellow] [purple]{', '.join(downloadable_languages)}[/purple]")
|
|
206
210
|
|
|
207
211
|
else:
|
|
208
212
|
console.log("[red]Can't find a list of subtitles")
|
|
@@ -212,13 +216,18 @@ class ContentExtractor:
|
|
|
212
216
|
It identifies the best video quality and displays relevant information to the user.
|
|
213
217
|
"""
|
|
214
218
|
logging.info(f"class 'ContentExtractor'; call _collect_video()")
|
|
219
|
+
set_resolution = "Best"
|
|
215
220
|
|
|
216
221
|
# Collect custom quality video if a specific resolution is set
|
|
217
222
|
if FILTER_CUSTOM_REOLUTION != -1:
|
|
218
223
|
self.m3u8_index, video_res = self.obj_parse._video.get_custom_uri(y_resolution=FILTER_CUSTOM_REOLUTION)
|
|
224
|
+
set_resolution = f"{FILTER_CUSTOM_REOLUTION}p"
|
|
219
225
|
|
|
220
|
-
|
|
221
|
-
|
|
226
|
+
else:
|
|
227
|
+
|
|
228
|
+
# Otherwise, get the best available video quality
|
|
229
|
+
self.m3u8_index, video_res = self.obj_parse._video.get_best_uri()
|
|
230
|
+
|
|
222
231
|
self.codec: M3U8_Codec = self.obj_parse.codec
|
|
223
232
|
|
|
224
233
|
# List all available resolutions
|
|
@@ -227,18 +236,34 @@ class ContentExtractor:
|
|
|
227
236
|
logging.info(f"M3U8 index selected: {self.m3u8_index}, with resolution: {video_res}")
|
|
228
237
|
|
|
229
238
|
# Create a formatted table to display video info
|
|
230
|
-
console.print(f"[cyan bold]Video
|
|
231
|
-
|
|
239
|
+
console.print(f"[cyan bold]Video →[/cyan bold] [green]Available:[/green] [purple]{', '.join(list_available_resolution)}[/purple] | "
|
|
240
|
+
f"[red]Set:[/red] [purple]{set_resolution}[/purple] | "
|
|
241
|
+
f"[yellow]Downloadable:[/yellow] [purple]{video_res[0]}x{video_res[1]}[/purple]")
|
|
232
242
|
|
|
233
243
|
if self.codec is not None:
|
|
244
|
+
|
|
245
|
+
# Generate the string for available codec information
|
|
246
|
+
available_codec_info = (
|
|
247
|
+
f"[green]v[/green]: [yellow]{self.codec.video_codec_name}[/yellow] "
|
|
248
|
+
f"([green]b[/green]: [yellow]{self.codec.video_bitrate // 1000}k[/yellow]), "
|
|
249
|
+
f"[green]a[/green]: [yellow]{self.codec.audio_codec_name}[/yellow] "
|
|
250
|
+
f"([green]b[/green]: [yellow]{self.codec.audio_bitrate // 1000}k[/yellow])"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# Determine what to display for "Set"
|
|
254
|
+
# If the codec usage is enabled in the configuration, use the detailed codec info
|
|
255
|
+
# Otherwise, display "copy"
|
|
234
256
|
if config_manager.get_bool("M3U8_CONVERSION", "use_codec"):
|
|
235
|
-
|
|
236
|
-
f"([green]b[/green]: [yellow]{self.codec.video_bitrate // 1000}k[/yellow]), "
|
|
237
|
-
f"[green]a[/green]: [yellow]{self.codec.audio_codec_name}[/yellow] "
|
|
238
|
-
f"([green]b[/green]: [yellow]{self.codec.audio_bitrate // 1000}k[/yellow])")
|
|
257
|
+
set_codec_info = available_codec_info
|
|
239
258
|
else:
|
|
240
|
-
|
|
241
|
-
|
|
259
|
+
set_codec_info = "[purple]copy[/purple]"
|
|
260
|
+
|
|
261
|
+
# Print the formatted result with "Available" and "Set" information
|
|
262
|
+
console.print(
|
|
263
|
+
f"[bold cyan]Codec →[/bold cyan] [green]Available:[/green] {available_codec_info} | "
|
|
264
|
+
f"[red]Set:[/red] {set_codec_info}"
|
|
265
|
+
)
|
|
266
|
+
|
|
242
267
|
|
|
243
268
|
# Fix the URL if it does not include the full protocol
|
|
244
269
|
if "http" not in self.m3u8_index:
|
|
@@ -486,19 +511,6 @@ class ContentJoiner:
|
|
|
486
511
|
self.there_is_audio = len(downloaded_audio) > 0
|
|
487
512
|
self.there_is_subtitle = len(downloaded_subtitle) > 0
|
|
488
513
|
|
|
489
|
-
if self.there_is_audio or self.there_is_subtitle:
|
|
490
|
-
|
|
491
|
-
# Display the status of available media
|
|
492
|
-
table = Table(show_header=False, box=None)
|
|
493
|
-
|
|
494
|
-
table.add_row(f"[green]Video - audio", f"[yellow]{self.there_is_audio}")
|
|
495
|
-
table.add_row(f"[green]Video - Subtitle", f"[yellow]{self.there_is_subtitle}")
|
|
496
|
-
|
|
497
|
-
print("")
|
|
498
|
-
console.rule("[bold green] JOIN ", style="bold red")
|
|
499
|
-
console.print(table)
|
|
500
|
-
print("")
|
|
501
|
-
|
|
502
514
|
# Start the joining process
|
|
503
515
|
self.conversione()
|
|
504
516
|
|
|
@@ -584,10 +596,6 @@ class ContentJoiner:
|
|
|
584
596
|
# Check if the joined video file already exists
|
|
585
597
|
if not os.path.exists(path_join_video):
|
|
586
598
|
|
|
587
|
-
# Set codec to None if not defined in class
|
|
588
|
-
#if not hasattr(self, 'codec'):
|
|
589
|
-
# self.codec = None
|
|
590
|
-
|
|
591
599
|
# Join the video segments into a single video file
|
|
592
600
|
join_video(
|
|
593
601
|
video_path=self.downloaded_video[0].get('path'),
|
|
@@ -844,7 +852,7 @@ class HLS_Downloader:
|
|
|
844
852
|
f"[bold green]Download completed![/bold green]\n"
|
|
845
853
|
f"[cyan]File size: [bold red]{formatted_size}[/bold red]\n"
|
|
846
854
|
f"[cyan]Duration: [bold]{formatted_duration}[/bold]\n"
|
|
847
|
-
f"[cyan]Output: [bold]{self.output_filename}[/bold]"
|
|
855
|
+
f"[cyan]Output: [bold]{os.path.abspath(self.output_filename)}[/bold]"
|
|
848
856
|
)
|
|
849
857
|
|
|
850
858
|
if missing_ts:
|
|
@@ -432,7 +432,7 @@ class M3U8_Segments:
|
|
|
432
432
|
if "audio" in str(type):
|
|
433
433
|
TQDM_MAX_WORKER = AUDIO_WORKERS
|
|
434
434
|
|
|
435
|
-
console.print(f"[cyan]Video workers[white]: [green]{VIDEO_WORKERS} [white]| [cyan]Audio workers[white]: [green]{AUDIO_WORKERS}")
|
|
435
|
+
#console.print(f"[cyan]Video workers[white]: [green]{VIDEO_WORKERS} [white]| [cyan]Audio workers[white]: [green]{AUDIO_WORKERS}")
|
|
436
436
|
|
|
437
437
|
# Custom bar for mobile and pc
|
|
438
438
|
if TQDM_USE_LARGE_BAR:
|
|
@@ -544,18 +544,17 @@ class M3U8_Segments:
|
|
|
544
544
|
file_size = os.path.getsize(self.tmp_file_path)
|
|
545
545
|
if file_size == 0:
|
|
546
546
|
raise Exception("Output file is empty")
|
|
547
|
-
|
|
548
|
-
#
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
547
|
+
|
|
548
|
+
# Display additional info only if there is failed segments
|
|
549
|
+
if self.info_nFailed > 0:
|
|
550
|
+
|
|
551
|
+
# Get expected time
|
|
552
|
+
ex_hours, ex_minutes, ex_seconds = format_duration(self.expected_real_time_s)
|
|
553
|
+
ex_formatted_duration = f"[yellow]{int(ex_hours)}[red]h [yellow]{int(ex_minutes)}[red]m [yellow]{int(ex_seconds)}[red]s"
|
|
554
|
+
console.print(f"[cyan]Max retry per URL[white]: [green]{self.info_maxRetry}[green] [white]| [cyan]Total retry done[white]: [green]{self.info_nRetry}[green] [white]| [cyan]Missing TS: [red]{self.info_nFailed} [white]| [cyan]Duration: {print_duration_table(self.tmp_file_path, None, True)} [white]| [cyan]Expected duation: {ex_formatted_duration} \n")
|
|
552
555
|
|
|
553
556
|
if self.info_nRetry >= len(self.segments) * (1/3.33):
|
|
554
|
-
console.print(
|
|
555
|
-
"[yellow]⚠ Warning:[/yellow] Too many retries detected! "
|
|
556
|
-
"Consider reducing the number of [cyan]workers[/cyan] in the [magenta]config.json[/magenta] file. "
|
|
557
|
-
"This will impact [bold]performance[/bold]. \n"
|
|
558
|
-
)
|
|
557
|
+
console.print("[yellow]⚠ Warning:[/yellow] Too many retries detected! Consider reducing the number of [cyan]workers[/cyan] in the [magenta]config.json[/magenta] file. This will impact [bold]performance[/bold]. \n")
|
|
559
558
|
|
|
560
559
|
# Info to return
|
|
561
560
|
return {'type': type, 'nFailed': self.info_nFailed}
|
|
@@ -59,7 +59,7 @@ def capture_output(process: subprocess.Popen, description: str) -> None:
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
# Construct the progress string with formatted output information
|
|
62
|
-
progress_string = (f"
|
|
62
|
+
progress_string = (f"→ {description}[white]: "
|
|
63
63
|
f"([green]'speed': [yellow]{data.get('speed', 'N/A')}[white], "
|
|
64
64
|
f"[green]'size': [yellow]{internet_manager.format_file_size(byte_size)}[white])")
|
|
65
65
|
max_length = max(max_length, len(progress_string))
|
|
@@ -208,7 +208,7 @@ def is_png_format_or_codec(file_info):
|
|
|
208
208
|
if not file_info:
|
|
209
209
|
return False
|
|
210
210
|
|
|
211
|
-
console.print(f"[yellow][FFmpeg] [cyan]Avaiable codec[white]: [red]{file_info['codec_names']}")
|
|
211
|
+
#console.print(f"[yellow][FFmpeg] [cyan]Avaiable codec[white]: [red]{file_info['codec_names']}")
|
|
212
212
|
return file_info['format_name'] == 'png_pipe' or 'png' in file_info['codec_names']
|
|
213
213
|
|
|
214
214
|
|
|
@@ -17,7 +17,7 @@ crypto_installed = crypto_spec is not None
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
if crypto_installed:
|
|
20
|
-
|
|
20
|
+
logging.info("[cyan]Decrypy use: Cryptodomex")
|
|
21
21
|
from Cryptodome.Cipher import AES
|
|
22
22
|
from Cryptodome.Util.Padding import unpad
|
|
23
23
|
|
|
@@ -93,7 +93,7 @@ else:
|
|
|
93
93
|
# Check if openssl command is available
|
|
94
94
|
try:
|
|
95
95
|
openssl_available = subprocess.run(["openssl", "version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0
|
|
96
|
-
|
|
96
|
+
logging.info("[cyan]Decrypy use: OPENSSL")
|
|
97
97
|
except:
|
|
98
98
|
openssl_available = False
|
|
99
99
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
# 20.02.24
|
|
2
|
-
|
|
3
1
|
import os
|
|
4
2
|
import time
|
|
5
3
|
import logging
|
|
@@ -34,10 +32,13 @@ class M3U8_Ts_Estimator:
|
|
|
34
32
|
self.now_downloaded_size = 0
|
|
35
33
|
self.total_segments = total_segments
|
|
36
34
|
self.lock = threading.Lock()
|
|
37
|
-
self.speed = {"upload": "N/A", "download": "N/A"}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
self.speed = {"upload": "N/A", "download": "N/A"}
|
|
36
|
+
|
|
37
|
+
# Only start the speed capture thread if TQDM_USE_LARGE_BAR is True
|
|
38
|
+
if not TQDM_USE_LARGE_BAR:
|
|
39
|
+
self.speed_thread = threading.Thread(target=self.capture_speed)
|
|
40
|
+
self.speed_thread.daemon = True
|
|
41
|
+
self.speed_thread.start()
|
|
41
42
|
|
|
42
43
|
def add_ts_file(self, size: int, size_download: int, duration: float):
|
|
43
44
|
"""
|
|
@@ -56,43 +57,71 @@ class M3U8_Ts_Estimator:
|
|
|
56
57
|
self.ts_file_sizes.append(size)
|
|
57
58
|
self.now_downloaded_size += size_download
|
|
58
59
|
|
|
59
|
-
def capture_speed(self, interval: float = 1):
|
|
60
|
+
def capture_speed(self, interval: float = 1, pid: int = None):
|
|
60
61
|
"""
|
|
61
|
-
Capture the internet speed periodically
|
|
62
|
+
Capture the internet speed periodically for a specific process (identified by PID)
|
|
63
|
+
or the entire system if no PID is provided.
|
|
62
64
|
"""
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
|
|
66
|
+
def get_network_io(process=None):
|
|
67
|
+
"""
|
|
68
|
+
Get network I/O counters for a specific process or system-wide if no process is specified.
|
|
69
|
+
"""
|
|
65
70
|
try:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
if process:
|
|
72
|
+
io_counters = process.io_counters()
|
|
73
|
+
return io_counters
|
|
74
|
+
else:
|
|
75
|
+
io_counters = psutil.net_io_counters()
|
|
76
|
+
return io_counters
|
|
69
77
|
except Exception as e:
|
|
70
78
|
logging.warning(f"Unable to access network I/O counters: {e}")
|
|
71
79
|
return None
|
|
72
80
|
|
|
81
|
+
# If a PID is provided, attempt to attach to the corresponding process
|
|
82
|
+
process = None
|
|
83
|
+
if pid is not None:
|
|
84
|
+
try:
|
|
85
|
+
process = psutil.Process(pid)
|
|
86
|
+
except psutil.NoSuchProcess:
|
|
87
|
+
logging.error(f"Process with PID {pid} does not exist.")
|
|
88
|
+
return
|
|
89
|
+
except Exception as e:
|
|
90
|
+
logging.error(f"Failed to attach to process with PID {pid}: {e}")
|
|
91
|
+
return
|
|
92
|
+
|
|
73
93
|
while True:
|
|
74
|
-
old_value = get_network_io()
|
|
94
|
+
old_value = get_network_io(process)
|
|
75
95
|
|
|
76
|
-
if old_value is None: # If psutil
|
|
96
|
+
if old_value is None: # If psutil fails, continue with the next interval
|
|
77
97
|
time.sleep(interval)
|
|
78
98
|
continue
|
|
79
99
|
|
|
80
100
|
time.sleep(interval)
|
|
81
|
-
new_value = get_network_io()
|
|
82
|
-
|
|
101
|
+
new_value = get_network_io(process)
|
|
102
|
+
|
|
83
103
|
if new_value is None: # Handle again if psutil fails in the next call
|
|
84
104
|
time.sleep(interval)
|
|
85
105
|
continue
|
|
86
106
|
|
|
87
107
|
with self.lock:
|
|
88
|
-
|
|
89
|
-
|
|
108
|
+
|
|
109
|
+
# Calculate speed based on process-specific counters if process is specified
|
|
110
|
+
if process:
|
|
111
|
+
upload_speed = (new_value.write_bytes - old_value.write_bytes) / interval
|
|
112
|
+
download_speed = (new_value.read_bytes - old_value.read_bytes) / interval
|
|
113
|
+
|
|
114
|
+
else:
|
|
115
|
+
# System-wide counters
|
|
116
|
+
upload_speed = (new_value.bytes_sent - old_value.bytes_sent) / interval
|
|
117
|
+
download_speed = (new_value.bytes_recv - old_value.bytes_recv) / interval
|
|
90
118
|
|
|
91
119
|
self.speed = {
|
|
92
120
|
"upload": internet_manager.format_transfer_speed(upload_speed),
|
|
93
121
|
"download": internet_manager.format_transfer_speed(download_speed)
|
|
94
122
|
}
|
|
95
123
|
|
|
124
|
+
|
|
96
125
|
def get_average_speed(self) -> float:
|
|
97
126
|
"""
|
|
98
127
|
Calculate the average internet speed.
|
|
@@ -159,18 +188,17 @@ class M3U8_Ts_Estimator:
|
|
|
159
188
|
units_file_downloaded = downloaded_file_size_str.split(' ')[1]
|
|
160
189
|
units_file_total_size = file_total_size.split(' ')[1]
|
|
161
190
|
|
|
162
|
-
average_internet_speed = self.get_average_speed()[0]
|
|
163
|
-
average_internet_unit = self.get_average_speed()[1]
|
|
164
|
-
|
|
165
191
|
# Update the progress bar's postfix
|
|
166
192
|
if TQDM_USE_LARGE_BAR:
|
|
193
|
+
average_internet_speed = self.get_average_speed()[0]
|
|
194
|
+
average_internet_unit = self.get_average_speed()[1]
|
|
167
195
|
progress_counter.set_postfix_str(
|
|
168
196
|
f"{Colors.WHITE}[ {Colors.GREEN}{number_file_downloaded} {Colors.WHITE}< {Colors.GREEN}{number_file_total_size} {Colors.RED}{units_file_total_size} "
|
|
169
197
|
f"{Colors.WHITE}| {Colors.CYAN}{average_internet_speed} {Colors.RED}{average_internet_unit}"
|
|
170
198
|
)
|
|
199
|
+
|
|
171
200
|
else:
|
|
172
201
|
progress_counter.set_postfix_str(
|
|
173
202
|
f"{Colors.WHITE}[ {Colors.GREEN}{number_file_downloaded}{Colors.RED} {units_file_downloaded} "
|
|
174
|
-
f"{Colors.WHITE}| {Colors.CYAN}{
|
|
175
|
-
)
|
|
176
|
-
|
|
203
|
+
f"{Colors.WHITE}| {Colors.CYAN}N/A{Colors.RED} N/A"
|
|
204
|
+
)
|
|
@@ -57,11 +57,10 @@ def update():
|
|
|
57
57
|
|
|
58
58
|
# Check installed version
|
|
59
59
|
if str(__version__).replace('v', '') != str(last_version).replace('v', '') :
|
|
60
|
-
console.print(f"[red]New version available: [yellow]{last_version}")
|
|
60
|
+
console.print(f"[red]New version available: [yellow]{last_version} \n")
|
|
61
61
|
else:
|
|
62
|
-
console.print(f"[
|
|
62
|
+
console.print(f" [yellow]Everything is up to date \n")
|
|
63
63
|
|
|
64
|
-
console.print("\n")
|
|
65
64
|
console.print(f"[red]{__title__} has been downloaded [yellow]{total_download_count} [red]times, but only [yellow]{percentual_stars}% [red]of users have starred it.\n\
|
|
66
65
|
[cyan]Help the repository grow today by leaving a [yellow]star [cyan]and [yellow]sharing [cyan]it with others online!")
|
|
67
66
|
|
|
@@ -116,21 +116,34 @@ class FFMPEGDownloader:
|
|
|
116
116
|
def _check_existing_binaries(self) -> Tuple[Optional[str], Optional[str], Optional[str]]:
|
|
117
117
|
"""
|
|
118
118
|
Check if FFmpeg binaries already exist in the base directory.
|
|
119
|
-
|
|
120
|
-
Returns:
|
|
121
|
-
Tuple[Optional[str], Optional[str], Optional[str]]: Paths to ffmpeg, ffprobe, and ffplay executables.
|
|
122
|
-
Returns None for each executable that is not found.
|
|
119
|
+
Enhanced to check both the binary directory and system paths on macOS.
|
|
123
120
|
"""
|
|
124
121
|
config = FFMPEG_CONFIGURATION[self.os_name]
|
|
125
122
|
executables = config['executables']
|
|
126
123
|
found_executables = []
|
|
127
124
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
125
|
+
# For macOS, check both binary directory and system paths
|
|
126
|
+
if self.os_name == 'darwin':
|
|
127
|
+
potential_paths = [
|
|
128
|
+
'/usr/local/bin',
|
|
129
|
+
'/opt/homebrew/bin',
|
|
130
|
+
'/usr/bin',
|
|
131
|
+
self.base_dir
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
for executable in executables:
|
|
135
|
+
found = None
|
|
136
|
+
for path in potential_paths:
|
|
137
|
+
full_path = os.path.join(path, executable)
|
|
138
|
+
if os.path.exists(full_path) and os.access(full_path, os.X_OK):
|
|
139
|
+
found = full_path
|
|
140
|
+
break
|
|
141
|
+
found_executables.append(found)
|
|
142
|
+
else:
|
|
143
|
+
# Original behavior for other operating systems
|
|
144
|
+
for executable in executables:
|
|
145
|
+
exe_paths = glob.glob(os.path.join(self.base_dir, executable))
|
|
146
|
+
found_executables.append(exe_paths[0] if exe_paths else None)
|
|
134
147
|
|
|
135
148
|
return tuple(found_executables) if len(found_executables) == 3 else (None, None, None)
|
|
136
149
|
|
|
@@ -275,37 +288,61 @@ class FFMPEGDownloader:
|
|
|
275
288
|
def check_ffmpeg() -> Tuple[Optional[str], Optional[str], Optional[str]]:
|
|
276
289
|
"""
|
|
277
290
|
Check for FFmpeg executables in the system and download them if not found.
|
|
291
|
+
Enhanced detection for macOS systems.
|
|
278
292
|
|
|
279
293
|
Returns:
|
|
280
294
|
Tuple[Optional[str], Optional[str], Optional[str]]: Paths to ffmpeg, ffprobe, and ffplay executables.
|
|
281
|
-
Returns None for each executable that couldn't be found or downloaded.
|
|
282
|
-
|
|
283
|
-
The function first checks if FFmpeg executables are available in the system PATH:
|
|
284
|
-
- On Windows, uses the 'where' command
|
|
285
|
-
- On Unix-like systems, uses 'which'
|
|
286
|
-
|
|
287
|
-
If the executables are not found in PATH, it attempts to download and install them
|
|
288
|
-
using the FFMPEGDownloader class.
|
|
289
295
|
"""
|
|
290
296
|
try:
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
297
|
+
system_platform = platform.system().lower()
|
|
298
|
+
|
|
299
|
+
# Special handling for macOS
|
|
300
|
+
if system_platform == 'darwin':
|
|
301
|
+
# Common installation paths on macOS
|
|
302
|
+
potential_paths = [
|
|
303
|
+
'/usr/local/bin', # Homebrew default
|
|
304
|
+
'/opt/homebrew/bin', # Apple Silicon Homebrew
|
|
305
|
+
'/usr/bin', # System default
|
|
306
|
+
os.path.expanduser('~/Applications/binary'), # Custom installation
|
|
307
|
+
'/Applications/binary' # Custom installation
|
|
308
|
+
]
|
|
295
309
|
|
|
296
|
-
|
|
297
|
-
|
|
310
|
+
for path in potential_paths:
|
|
311
|
+
ffmpeg_path = os.path.join(path, 'ffmpeg')
|
|
312
|
+
ffprobe_path = os.path.join(path, 'ffprobe')
|
|
313
|
+
ffplay_path = os.path.join(path, 'ffplay')
|
|
314
|
+
|
|
315
|
+
if (os.path.exists(ffmpeg_path) and os.path.exists(ffprobe_path) and
|
|
316
|
+
os.access(ffmpeg_path, os.X_OK) and os.access(ffprobe_path, os.X_OK)):
|
|
317
|
+
# Return found executables, with ffplay being optional
|
|
318
|
+
ffplay_path = ffplay_path if os.path.exists(ffplay_path) else None
|
|
319
|
+
return ffmpeg_path, ffprobe_path, ffplay_path
|
|
320
|
+
|
|
321
|
+
# Windows detection
|
|
322
|
+
elif system_platform == 'windows':
|
|
323
|
+
try:
|
|
324
|
+
ffmpeg_path = subprocess.check_output(['where', 'ffmpeg'], text=True).strip().split('\n')[0]
|
|
325
|
+
ffprobe_path = subprocess.check_output(['where', 'ffprobe'], text=True).strip().split('\n')[0]
|
|
326
|
+
ffplay_path = subprocess.check_output(['where', 'ffplay'], text=True).strip().split('\n')[0]
|
|
327
|
+
|
|
328
|
+
if ffmpeg_path and ffprobe_path:
|
|
329
|
+
return ffmpeg_path, ffprobe_path, ffplay_path
|
|
330
|
+
except subprocess.CalledProcessError:
|
|
331
|
+
pass
|
|
332
|
+
|
|
333
|
+
# Linux detection
|
|
298
334
|
else:
|
|
299
335
|
ffmpeg_path = shutil.which('ffmpeg')
|
|
300
336
|
ffprobe_path = shutil.which('ffprobe')
|
|
301
337
|
ffplay_path = shutil.which('ffplay')
|
|
302
338
|
|
|
303
|
-
if
|
|
339
|
+
if ffmpeg_path and ffprobe_path:
|
|
304
340
|
return ffmpeg_path, ffprobe_path, ffplay_path
|
|
305
|
-
|
|
341
|
+
|
|
342
|
+
# If executables were not found, attempt to download FFmpeg
|
|
306
343
|
downloader = FFMPEGDownloader()
|
|
307
344
|
return downloader.download()
|
|
308
|
-
|
|
345
|
+
|
|
309
346
|
except Exception as e:
|
|
310
|
-
logging.error(f"Error checking FFmpeg: {e}")
|
|
347
|
+
logging.error(f"Error checking or downloading FFmpeg executables: {e}")
|
|
311
348
|
return None, None, None
|
StreamingCommunity/Util/os.py
CHANGED
|
@@ -317,16 +317,13 @@ class InternManager():
|
|
|
317
317
|
def check_internet():
|
|
318
318
|
while True:
|
|
319
319
|
try:
|
|
320
|
-
httpx.get("https://www.google.com")
|
|
321
|
-
#console.log("[bold green]Internet is available![/bold green]")
|
|
320
|
+
httpx.get("https://www.google.com", timeout=15)
|
|
322
321
|
break
|
|
323
322
|
|
|
324
323
|
except urllib.error.URLError:
|
|
325
324
|
console.log("[bold red]Internet is not available. Waiting...[/bold red]")
|
|
326
325
|
time.sleep(5)
|
|
327
326
|
|
|
328
|
-
print()
|
|
329
|
-
|
|
330
327
|
|
|
331
328
|
class OsSummary:
|
|
332
329
|
|
|
@@ -353,20 +350,17 @@ class OsSummary:
|
|
|
353
350
|
console.print(f"{command[0]} not found", style="bold red")
|
|
354
351
|
sys.exit(0)
|
|
355
352
|
|
|
356
|
-
def check_ffmpeg_location(self, command: list):
|
|
353
|
+
def check_ffmpeg_location(self, command: list) -> str:
|
|
357
354
|
"""
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
Returns:
|
|
361
|
-
str: Location of FFmpeg executable or None if not found
|
|
355
|
+
Check if a specific executable (ffmpeg or ffprobe) is located using the given command.
|
|
356
|
+
Returns the path of the executable or None if not found.
|
|
362
357
|
"""
|
|
363
358
|
try:
|
|
364
|
-
result = subprocess.check_output(command,
|
|
365
|
-
return result
|
|
359
|
+
result = subprocess.check_output(command, text=True).strip()
|
|
360
|
+
return result.split('\n')[0] if result else None
|
|
366
361
|
|
|
367
362
|
except subprocess.CalledProcessError:
|
|
368
|
-
|
|
369
|
-
sys.exit(0)
|
|
363
|
+
return None
|
|
370
364
|
|
|
371
365
|
def get_library_version(self, lib_name: str):
|
|
372
366
|
"""
|
|
@@ -467,27 +461,24 @@ class OsSummary:
|
|
|
467
461
|
console.print(f"[cyan]Python[white]: [bold red]{python_version} ({python_implementation} {arch}) - {os_info} ({glibc_version})[/bold red]")
|
|
468
462
|
logging.info(f"Python: {python_version} ({python_implementation} {arch}) - {os_info} ({glibc_version})")
|
|
469
463
|
|
|
470
|
-
#
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
# Usa il comando 'which' su Unix/Linux
|
|
475
|
-
else:
|
|
476
|
-
command = 'which'
|
|
464
|
+
# Use the 'where' command on Windows and 'which' command on Unix-like systems
|
|
465
|
+
system_platform = platform.system().lower()
|
|
466
|
+
command = 'where' if system_platform == 'windows' else 'which'
|
|
477
467
|
|
|
478
|
-
# Locate ffmpeg and ffprobe from
|
|
479
|
-
if self.ffmpeg_path
|
|
468
|
+
# Locate ffmpeg and ffprobe from the PATH environment
|
|
469
|
+
if self.ffmpeg_path is not None and "binary" not in self.ffmpeg_path:
|
|
480
470
|
self.ffmpeg_path = self.check_ffmpeg_location([command, 'ffmpeg'])
|
|
481
471
|
|
|
482
|
-
if self.ffprobe_path
|
|
472
|
+
if self.ffprobe_path is not None and "binary" not in self.ffprobe_path:
|
|
483
473
|
self.ffprobe_path = self.check_ffmpeg_location([command, 'ffprobe'])
|
|
484
474
|
|
|
485
|
-
#
|
|
475
|
+
# If ffmpeg or ffprobe is not located, fall back to using the check_ffmpeg function
|
|
486
476
|
if self.ffmpeg_path is None or self.ffprobe_path is None:
|
|
487
477
|
self.ffmpeg_path, self.ffprobe_path, self.ffplay_path = check_ffmpeg()
|
|
488
478
|
|
|
479
|
+
# If still not found, print error and exit
|
|
489
480
|
if self.ffmpeg_path is None or self.ffprobe_path is None:
|
|
490
|
-
console.log("[red]
|
|
481
|
+
console.log("[red]Can't locate ffmpeg or ffprobe")
|
|
491
482
|
sys.exit(0)
|
|
492
483
|
|
|
493
484
|
ffmpeg_version = self.get_executable_version([self.ffprobe_path, '-version'])
|
StreamingCommunity/run.py
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: StreamingCommunity
|
|
3
|
-
Version: 2.0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 2.0.5
|
|
4
|
+
Summary: UNKNOWN
|
|
5
5
|
Home-page: https://github.com/Lovi-0/StreamingCommunity
|
|
6
6
|
Author: Lovi-0
|
|
7
|
+
License: UNKNOWN
|
|
7
8
|
Project-URL: Bug Reports, https://github.com/Lovi-0/StreamingCommunity/issues
|
|
8
9
|
Project-URL: Source, https://github.com/Lovi-0/StreamingCommunity
|
|
9
|
-
Keywords:
|
|
10
|
+
Keywords: streaming community
|
|
11
|
+
Platform: UNKNOWN
|
|
10
12
|
Requires-Python: >=3.8
|
|
11
13
|
Description-Content-Type: text/markdown
|
|
12
|
-
License-File: LICENSE
|
|
13
14
|
Requires-Dist: httpx
|
|
14
|
-
Requires-Dist: cffi
|
|
15
15
|
Requires-Dist: bs4
|
|
16
16
|
Requires-Dist: rich
|
|
17
17
|
Requires-Dist: tqdm
|
|
@@ -255,7 +255,7 @@ The configuration file is divided into several main sections:
|
|
|
255
255
|
* `%(episode)` : Is the number of the episode
|
|
256
256
|
* `%(episode_name)` : Is the name of the episode
|
|
257
257
|
`<br/><br/>`
|
|
258
|
-
|
|
258
|
+
|
|
259
259
|
- `not_close`: If true, continues running after downloading
|
|
260
260
|
|
|
261
261
|
### qBittorrent Configuration
|
|
@@ -359,10 +359,8 @@ forced-ita hin - Hindi pol - Polish tur - Turkish
|
|
|
359
359
|
|
|
360
360
|
<br>
|
|
361
361
|
|
|
362
|
-
|
|
363
362
|
# COMMAND
|
|
364
363
|
|
|
365
|
-
|
|
366
364
|
- Download a specific season by entering its number.
|
|
367
365
|
* **Example:** `1` will download *Season 1* only.
|
|
368
366
|
|
|
@@ -433,7 +431,7 @@ The `run-container` command mounts also the `config.json` file, so any change to
|
|
|
433
431
|
|
|
434
432
|
# To Do
|
|
435
433
|
|
|
436
|
-
-
|
|
434
|
+
- Finish [website API](https://github.com/Lovi-0/StreamingCommunity/tree/test_gui_1)
|
|
437
435
|
|
|
438
436
|
# Contributing
|
|
439
437
|
|
|
@@ -454,3 +452,5 @@ This software is provided "as is", without warranty of any kind, express or impl
|
|
|
454
452
|
<a href="https://github.com/Lovi-0/StreamingCommunity/graphs/contributors" alt="View Contributors">
|
|
455
453
|
<img src="https://contrib.rocks/image?repo=Lovi-0/StreamingCommunity&max=1000&columns=10" alt="Contributors" />
|
|
456
454
|
</a>
|
|
455
|
+
|
|
456
|
+
|
|
@@ -1,92 +1,92 @@
|
|
|
1
1
|
StreamingCommunity/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
StreamingCommunity/run.py,sha256=
|
|
2
|
+
StreamingCommunity/run.py,sha256=bi57FYyAS49NCXObiJpwunGbnpRzXG0EpLjBOq86hjQ,6418
|
|
3
3
|
StreamingCommunity/Api/Player/ddl.py,sha256=cRPb3sfX5o_rkJ33qe7rDGmlgRgYMr0LTmdZLCsbO90,2409
|
|
4
4
|
StreamingCommunity/Api/Player/maxstream.py,sha256=0NwzyTFrIWC21xZytmg4Pl_ShAPfqIOXNU6XpEy9KuY,5304
|
|
5
5
|
StreamingCommunity/Api/Player/supervideo.py,sha256=gNoAo_LVZWycfIzmK28erWRTXwNDV6FKJSLJ2Lrw_Ok,6118
|
|
6
6
|
StreamingCommunity/Api/Player/vixcloud.py,sha256=pTl60wxSQmyCWjhv1RfHST_hacnwT3N9WnYnUJwKank,9498
|
|
7
7
|
StreamingCommunity/Api/Player/Helper/Vixcloud/js_parser.py,sha256=HniFPpcF1qtMAT8z5bf4noHJO85x8wRke6KUxB5r-F0,4438
|
|
8
8
|
StreamingCommunity/Api/Player/Helper/Vixcloud/util.py,sha256=4DomUZ6qg_GGCIfQ38-CKrO24zyzm8voJgv8OiMxDbw,5257
|
|
9
|
-
StreamingCommunity/Api/Site/1337xx/__init__.py,sha256=
|
|
9
|
+
StreamingCommunity/Api/Site/1337xx/__init__.py,sha256=gZFNPJahddRTELSdO4_-yt50wl0cZRDuxdRqIhYzSiQ,1219
|
|
10
10
|
StreamingCommunity/Api/Site/1337xx/costant.py,sha256=3jDI0qlysmtfiZIAciNA-pBLSza5GBYKZlONyMpb-FI,451
|
|
11
11
|
StreamingCommunity/Api/Site/1337xx/site.py,sha256=Cn6DC5Od89f8xj22x2NPUgAPlBmWEgXG5J3Aa810Pok,2689
|
|
12
12
|
StreamingCommunity/Api/Site/1337xx/title.py,sha256=lGJCxmlWvSWtxn69ClOCA4e_zbtHk_otHV7DiSaKcLo,1980
|
|
13
|
-
StreamingCommunity/Api/Site/altadefinizione/__init__.py,sha256=
|
|
13
|
+
StreamingCommunity/Api/Site/altadefinizione/__init__.py,sha256=sTUnun_Ew5YrxmpYZvwHV_qj-NIYTOpJOujwN0O7iU8,1210
|
|
14
14
|
StreamingCommunity/Api/Site/altadefinizione/costant.py,sha256=bxFBx_dljZM8x4nxig8pg35ylDQnR4NNr2SV3_S7Lg0,451
|
|
15
15
|
StreamingCommunity/Api/Site/altadefinizione/film.py,sha256=aKVnn-7xdxnWJZvkDZwbxVXP-vmZ4RN3jIIBivEAcY0,2253
|
|
16
16
|
StreamingCommunity/Api/Site/altadefinizione/site.py,sha256=JqNiP4Rqmn7zLALKBIAv0U_KY6uLYvGgg4_6Dw77SKU,2883
|
|
17
|
-
StreamingCommunity/Api/Site/animeunity/__init__.py,sha256=
|
|
17
|
+
StreamingCommunity/Api/Site/animeunity/__init__.py,sha256=rcG40DnZPn3vvniohYo5SgyGNTZY24XXfV5bDAEpTWE,1300
|
|
18
18
|
StreamingCommunity/Api/Site/animeunity/costant.py,sha256=bxFBx_dljZM8x4nxig8pg35ylDQnR4NNr2SV3_S7Lg0,451
|
|
19
19
|
StreamingCommunity/Api/Site/animeunity/film_serie.py,sha256=clYVjrZRKjWRnJCAvsLEEAfKMtPN9s7pgCgAx64eJuU,4162
|
|
20
20
|
StreamingCommunity/Api/Site/animeunity/site.py,sha256=WYfPtlaInQuV98Vc7CEFeRSri3EduC9ix6niVXJTnw4,5306
|
|
21
21
|
StreamingCommunity/Api/Site/animeunity/util/ScrapeSerie.py,sha256=Gv0xvTJMHru3YExAqQ-ZFfwIxs_227mB3d0JpsI3oAY,2983
|
|
22
|
-
StreamingCommunity/Api/Site/cb01new/__init__.py,sha256=
|
|
22
|
+
StreamingCommunity/Api/Site/cb01new/__init__.py,sha256=ClPCBPR6dCdVIZvXknUNum86AOkxCycIKzNl4ZJGVPk,1230
|
|
23
23
|
StreamingCommunity/Api/Site/cb01new/costant.py,sha256=EcUm_oH28CGbi5Dm2sBBAHJ1ywzn6R6mwk-Rx8mj7aQ,453
|
|
24
24
|
StreamingCommunity/Api/Site/cb01new/film.py,sha256=uc_SCS645rtwyYCofnzMj7_xc5tna1R4-hHdFHmo9T8,2268
|
|
25
25
|
StreamingCommunity/Api/Site/cb01new/site.py,sha256=dDv9P9GWtL07eygwndrPsPVmPd0kueeAn2vLPw4vHCc,2153
|
|
26
|
-
StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py,sha256=
|
|
26
|
+
StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py,sha256=3vjwaEXlnrDAProWxyWpkN2Zg528XnpNwUrOvyTHPTY,1374
|
|
27
27
|
StreamingCommunity/Api/Site/ddlstreamitaly/costant.py,sha256=r-rc_9dIFkfFZwJmY3noG1LgkKgHDxEEzQY-8aWj3dI,515
|
|
28
|
-
StreamingCommunity/Api/Site/ddlstreamitaly/series.py,sha256=
|
|
28
|
+
StreamingCommunity/Api/Site/ddlstreamitaly/series.py,sha256=QMXO7312R3M6HylF-3Y_SY7MQVgZLBq0dcSb10eNROk,4267
|
|
29
29
|
StreamingCommunity/Api/Site/ddlstreamitaly/site.py,sha256=0UfXLlABh7-ejU2cRTVsWPAPLaARbF6xu1PV4kyZvG4,2903
|
|
30
30
|
StreamingCommunity/Api/Site/ddlstreamitaly/util/ScrapeSerie.py,sha256=f7uA3VTANmWnLvYhjxMjzKRxU6FTgH2H8MSntWgc8jw,2652
|
|
31
|
-
StreamingCommunity/Api/Site/guardaserie/__init__.py,sha256=
|
|
31
|
+
StreamingCommunity/Api/Site/guardaserie/__init__.py,sha256=1NUo7z1OqDQ-DmXj6Db303SeC-0OQ_ElKcxo73JbpOM,1221
|
|
32
32
|
StreamingCommunity/Api/Site/guardaserie/costant.py,sha256=OOxqZVqlZ9XcdrOLLOz4U7MA2SrRbZ_qvvlGpuQzczo,453
|
|
33
|
-
StreamingCommunity/Api/Site/guardaserie/series.py,sha256=
|
|
33
|
+
StreamingCommunity/Api/Site/guardaserie/series.py,sha256=6qaeIYZrk8b1nC-B0D0Nkn3Zi1YpGTsXrX5ZMhAqO-c,6881
|
|
34
34
|
StreamingCommunity/Api/Site/guardaserie/site.py,sha256=2f_Q3D25bf0vNAx0XgPRTcay0QeWOm8espzDo9Lp3EA,2632
|
|
35
35
|
StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py,sha256=Os4lKYQB8PT1b-jzozRIpdovGh4gUQuXrPEHyLriYio,3399
|
|
36
|
-
StreamingCommunity/Api/Site/ilcorsaronero/__init__.py,sha256=
|
|
36
|
+
StreamingCommunity/Api/Site/ilcorsaronero/__init__.py,sha256=JBvYZqLlk5nckjYRApX5AZ58OfxF-yTTu6SE54F49Ms,1248
|
|
37
37
|
StreamingCommunity/Api/Site/ilcorsaronero/costant.py,sha256=3jDI0qlysmtfiZIAciNA-pBLSza5GBYKZlONyMpb-FI,451
|
|
38
38
|
StreamingCommunity/Api/Site/ilcorsaronero/site.py,sha256=dkNOUZanaPtQJIrOskzOC4cEOHFG3gnwH5RFYaw_ERU,1812
|
|
39
39
|
StreamingCommunity/Api/Site/ilcorsaronero/title.py,sha256=5cPMiBWoZPUerS2aI_6qO14hA14ztW-cWkaIInUYBlI,1377
|
|
40
|
-
StreamingCommunity/Api/Site/ilcorsaronero/util/ilCorsarScraper.py,sha256=
|
|
41
|
-
StreamingCommunity/Api/Site/mostraguarda/__init__.py,sha256=
|
|
40
|
+
StreamingCommunity/Api/Site/ilcorsaronero/util/ilCorsarScraper.py,sha256=PYV_OOU8KyO9ysVchfuFNfikCGmyE-NHknhGB64XOsw,5258
|
|
41
|
+
StreamingCommunity/Api/Site/mostraguarda/__init__.py,sha256=0HRPWgz4pFpVR6_mUQy8SLGcUgQdseVrH-QqnfKlxW4,1177
|
|
42
42
|
StreamingCommunity/Api/Site/mostraguarda/costant.py,sha256=bxFBx_dljZM8x4nxig8pg35ylDQnR4NNr2SV3_S7Lg0,451
|
|
43
|
-
StreamingCommunity/Api/Site/mostraguarda/film.py,sha256=
|
|
44
|
-
StreamingCommunity/Api/Site/streamingcommunity/__init__.py,sha256=
|
|
43
|
+
StreamingCommunity/Api/Site/mostraguarda/film.py,sha256=R93viJqWom4xMsvG-yZqi36bnTlxkSGyW5AbmR0vRyE,3053
|
|
44
|
+
StreamingCommunity/Api/Site/streamingcommunity/__init__.py,sha256=TF8q_xHPXmwEkhDj3MS8xOVHgRQRmK-WNo0pQHChV74,1487
|
|
45
45
|
StreamingCommunity/Api/Site/streamingcommunity/costant.py,sha256=XEksndtxSC8As-r15f0hRuFAum-CjseegrVjFYOc_XY,436
|
|
46
|
-
StreamingCommunity/Api/Site/streamingcommunity/film.py,sha256=
|
|
47
|
-
StreamingCommunity/Api/Site/streamingcommunity/series.py,sha256
|
|
46
|
+
StreamingCommunity/Api/Site/streamingcommunity/film.py,sha256=dI6NHtsBitVU8rJsdwo9u_WXSoG-uWrFDMWxqYvNmPQ,2339
|
|
47
|
+
StreamingCommunity/Api/Site/streamingcommunity/series.py,sha256=-oEprxWV55XWK2fz4CyP-KtdGt68WhVZGZsyiQ6KkTY,7393
|
|
48
48
|
StreamingCommunity/Api/Site/streamingcommunity/site.py,sha256=6nW5USa6X5xiZvEHMRKipjZzsV__f8iV1Hx8dqEW2w0,3902
|
|
49
49
|
StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py,sha256=sNK61c1V6ULTk2kJFBOq3q_jzv281gfE52cjh7Oufsg,4313
|
|
50
50
|
StreamingCommunity/Api/Template/__init__.py,sha256=lhVpudlILM1L9iBZ_7vfYwgKyrdR1OWeLvk364-fJpU,48
|
|
51
51
|
StreamingCommunity/Api/Template/site.py,sha256=Xk_XpZ97xDUhGeiwo-52b7h1kOVsRQVdCqoXcMs2zHk,2995
|
|
52
52
|
StreamingCommunity/Api/Template/Class/SearchType.py,sha256=lJ054oi7baFU8HoXzq_mO4foRz0wtF9GIGOOFS8VpmY,2632
|
|
53
53
|
StreamingCommunity/Api/Template/Util/__init__.py,sha256=e-206LT2iAnL9I21fR_1FRm8F3WsbBRrUmraPP2Xfzo,202
|
|
54
|
-
StreamingCommunity/Api/Template/Util/get_domain.py,sha256=
|
|
54
|
+
StreamingCommunity/Api/Template/Util/get_domain.py,sha256=HFV5R-D6Nu0yhP19PCRSZOSFvvNq5FKWg9lHd1juTxI,6479
|
|
55
55
|
StreamingCommunity/Api/Template/Util/manage_ep.py,sha256=TkeRH8120Bsu7gTOJb2GRdZI6LZmgYCD60tQVruwkvI,6697
|
|
56
56
|
StreamingCommunity/Api/Template/Util/recall_search.py,sha256=N-h00R2S8rQud3mNtUmaakK_8d9e5YxgHv4QwcJ4aZw,1184
|
|
57
57
|
StreamingCommunity/Lib/Downloader/__init__.py,sha256=vAn-rpmlSmojvz4Quv47A5HLq4yBR_7noy4r6hqk0OQ,144
|
|
58
|
-
StreamingCommunity/Lib/Downloader/HLS/downloader.py,sha256=
|
|
58
|
+
StreamingCommunity/Lib/Downloader/HLS/downloader.py,sha256=_3kXihkLDChHIXlRzgN0o0prgyduONx7K7DE0pJm23Y,39919
|
|
59
59
|
StreamingCommunity/Lib/Downloader/HLS/proxyes.py,sha256=Mrs5Mr6ATv-6BUS7CLcZjw3JNH7g_XOz7boeB1oQAQ8,3385
|
|
60
|
-
StreamingCommunity/Lib/Downloader/HLS/segments.py,sha256=
|
|
60
|
+
StreamingCommunity/Lib/Downloader/HLS/segments.py,sha256=D7XwnranQP24zy5Ht3ctBIJ7NVBLt5jB2-zT3SHeNH0,22461
|
|
61
61
|
StreamingCommunity/Lib/Downloader/MP4/downloader.py,sha256=gWhNHhL5f3bXAGUaOlH2wCwda0sKi-R9s6PxOIp7LrU,6028
|
|
62
62
|
StreamingCommunity/Lib/Downloader/TOR/downloader.py,sha256=67RDi3Er5xpoHFIn11sGcCB1xgIEGE-Nhn9wqDfmGak,11617
|
|
63
63
|
StreamingCommunity/Lib/FFmpeg/__init__.py,sha256=0KehwaTYL72PJaJJo2fzveGgc9F2-abIk7w6gXsfX1w,135
|
|
64
|
-
StreamingCommunity/Lib/FFmpeg/capture.py,sha256=
|
|
64
|
+
StreamingCommunity/Lib/FFmpeg/capture.py,sha256=YptVE5Ff6-hJXmsARxtXclWXmVl16y6QLR-VMybBaEg,5601
|
|
65
65
|
StreamingCommunity/Lib/FFmpeg/command.py,sha256=EfI3Q4tQkV7XmKWx0eBDnuIhn0O_lfPiAiI5IET3nLk,10289
|
|
66
|
-
StreamingCommunity/Lib/FFmpeg/util.py,sha256=
|
|
66
|
+
StreamingCommunity/Lib/FFmpeg/util.py,sha256=QTtBaFdrUa5CFSPq1ycr3_zw81A7zVRmQN7FoxUwuR8,8341
|
|
67
67
|
StreamingCommunity/Lib/M3U8/__init__.py,sha256=sD2VIslF43OrudA1r-9xkSfUvSblr1LOZpaIM89F6M4,175
|
|
68
|
-
StreamingCommunity/Lib/M3U8/decryptor.py,sha256=
|
|
69
|
-
StreamingCommunity/Lib/M3U8/estimator.py,sha256=
|
|
68
|
+
StreamingCommunity/Lib/M3U8/decryptor.py,sha256=q6GVlM9UtiR_Yop7-MDUTShAESc66LX2k2Eqvj3vge8,6473
|
|
69
|
+
StreamingCommunity/Lib/M3U8/estimator.py,sha256=_tYTCjWmd4zlmywozricbUo2tDk81N2UVMh5BRdwRmo,7918
|
|
70
70
|
StreamingCommunity/Lib/M3U8/parser.py,sha256=xmyCU4AYGIOUheTZ4OBPmQl_R4dJ3Y72i9UUa6_aErA,23553
|
|
71
71
|
StreamingCommunity/Lib/M3U8/url_fixer.py,sha256=6NVKhc8R5CqarDM5TLWy6QU05qXPouW31gyilQszwbI,1675
|
|
72
72
|
StreamingCommunity/Lib/TMBD/__init__.py,sha256=b3yUqfeBFpnKH-MScrZ3r90cpXc2ufCC-El3whK1zk8,55
|
|
73
73
|
StreamingCommunity/Lib/TMBD/obj_tmbd.py,sha256=HEL3jAqUYtVgX7GCaw60EAD3JGvEJLOQXfD6lRYEjxA,1968
|
|
74
74
|
StreamingCommunity/Lib/TMBD/tmdb.py,sha256=3UO_0uzi8xtrokX8Y_vO8vx_V8XHSkOVCqtgT-289GM,12000
|
|
75
|
-
StreamingCommunity/Upload/update.py,sha256=
|
|
76
|
-
StreamingCommunity/Upload/version.py,sha256=
|
|
75
|
+
StreamingCommunity/Upload/update.py,sha256=ly2E4gf5RCkhlHvDyVNP57jkGGH41V-UHLYwBwgypP4,2324
|
|
76
|
+
StreamingCommunity/Upload/version.py,sha256=NnaKT3FVdTBYEFQQvkTJdUwaLcGasNTNHck7hLJ1A3g,175
|
|
77
77
|
StreamingCommunity/Util/_jsonConfig.py,sha256=CLvk6HWxUklZztoy55SzEOvdsbNo-pFcVQVanwixXCc,7001
|
|
78
78
|
StreamingCommunity/Util/call_stack.py,sha256=zuYbO8dV8bCa7fCdtaKQYuheA5K7BkTm3Oj8JA6GCpM,1417
|
|
79
79
|
StreamingCommunity/Util/color.py,sha256=1iQUf5xDp5XKKbXl9MvKEXJvv44Zf0P4J2Nu5ATIId8,479
|
|
80
80
|
StreamingCommunity/Util/console.py,sha256=bXP_iibXMpRIJ_zs03xFmSbkvMP3SguIuEUJZovoOqw,230
|
|
81
|
-
StreamingCommunity/Util/ffmpeg_installer.py,sha256=
|
|
81
|
+
StreamingCommunity/Util/ffmpeg_installer.py,sha256=fDV45sb17KNO7DCzMfTtV1fKsrQUNNMInOTVtL_BokE,13907
|
|
82
82
|
StreamingCommunity/Util/headers.py,sha256=r5hK8HVF-y6dQzCQpDnpbDGP02n29OYlzgaZAorcmG0,4565
|
|
83
83
|
StreamingCommunity/Util/logger.py,sha256=5XmFquGYt4FjvKNyYKa21mLLKARmzGWk-mBo05ezlHQ,1914
|
|
84
84
|
StreamingCommunity/Util/message.py,sha256=pgUf50z4kSJjKHBlBKn5bd26xlAAEvlLiMs9dvcvJ_s,3675
|
|
85
|
-
StreamingCommunity/Util/os.py,sha256=
|
|
85
|
+
StreamingCommunity/Util/os.py,sha256=K8bgAxFq8pPFaMQPfeTSQ_E9aEERN7pwWB_XEXBC84Q,19429
|
|
86
86
|
StreamingCommunity/Util/table.py,sha256=lKCgvrtfODuQY5dFJqlNUYL2aFzfij-efS5oGv84E44,8508
|
|
87
|
-
StreamingCommunity-2.0.
|
|
88
|
-
StreamingCommunity-2.0.
|
|
89
|
-
StreamingCommunity-2.0.
|
|
90
|
-
StreamingCommunity-2.0.
|
|
91
|
-
StreamingCommunity-2.0.
|
|
92
|
-
StreamingCommunity-2.0.
|
|
87
|
+
StreamingCommunity-2.0.5.dist-info/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
88
|
+
StreamingCommunity-2.0.5.dist-info/METADATA,sha256=tuuAltpAwEyHXXoonOx6SwLg98u-CWdVsjnn6MYTwCk,13406
|
|
89
|
+
StreamingCommunity-2.0.5.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
90
|
+
StreamingCommunity-2.0.5.dist-info/entry_points.txt,sha256=-iQU6qfeHFwauAg4iZhifWhNZAkiV-x3XuEauo_EjUc,68
|
|
91
|
+
StreamingCommunity-2.0.5.dist-info/top_level.txt,sha256=YsOcxKP-WOhWpIWgBlh0coll9XUx7aqmRPT7kmt3fH0,19
|
|
92
|
+
StreamingCommunity-2.0.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|