|
22 | 22 | from bs4 import BeautifulSoup |
23 | 23 | from urllib.parse import urljoin |
24 | 24 | from functools import cached_property |
25 | | -from typing import Generator, Union, Optional |
26 | 25 | from base_api.modules.config import RuntimeConfig |
| 26 | +from typing import Generator, Union, Optional, List |
27 | 27 | from base_api.base import BaseCore, setup_logger, Helper |
28 | 28 |
|
29 | 29 | """ |
|
57 | 57 | """ |
58 | 58 |
|
59 | 59 |
|
| 60 | + |
| 61 | +def _normalize_quality_value(q) -> Union[str, int]: |
| 62 | + if isinstance(q, int): |
| 63 | + return q |
| 64 | + s = str(q).lower().strip() |
| 65 | + if s in {"best", "half", "worst"}: |
| 66 | + return s |
| 67 | + m = re.search(r'(\d{3,4})', s) |
| 68 | + if m: |
| 69 | + return int(m.group(1)) |
| 70 | + raise ValueError(f"Invalid quality: {q}") |
| 71 | + |
| 72 | + |
| 73 | +def _choose_quality_from_list(available: List[str | int], target: Union[str, int]): |
| 74 | + # available like ["240", "360", "480", "720", "1080"] |
| 75 | + av = sorted({int(x) for x in available}) |
| 76 | + if isinstance(target, str): |
| 77 | + if target == "best": |
| 78 | + return av[-1] |
| 79 | + if target == "worst": |
| 80 | + return av[0] |
| 81 | + if target == "half": |
| 82 | + return av[len(av) // 2] |
| 83 | + raise ValueError("Invalid label.") |
| 84 | + # numeric: highest ≤ target, else closest |
| 85 | + le = [h for h in av if h <= target] |
| 86 | + if le: |
| 87 | + return le[-1] |
| 88 | + # fallback closest (ties -> higher) |
| 89 | + return min(av, key=lambda h: (abs(h - target), -h)) |
| 90 | + |
| 91 | + |
| 92 | + |
| 93 | + |
60 | 94 | class Video: |
61 | 95 | def __init__(self, url: str, enable_html_scraping: bool = True, core: Optional[BaseCore] = None): |
62 | 96 | self.core = core |
@@ -291,48 +325,54 @@ def author(self) -> str: |
291 | 325 |
|
292 | 326 | def direct_download_link(self, quality, mode) -> str: |
293 | 327 | """ |
294 | | - Returns the direct download URL for a given quality |
295 | | - :param quality: 'best', 'half', 'worst', or a specific resolution like '720p' |
| 328 | + Returns the direct download URL for a given quality (best/half/worst or a specific resolution). |
| 329 | + :param quality: 'best', 'half', 'worst', or a specific resolution like '720', '720p', 1080, etc. |
296 | 330 | :param mode: The mode to filter links by (e.g., 'video') |
297 | 331 | :return: str |
298 | 332 | """ |
299 | 333 | if not self.enable_html: |
300 | 334 | raise HTML_IS_DISABLED("HTML content is disabled! See Documentation for more details") |
301 | 335 |
|
302 | 336 | soup = BeautifulSoup(self.html_content, 'lxml') |
303 | | - available_links = [] |
| 337 | + quality_to_url: dict[int, str] = {} |
| 338 | + |
| 339 | + # Extract resolutions from link text or href (e.g., "1080p", "720", etc.) |
| 340 | + # Accept any 3–4 digit height optionally followed by 'p' |
| 341 | + _res_rx = re.compile(r'(?<!\d)(\d{3,4})p?(?!\d)') |
304 | 342 |
|
305 | | - # Define the quality preferences in descending order |
306 | | - quality_preferences = ['2160p', '1440p', '1080p', '720p', '480p', '360p', '240p'] |
| 343 | + mode_lc = str(mode).lower().strip() |
307 | 344 |
|
308 | | - # Search for all <a> tags and collect links for the specified mode |
309 | 345 | for a_tag in soup.find_all('a', href=True): |
310 | | - link_text = a_tag.text.lower() |
| 346 | + # Combine visible text + href for matching |
| 347 | + text = " ".join(a_tag.stripped_strings).lower() |
311 | 348 | href = a_tag['href'] |
312 | | - # Filter links by mode |
313 | | - if str(mode.lower()) in link_text: |
314 | | - for preference in quality_preferences: |
315 | | - if preference in link_text: |
316 | | - available_links.append((preference, href)) |
317 | | - break |
| 349 | + haystack = f"{text} {href.lower()}" |
318 | 350 |
|
319 | | - reversed_links = list(reversed(available_links)) |
| 351 | + # Filter by mode (same semantics as your original code) |
| 352 | + if mode_lc not in haystack: |
| 353 | + continue |
320 | 354 |
|
321 | | - if quality == "best": |
322 | | - quality, url = reversed_links[0] |
| 355 | + m = _res_rx.search(haystack) |
| 356 | + if not m: |
| 357 | + continue |
323 | 358 |
|
324 | | - elif quality == "half": |
325 | | - index_to_use = round(len(available_links) / 2) |
326 | | - quality, url = reversed_links[index_to_use] |
| 359 | + height = int(m.group(1)) # e.g., 1080 |
| 360 | + quality_to_url[height] = href # last one wins; order doesn't matter |
327 | 361 |
|
328 | | - elif quality == "worst": |
329 | | - quality, url = reversed_links[-1] |
| 362 | + if not quality_to_url: |
| 363 | + raise NotAvailable(f"No URLs available for mode '{mode}'") |
330 | 364 |
|
331 | | - else: |
332 | | - raise "No URLs available? Please report that" |
| 365 | + # Choose the appropriate height using your helpers |
| 366 | + available_heights = sorted(quality_to_url.keys()) # e.g., [240, 360, 480, 720, 1080] |
| 367 | + qn = _normalize_quality_value(quality) # -> 'best' | 'half' | 'worst' | int |
| 368 | + chosen_height = _choose_quality_from_list(available_heights, qn) |
| 369 | + |
| 370 | + # Map back to URL and return absolute URL |
| 371 | + chosen_url = quality_to_url[chosen_height] |
| 372 | + full_url = urljoin("https://eporner.com", str(chosen_url)) |
333 | 373 |
|
334 | | - self.logger.error(f"Using direct donwload Link: {str(url)}") |
335 | | - return urljoin("https://eporner.com", str(url)) |
| 374 | + self.logger.info(f"Using direct download link: {full_url} ({chosen_height}p)") |
| 375 | + return full_url |
336 | 376 |
|
337 | 377 | def download(self, quality, path, callback=None, mode=Encoding.mp4_h264, no_title=False, use_workaround=False): |
338 | 378 | if not self.enable_html: |
|
0 commit comments