StreamingCommunity 3.3.1__py3-none-any.whl → 3.3.3__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/mediasetinfinity/util/get_license.py +28 -1
- StreamingCommunity/Api/Site/raiplay/site.py +6 -4
- StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py +6 -2
- StreamingCommunity/Api/Site/streamingcommunity/site.py +0 -3
- StreamingCommunity/Api/Site/streamingwatch/site.py +0 -3
- StreamingCommunity/Lib/Downloader/DASH/cdm_helpher.py +1 -18
- StreamingCommunity/Lib/Downloader/DASH/downloader.py +18 -14
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +22 -10
- StreamingCommunity/Lib/Downloader/HLS/segments.py +126 -72
- StreamingCommunity/Lib/M3U8/decryptor.py +0 -14
- StreamingCommunity/Lib/M3U8/estimator.py +44 -34
- StreamingCommunity/Lib/TMBD/tmdb.py +0 -12
- StreamingCommunity/Upload/update.py +1 -1
- StreamingCommunity/Upload/version.py +1 -1
- StreamingCommunity/Util/{bento4_installer.py → installer/bento4_install.py} +56 -44
- StreamingCommunity/Util/installer/binary_paths.py +83 -0
- StreamingCommunity/Util/installer/device_install.py +133 -0
- StreamingCommunity/Util/{ffmpeg_installer.py → installer/ffmpeg_install.py} +100 -138
- StreamingCommunity/Util/logger.py +3 -8
- StreamingCommunity/Util/os.py +34 -150
- StreamingCommunity/run.py +2 -3
- {streamingcommunity-3.3.1.dist-info → streamingcommunity-3.3.3.dist-info}/METADATA +295 -532
- {streamingcommunity-3.3.1.dist-info → streamingcommunity-3.3.3.dist-info}/RECORD +27 -28
- StreamingCommunity/Api/Player/ddl.py +0 -82
- StreamingCommunity/Api/Player/maxstream.py +0 -141
- StreamingCommunity/Api/Player/mixdrop.py +0 -146
- {streamingcommunity-3.3.1.dist-info → streamingcommunity-3.3.3.dist-info}/WHEEL +0 -0
- {streamingcommunity-3.3.1.dist-info → streamingcommunity-3.3.3.dist-info}/entry_points.txt +0 -0
- {streamingcommunity-3.3.1.dist-info → streamingcommunity-3.3.3.dist-info}/licenses/LICENSE +0 -0
- {streamingcommunity-3.3.1.dist-info → streamingcommunity-3.3.3.dist-info}/top_level.txt +0 -0
|
@@ -9,7 +9,12 @@ import xml.etree.ElementTree as ET
|
|
|
9
9
|
# External library
|
|
10
10
|
import httpx
|
|
11
11
|
from rich.console import Console
|
|
12
|
-
|
|
12
|
+
try:
|
|
13
|
+
from seleniumbase import Driver
|
|
14
|
+
SELENIUMBASE_AVAILABLE = True
|
|
15
|
+
except ImportError:
|
|
16
|
+
SELENIUMBASE_AVAILABLE = False
|
|
17
|
+
Driver = None
|
|
13
18
|
|
|
14
19
|
|
|
15
20
|
# Internal utilities
|
|
@@ -40,6 +45,17 @@ def save_network_data(data):
|
|
|
40
45
|
|
|
41
46
|
|
|
42
47
|
def generate_betoken(username: str, password: str, sleep_action: float = 1.0) -> str:
|
|
48
|
+
"""Generate beToken using browser automation"""
|
|
49
|
+
|
|
50
|
+
if not SELENIUMBASE_AVAILABLE:
|
|
51
|
+
console.print("[red]Error: seleniumbase is not installed. Cannot perform browser login.")
|
|
52
|
+
console.print("[yellow]Install seleniumbase with: pip install seleniumbase")
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
if not Driver:
|
|
56
|
+
console.print("[red]Error: seleniumbase Driver is not available.")
|
|
57
|
+
return None
|
|
58
|
+
|
|
43
59
|
driver = Driver(uc=True, uc_cdp_events=True, incognito=True, headless=True)
|
|
44
60
|
|
|
45
61
|
try:
|
|
@@ -155,6 +171,7 @@ def generate_betoken(username: str, password: str, sleep_action: float = 1.0) ->
|
|
|
155
171
|
finally:
|
|
156
172
|
driver.quit()
|
|
157
173
|
|
|
174
|
+
|
|
158
175
|
def get_bearer_token():
|
|
159
176
|
"""
|
|
160
177
|
Gets the BEARER_TOKEN for authentication.
|
|
@@ -173,6 +190,11 @@ def get_bearer_token():
|
|
|
173
190
|
password = config_manager.get_dict("SITE_LOGIN", "mediasetinfinity").get("password", "")
|
|
174
191
|
|
|
175
192
|
if username and password:
|
|
193
|
+
if not SELENIUMBASE_AVAILABLE:
|
|
194
|
+
console.print("[yellow]Warning: seleniumbase not available. Cannot perform automatic login.")
|
|
195
|
+
console.print("[yellow]Please manually obtain beToken and set it in config.")
|
|
196
|
+
return config_manager.get_dict("SITE_LOGIN", "mediasetinfinity")["beToken"]
|
|
197
|
+
|
|
176
198
|
beToken = generate_betoken(username, password)
|
|
177
199
|
|
|
178
200
|
if beToken is not None:
|
|
@@ -187,6 +209,7 @@ def get_bearer_token():
|
|
|
187
209
|
|
|
188
210
|
return config_manager.get_dict("SITE_LOGIN", "mediasetinfinity")["beToken"]
|
|
189
211
|
|
|
212
|
+
|
|
190
213
|
def get_playback_url(BEARER_TOKEN, CONTENT_ID):
|
|
191
214
|
"""
|
|
192
215
|
Gets the playback URL for the specified content.
|
|
@@ -231,6 +254,7 @@ def get_playback_url(BEARER_TOKEN, CONTENT_ID):
|
|
|
231
254
|
except Exception as e:
|
|
232
255
|
raise RuntimeError(f"Failed to get playback URL: {e}")
|
|
233
256
|
|
|
257
|
+
|
|
234
258
|
def parse_tracking_data(tracking_value):
|
|
235
259
|
"""
|
|
236
260
|
Parses the trackingData string into a dictionary.
|
|
@@ -243,6 +267,7 @@ def parse_tracking_data(tracking_value):
|
|
|
243
267
|
"""
|
|
244
268
|
return dict(item.split('=', 1) for item in tracking_value.split('|') if '=' in item)
|
|
245
269
|
|
|
270
|
+
|
|
246
271
|
def parse_smil_for_tracking_and_video(smil_xml):
|
|
247
272
|
"""
|
|
248
273
|
Extracts all video_src and trackingData pairs from the SMIL.
|
|
@@ -283,6 +308,7 @@ def parse_smil_for_tracking_and_video(smil_xml):
|
|
|
283
308
|
|
|
284
309
|
return results
|
|
285
310
|
|
|
311
|
+
|
|
286
312
|
def get_tracking_info(BEARER_TOKEN, PLAYBACK_JSON):
|
|
287
313
|
"""
|
|
288
314
|
Retrieves tracking information from the playback JSON.
|
|
@@ -326,6 +352,7 @@ def get_tracking_info(BEARER_TOKEN, PLAYBACK_JSON):
|
|
|
326
352
|
except Exception:
|
|
327
353
|
return None
|
|
328
354
|
|
|
355
|
+
|
|
329
356
|
def generate_license_url(BEARER_TOKEN, tracking_info):
|
|
330
357
|
"""
|
|
331
358
|
Generates the URL to obtain the Widevine license.
|
|
@@ -42,7 +42,7 @@ def determine_media_type(item):
|
|
|
42
42
|
|
|
43
43
|
scraper = GetSerieInfo(program_name)
|
|
44
44
|
scraper.collect_info_title()
|
|
45
|
-
return
|
|
45
|
+
return scraper.prog_tipology, scraper.prog_description, scraper.prog_year
|
|
46
46
|
|
|
47
47
|
except Exception as e:
|
|
48
48
|
console.print(f"[red]Error determining media type: {e}[/red]")
|
|
@@ -91,18 +91,20 @@ def title_search(query: str) -> int:
|
|
|
91
91
|
return 0
|
|
92
92
|
|
|
93
93
|
# Limit to only 15 results for performance
|
|
94
|
-
data = response.json().get('agg').get('titoli').get('cards')
|
|
95
|
-
data = data[:15] if len(data) > 15 else data
|
|
94
|
+
data = response.json().get('agg').get('titoli').get('cards')[:15]
|
|
96
95
|
|
|
97
96
|
# Process each item and add to media manager
|
|
98
97
|
for item in data:
|
|
98
|
+
media_type, prog_description, prog_year = determine_media_type(item)
|
|
99
99
|
media_search_manager.add_media({
|
|
100
100
|
'id': item.get('id', ''),
|
|
101
101
|
'name': item.get('titolo', ''),
|
|
102
|
-
'type':
|
|
102
|
+
'type': media_type,
|
|
103
103
|
'path_id': item.get('path_id', ''),
|
|
104
104
|
'url': f"https://www.raiplay.it{item.get('url', '')}",
|
|
105
105
|
'image': f"https://www.raiplay.it{item.get('immagine', '')}",
|
|
106
|
+
'desc': prog_description,
|
|
107
|
+
'year': prog_year
|
|
106
108
|
})
|
|
107
109
|
|
|
108
110
|
return media_search_manager.get_length()
|
|
@@ -23,6 +23,9 @@ class GetSerieInfo:
|
|
|
23
23
|
self.base_url = "https://www.raiplay.it"
|
|
24
24
|
self.program_name = program_name
|
|
25
25
|
self.series_name = program_name
|
|
26
|
+
self.prog_tipology = None
|
|
27
|
+
self.prog_description = None
|
|
28
|
+
self.prog_year = None
|
|
26
29
|
self.seasons_manager = SeasonManager()
|
|
27
30
|
|
|
28
31
|
def collect_info_title(self) -> None:
|
|
@@ -38,6 +41,9 @@ class GetSerieInfo:
|
|
|
38
41
|
|
|
39
42
|
response.raise_for_status()
|
|
40
43
|
json_data = response.json()
|
|
44
|
+
self.prog_tipology = "tv" if "tv" in json_data.get('track_info').get('typology') else "film"
|
|
45
|
+
self.prog_description = json_data.get('program_info', '').get('vanity', '')
|
|
46
|
+
self.prog_year = json_data.get('program_info', '').get('year', '')
|
|
41
47
|
|
|
42
48
|
# Look for seasons in the 'blocks' property
|
|
43
49
|
for block in json_data.get('blocks', []):
|
|
@@ -59,8 +65,6 @@ class GetSerieInfo:
|
|
|
59
65
|
for season_set in block.get('sets', []):
|
|
60
66
|
self._add_season(season_set, block.get('id'))
|
|
61
67
|
|
|
62
|
-
except httpx.HTTPError as e:
|
|
63
|
-
logging.error(f"Error collecting series info: {e}")
|
|
64
68
|
except Exception as e:
|
|
65
69
|
logging.error(f"Unexpected error collecting series info: {e}")
|
|
66
70
|
|
|
@@ -59,9 +59,6 @@ def title_search(query: str) -> int:
|
|
|
59
59
|
version = json.loads(soup.find('div', {'id': "app"}).get("data-page"))['version']
|
|
60
60
|
|
|
61
61
|
except Exception as e:
|
|
62
|
-
if "WinError" in str(e) or "Errno" in str(e):
|
|
63
|
-
console.print("\n[bold yellow]Please make sure you have enabled and configured a valid proxy.[/bold yellow]")
|
|
64
|
-
|
|
65
62
|
console.print(f"[red]Site: {site_constant.SITE_NAME} version, request error: {e}")
|
|
66
63
|
return 0
|
|
67
64
|
|
|
@@ -86,9 +86,6 @@ def title_search(query: str) -> int:
|
|
|
86
86
|
soup = BeautifulSoup(response.text, 'html.parser')
|
|
87
87
|
|
|
88
88
|
except Exception as e:
|
|
89
|
-
if "WinError" in str(e) or "Errno" in str(e):
|
|
90
|
-
console.print("\n[bold yellow]Please make sure you have enabled and configured a valid proxy.[/bold yellow]")
|
|
91
|
-
|
|
92
89
|
console.print(f"[red]Site: {site_constant.SITE_NAME}, request search error: {e}")
|
|
93
90
|
return 0
|
|
94
91
|
|
|
@@ -29,29 +29,11 @@ def get_widevine_keys(pssh, license_url, cdm_device_path, headers=None, payload=
|
|
|
29
29
|
Returns:
|
|
30
30
|
list: List of dicts {'kid': ..., 'key': ...} (only CONTENT keys) or None if error.
|
|
31
31
|
"""
|
|
32
|
-
|
|
33
|
-
# Check if PSSH is a valid base64 string
|
|
34
|
-
try:
|
|
35
|
-
base64.b64decode(pssh)
|
|
36
|
-
except Exception:
|
|
37
|
-
console.print("[bold red] Invalid PSSH base64 string.[/bold red]")
|
|
38
|
-
return None
|
|
39
|
-
|
|
40
32
|
try:
|
|
41
33
|
device = Device.load(cdm_device_path)
|
|
42
34
|
cdm = Cdm.from_device(device)
|
|
43
35
|
session_id = cdm.open()
|
|
44
36
|
|
|
45
|
-
# Display security level in a more readable format
|
|
46
|
-
security_levels = {1: "L1 (Hardware)", 2: "L2 (Software)", 3: "L3 (Software)"}
|
|
47
|
-
security_level_str = security_levels.get(device.security_level, 'Unknown')
|
|
48
|
-
logging.info(f"Security Level: {security_level_str}")
|
|
49
|
-
|
|
50
|
-
# Only allow L3, otherwise warn and exit
|
|
51
|
-
if device.security_level != 3:
|
|
52
|
-
console.print(f"[bold yellow]⚠️ Only L3 (Software) security level is supported. Current: {security_level_str}[/bold yellow]")
|
|
53
|
-
return None
|
|
54
|
-
|
|
55
37
|
try:
|
|
56
38
|
challenge = cdm.get_license_challenge(session_id, PSSH(pssh))
|
|
57
39
|
req_headers = headers or {}
|
|
@@ -108,6 +90,7 @@ def get_widevine_keys(pssh, license_url, cdm_device_path, headers=None, payload=
|
|
|
108
90
|
content_keys = []
|
|
109
91
|
for key in cdm.get_keys(session_id):
|
|
110
92
|
if key.type == "CONTENT":
|
|
93
|
+
|
|
111
94
|
kid = key.kid.hex() if isinstance(key.kid, bytes) else str(key.kid)
|
|
112
95
|
key_val = key.key.hex() if isinstance(key.key, bytes) else str(key.key)
|
|
113
96
|
|
|
@@ -12,7 +12,7 @@ from rich.panel import Panel
|
|
|
12
12
|
# Internal utilities
|
|
13
13
|
from StreamingCommunity.Util.config_json import config_manager
|
|
14
14
|
from StreamingCommunity.Util.os import internet_manager
|
|
15
|
-
from ...FFmpeg import print_duration_table
|
|
15
|
+
from ...FFmpeg import print_duration_table, join_audios, join_video
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
# Logic class
|
|
@@ -174,25 +174,26 @@ class DASH_Downloader:
|
|
|
174
174
|
|
|
175
175
|
def finalize_output(self):
|
|
176
176
|
|
|
177
|
-
#
|
|
177
|
+
# Definenition of decrypted files
|
|
178
|
+
video_file = os.path.join(self.decrypted_dir, "video.mp4")
|
|
179
|
+
audio_file = os.path.join(self.decrypted_dir, "audio.mp4")
|
|
178
180
|
output_file = self.original_output_path
|
|
179
181
|
|
|
180
182
|
# Set the output file path for status tracking
|
|
181
183
|
self.output_file = output_file
|
|
182
184
|
use_shortest = False
|
|
183
185
|
|
|
184
|
-
|
|
186
|
+
if os.path.exists(video_file) and os.path.exists(audio_file):
|
|
185
187
|
audio_tracks = [{"path": audio_file}]
|
|
186
|
-
|
|
188
|
+
_, use_shortest = join_audios(video_file, audio_tracks, output_file)
|
|
187
189
|
|
|
188
190
|
elif os.path.exists(video_file):
|
|
189
|
-
|
|
190
|
-
|
|
191
|
+
_ = join_video(video_file, output_file, codec=None)
|
|
192
|
+
|
|
191
193
|
else:
|
|
192
|
-
print("Video file missing, cannot export")
|
|
194
|
+
console.print("[red]Video file missing, cannot export[/red]")
|
|
193
195
|
return None
|
|
194
|
-
|
|
195
|
-
|
|
196
|
+
|
|
196
197
|
# Handle failed sync case
|
|
197
198
|
if use_shortest:
|
|
198
199
|
new_filename = output_file.replace(".mp4", "_failed_sync.mp4")
|
|
@@ -204,18 +205,22 @@ class DASH_Downloader:
|
|
|
204
205
|
if os.path.exists(output_file):
|
|
205
206
|
file_size = internet_manager.format_file_size(os.path.getsize(output_file))
|
|
206
207
|
duration = print_duration_table(output_file, description=False, return_string=True)
|
|
208
|
+
|
|
207
209
|
panel_content = (
|
|
208
210
|
f"[cyan]File size: [bold red]{file_size}[/bold red]\n"
|
|
209
211
|
f"[cyan]Duration: [bold]{duration}[/bold]\n"
|
|
210
212
|
f"[cyan]Output: [bold]{os.path.abspath(output_file)}[/bold]"
|
|
211
213
|
)
|
|
212
|
-
|
|
214
|
+
|
|
213
215
|
console.print(Panel(
|
|
214
216
|
panel_content,
|
|
215
217
|
title=f"{os.path.basename(output_file.replace('.mp4', ''))}",
|
|
216
218
|
border_style="green"
|
|
217
219
|
))
|
|
218
220
|
|
|
221
|
+
else:
|
|
222
|
+
console.print(f"[red]Output file not found: {output_file}")
|
|
223
|
+
|
|
219
224
|
# Clean up: delete only the tmp directory, not the main directory
|
|
220
225
|
if os.path.exists(self.tmp_dir):
|
|
221
226
|
shutil.rmtree(self.tmp_dir, ignore_errors=True)
|
|
@@ -226,13 +231,12 @@ class DASH_Downloader:
|
|
|
226
231
|
|
|
227
232
|
# Check if out_path is different from the actual output directory
|
|
228
233
|
# and if it's empty, then it's safe to remove
|
|
229
|
-
if (self.out_path != output_dir and
|
|
230
|
-
os.path.exists(self.out_path) and
|
|
231
|
-
not os.listdir(self.out_path)):
|
|
234
|
+
if (self.out_path != output_dir and os.path.exists(self.out_path) and not os.listdir(self.out_path)):
|
|
232
235
|
try:
|
|
233
236
|
os.rmdir(self.out_path)
|
|
237
|
+
|
|
234
238
|
except Exception as e:
|
|
235
|
-
print(f"[
|
|
239
|
+
console.print(f"[red]Cannot remove directory {self.out_path}: {e}")
|
|
236
240
|
|
|
237
241
|
# Verify the final file exists before returning
|
|
238
242
|
if os.path.exists(output_file):
|
|
@@ -39,6 +39,7 @@ DOWNLOAD_SPECIFIC_AUDIO = config_manager.get_list('M3U8_DOWNLOAD', 'specific_lis
|
|
|
39
39
|
DOWNLOAD_SPECIFIC_SUBTITLE = config_manager.get_list('M3U8_DOWNLOAD', 'specific_list_subtitles')
|
|
40
40
|
MERGE_SUBTITLE = config_manager.get_bool('M3U8_DOWNLOAD', 'merge_subs')
|
|
41
41
|
CLEANUP_TMP = config_manager.get_bool('M3U8_DOWNLOAD', 'cleanup_tmp_folder')
|
|
42
|
+
GET_ONLY_LINK = config_manager.get_int('M3U8_DOWNLOAD', 'get_only_link')
|
|
42
43
|
FILTER_CUSTOM_RESOLUTION = str(config_manager.get('M3U8_CONVERSION', 'force_resolution')).strip().lower()
|
|
43
44
|
RETRY_LIMIT = config_manager.get_int('REQUESTS', 'max_retry')
|
|
44
45
|
MAX_TIMEOUT = config_manager.get_int("REQUESTS", "timeout")
|
|
@@ -63,7 +64,6 @@ class HLSClient:
|
|
|
63
64
|
Returns:
|
|
64
65
|
Response content/text or None if all retries fail
|
|
65
66
|
"""
|
|
66
|
-
# Use unified HTTP client (inherits timeout/verify/proxy from config)
|
|
67
67
|
client = create_client(headers=self.headers)
|
|
68
68
|
|
|
69
69
|
for attempt in range(RETRY_LIMIT):
|
|
@@ -211,10 +211,10 @@ class M3U8Manager:
|
|
|
211
211
|
# Get available subtitles and their languages
|
|
212
212
|
available_subtitles = self.parser._subtitle.get_all_uris_and_names() or []
|
|
213
213
|
available_sub_languages = [sub.get('language') for sub in available_subtitles]
|
|
214
|
-
|
|
214
|
+
|
|
215
215
|
# If "*" is in DOWNLOAD_SPECIFIC_SUBTITLE, all languages are downloadable
|
|
216
216
|
downloadable_sub_languages = available_sub_languages if "*" in DOWNLOAD_SPECIFIC_SUBTITLE else list(set(available_sub_languages) & set(DOWNLOAD_SPECIFIC_SUBTITLE))
|
|
217
|
-
|
|
217
|
+
|
|
218
218
|
if available_sub_languages:
|
|
219
219
|
console.print(
|
|
220
220
|
f"[cyan bold]Subtitle [/cyan bold] [green]Available:[/green] [purple]{', '.join(available_sub_languages)}[/purple] | "
|
|
@@ -260,7 +260,7 @@ class DownloadManager:
|
|
|
260
260
|
|
|
261
261
|
if result.get('stopped', False):
|
|
262
262
|
self.stopped = True
|
|
263
|
-
|
|
263
|
+
|
|
264
264
|
return self.stopped
|
|
265
265
|
|
|
266
266
|
def download_audio(self, audio: Dict):
|
|
@@ -304,7 +304,7 @@ class DownloadManager:
|
|
|
304
304
|
"""
|
|
305
305
|
return_stopped = False
|
|
306
306
|
video_file = os.path.join(self.temp_dir, 'video', '0.ts')
|
|
307
|
-
|
|
307
|
+
|
|
308
308
|
if not os.path.exists(video_file):
|
|
309
309
|
if self.download_video(video_url):
|
|
310
310
|
if not return_stopped:
|
|
@@ -421,8 +421,20 @@ class HLS_Downloader:
|
|
|
421
421
|
- is_master: Whether the M3U8 was a master playlist
|
|
422
422
|
Or raises an exception if there's an error
|
|
423
423
|
"""
|
|
424
|
+
|
|
425
|
+
if GET_ONLY_LINK:
|
|
426
|
+
console.print(f"URL: [bold red]{self.m3u8_url}[/bold red]")
|
|
427
|
+
return {
|
|
428
|
+
'path': None,
|
|
429
|
+
'url': self.m3u8_url,
|
|
430
|
+
'is_master': getattr(self.m3u8_manager, 'is_master', None),
|
|
431
|
+
'msg': None,
|
|
432
|
+
'error': None,
|
|
433
|
+
'stopped': True
|
|
434
|
+
}
|
|
435
|
+
|
|
424
436
|
console.print("[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
|
|
425
|
-
|
|
437
|
+
|
|
426
438
|
if TELEGRAM_BOT:
|
|
427
439
|
bot = get_bot_instance()
|
|
428
440
|
|
|
@@ -440,7 +452,7 @@ class HLS_Downloader:
|
|
|
440
452
|
if TELEGRAM_BOT:
|
|
441
453
|
bot.send_message("Contenuto già scaricato!", None)
|
|
442
454
|
return response
|
|
443
|
-
|
|
455
|
+
|
|
444
456
|
self.path_manager.setup_directories()
|
|
445
457
|
|
|
446
458
|
# Parse M3U8 and determine if it's a master playlist
|
|
@@ -524,7 +536,7 @@ class HLS_Downloader:
|
|
|
524
536
|
|
|
525
537
|
if missing_ts:
|
|
526
538
|
panel_content += f"\n{missing_info}"
|
|
527
|
-
|
|
539
|
+
|
|
528
540
|
new_filename = self.path_manager.output_path
|
|
529
541
|
if missing_ts and use_shortest:
|
|
530
542
|
new_filename = new_filename.replace(".mp4", "_failed_sync_ts.mp4")
|
|
@@ -532,7 +544,7 @@ class HLS_Downloader:
|
|
|
532
544
|
new_filename = new_filename.replace(".mp4", "_failed_ts.mp4")
|
|
533
545
|
elif use_shortest:
|
|
534
546
|
new_filename = new_filename.replace(".mp4", "_failed_sync.mp4")
|
|
535
|
-
|
|
547
|
+
|
|
536
548
|
if missing_ts or use_shortest:
|
|
537
549
|
os.rename(self.path_manager.output_path, new_filename)
|
|
538
550
|
self.path_manager.output_path = new_filename
|
|
@@ -541,4 +553,4 @@ class HLS_Downloader:
|
|
|
541
553
|
panel_content,
|
|
542
554
|
title=f"{os.path.basename(self.path_manager.output_path.replace('.mp4', ''))}",
|
|
543
555
|
border_style="green"
|
|
544
|
-
))
|
|
556
|
+
))
|