F
This commit is contained in:
@@ -22,7 +22,7 @@
|
|||||||
"((1fichier\\.com|megadl\\.fr|alterupload\\.com|cjoint\\.net|desfichiers\\.com|dfichiers\\.com|mesfichiers\\.org|piecejointe\\.net|pjointe\\.com|tenvoi\\.com|dl4free\\.com)/\\?[a-zA-Z0-9]{5,30}(&pw=[^&]+)?)"
|
"((1fichier\\.com|megadl\\.fr|alterupload\\.com|cjoint\\.net|desfichiers\\.com|dfichiers\\.com|mesfichiers\\.org|piecejointe\\.net|pjointe\\.com|tenvoi\\.com|dl4free\\.com)/\\?[a-zA-Z0-9]{5,30}(&pw=[^&]+)?)"
|
||||||
],
|
],
|
||||||
"regexp": "((1fichier\\.com|megadl\\.fr|alterupload\\.com|cjoint\\.net|desfichiers\\.com|dfichiers\\.com|mesfichiers\\.org|piecejointe\\.net|pjointe\\.com|tenvoi\\.com|dl4free\\.com)/\\?[a-zA-Z0-9]{5,30}(&pw=[^&]+)?)",
|
"regexp": "((1fichier\\.com|megadl\\.fr|alterupload\\.com|cjoint\\.net|desfichiers\\.com|dfichiers\\.com|mesfichiers\\.org|piecejointe\\.net|pjointe\\.com|tenvoi\\.com|dl4free\\.com)/\\?[a-zA-Z0-9]{5,30}(&pw=[^&]+)?)",
|
||||||
"status": false
|
"status": true
|
||||||
},
|
},
|
||||||
"rapidgator": {
|
"rapidgator": {
|
||||||
"name": "rapidgator",
|
"name": "rapidgator",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
"(rapidgator\\.net/file/[0-9]{7,8})"
|
"(rapidgator\\.net/file/[0-9]{7,8})"
|
||||||
],
|
],
|
||||||
"regexp": "((rapidgator\\.net|rg\\.to|rapidgator\\.asia)/file/([0-9a-zA-Z]{32}))|((rapidgator\\.net/file/[0-9]{7,8}))",
|
"regexp": "((rapidgator\\.net|rg\\.to|rapidgator\\.asia)/file/([0-9a-zA-Z]{32}))|((rapidgator\\.net/file/[0-9]{7,8}))",
|
||||||
"status": true
|
"status": false
|
||||||
},
|
},
|
||||||
"turbobit": {
|
"turbobit": {
|
||||||
"name": "turbobit",
|
"name": "turbobit",
|
||||||
@@ -439,7 +439,7 @@
|
|||||||
"(hexupload\\.net|hexload\\.com)/([a-zA-Z0-9]{12})"
|
"(hexupload\\.net|hexload\\.com)/([a-zA-Z0-9]{12})"
|
||||||
],
|
],
|
||||||
"regexp": "(hexupload\\.net|hexload\\.com)/([a-zA-Z0-9]{12})",
|
"regexp": "(hexupload\\.net|hexload\\.com)/([a-zA-Z0-9]{12})",
|
||||||
"status": false
|
"status": true
|
||||||
},
|
},
|
||||||
"hot4share": {
|
"hot4share": {
|
||||||
"name": "hot4share",
|
"name": "hot4share",
|
||||||
@@ -495,7 +495,7 @@
|
|||||||
"(katfile\\.com/[0-9a-zA-Z]{12})"
|
"(katfile\\.com/[0-9a-zA-Z]{12})"
|
||||||
],
|
],
|
||||||
"regexp": "(katfile\\.(cloud|online)/([0-9a-zA-Z]{12}))|((katfile\\.com/[0-9a-zA-Z]{12}))",
|
"regexp": "(katfile\\.(cloud|online)/([0-9a-zA-Z]{12}))|((katfile\\.com/[0-9a-zA-Z]{12}))",
|
||||||
"status": false
|
"status": true
|
||||||
},
|
},
|
||||||
"mediafire": {
|
"mediafire": {
|
||||||
"name": "mediafire",
|
"name": "mediafire",
|
||||||
@@ -507,20 +507,6 @@
|
|||||||
"mediafire\\.com/(\\?|download/|file/|download\\.php\\?)([0-9a-z]{15})"
|
"mediafire\\.com/(\\?|download/|file/|download\\.php\\?)([0-9a-z]{15})"
|
||||||
],
|
],
|
||||||
"regexp": "mediafire\\.com/(\\?|download/|file/|download\\.php\\?)([0-9a-z]{15})",
|
"regexp": "mediafire\\.com/(\\?|download/|file/|download\\.php\\?)([0-9a-z]{15})",
|
||||||
"status": true
|
|
||||||
},
|
|
||||||
"mexashare": {
|
|
||||||
"name": "mexashare",
|
|
||||||
"type": "premium",
|
|
||||||
"domains": [
|
|
||||||
"mexashare.com",
|
|
||||||
"mx-sh.net",
|
|
||||||
"mexa.sh"
|
|
||||||
],
|
|
||||||
"regexps": [
|
|
||||||
"((mexashare\\.com|mx-sh\\.net|mexa\\.sh)/[0-9a-zA-Z]{12})"
|
|
||||||
],
|
|
||||||
"regexp": "((mexashare\\.com|mx-sh\\.net|mexa\\.sh)/[0-9a-zA-Z]{12})",
|
|
||||||
"status": false
|
"status": false
|
||||||
},
|
},
|
||||||
"mixdrop": {
|
"mixdrop": {
|
||||||
@@ -622,7 +608,7 @@
|
|||||||
"(simfileshare\\.net/download/[0-9]+/)"
|
"(simfileshare\\.net/download/[0-9]+/)"
|
||||||
],
|
],
|
||||||
"regexp": "(simfileshare\\.net/download/[0-9]+/)",
|
"regexp": "(simfileshare\\.net/download/[0-9]+/)",
|
||||||
"status": false
|
"status": true
|
||||||
},
|
},
|
||||||
"streamtape": {
|
"streamtape": {
|
||||||
"name": "streamtape",
|
"name": "streamtape",
|
||||||
@@ -802,7 +788,7 @@
|
|||||||
"(worldbytez\\.(net|com)/[a-zA-Z0-9]{12})"
|
"(worldbytez\\.(net|com)/[a-zA-Z0-9]{12})"
|
||||||
],
|
],
|
||||||
"regexp": "(worldbytez\\.(net|com)/[a-zA-Z0-9]{12})",
|
"regexp": "(worldbytez\\.(net|com)/[a-zA-Z0-9]{12})",
|
||||||
"status": false
|
"status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"streams": {
|
"streams": {
|
||||||
@@ -17956,9 +17942,9 @@
|
|||||||
"generic.tld"
|
"generic.tld"
|
||||||
],
|
],
|
||||||
"regexps": [
|
"regexps": [
|
||||||
"((example.com|1fichier.com|4shared.com|vev.io|clipwatching.com|clicknupload.click|playvidto.com|uploadrar.com|simfileshare.net|usersdrive.com|fastbit.cc|dropgalaxy.in|uploadboy.com|file.al|filespace.com|uploader.link|9xupload.asia|hexupload.net|filefactory.com|filerio.in|drive.google.com|gigapeta.com|isra.cloud|katfile.com|mediafire.com|mega.co.nz|alldebrid.com|prefiles.com|rapidgator.net|alfafile.net|scribd.com|turbobit.net|hitfile.net|sendit.cloud|ddl.to|exload.com|uploadhaven.com|vidoza.net|mixdrop.co|dropapk.to|indishare.me|world-files.com|uploadbox.io|worldbytez.com|mp4upload.com|mexashare.com|upload42.com|uploading.vn|filedot.to|zofile.com|spicyfile.com|modsbase.com|sharemods.com|dl-file.com|dosya.co|loadstar.club|dailyuploads.net|file-upload.com|uploadbank.com|filezip.cc|hot4share.com|streamtape.com)/folders?/[^'\"<>;]+)"
|
"((example.com|1fichier.com|4shared.com|vev.io|clipwatching.com|clicknupload.click|playvidto.com|uploadrar.com|simfileshare.net|usersdrive.com|fastbit.cc|dropgalaxy.in|uploadboy.com|file.al|filespace.com|uploader.link|9xupload.asia|hexupload.net|filefactory.com|filerio.in|drive.google.com|gigapeta.com|isra.cloud|katfile.com|mediafire.com|mega.co.nz|alldebrid.com|prefiles.com|rapidgator.net|alfafile.net|scribd.com|turbobit.net|hitfile.net|sendit.cloud|ddl.to|exload.com|uploadhaven.com|vidoza.net|mixdrop.co|dropapk.to|indishare.me|world-files.com|uploadbox.io|worldbytez.com|mp4upload.com|upload42.com|uploading.vn|filedot.to|zofile.com|spicyfile.com|modsbase.com|sharemods.com|dl-file.com|dosya.co|loadstar.club|dailyuploads.net|file-upload.com|uploadbank.com|filezip.cc|hot4share.com|streamtape.com)/folders?/[^'\"<>;]+)"
|
||||||
],
|
],
|
||||||
"regexp": "((example.com|1fichier.com|4shared.com|vev.io|clipwatching.com|clicknupload.click|playvidto.com|uploadrar.com|simfileshare.net|usersdrive.com|fastbit.cc|dropgalaxy.in|uploadboy.com|file.al|filespace.com|uploader.link|9xupload.asia|hexupload.net|filefactory.com|filerio.in|drive.google.com|gigapeta.com|isra.cloud|katfile.com|mediafire.com|mega.co.nz|alldebrid.com|prefiles.com|rapidgator.net|alfafile.net|scribd.com|turbobit.net|hitfile.net|sendit.cloud|ddl.to|exload.com|uploadhaven.com|vidoza.net|mixdrop.co|dropapk.to|indishare.me|world-files.com|uploadbox.io|worldbytez.com|mp4upload.com|mexashare.com|upload42.com|uploading.vn|filedot.to|zofile.com|spicyfile.com|modsbase.com|sharemods.com|dl-file.com|dosya.co|loadstar.club|dailyuploads.net|file-upload.com|uploadbank.com|filezip.cc|hot4share.com|streamtape.com)/folders?/[^'\"<>;]+)"
|
"regexp": "((example.com|1fichier.com|4shared.com|vev.io|clipwatching.com|clicknupload.click|playvidto.com|uploadrar.com|simfileshare.net|usersdrive.com|fastbit.cc|dropgalaxy.in|uploadboy.com|file.al|filespace.com|uploader.link|9xupload.asia|hexupload.net|filefactory.com|filerio.in|drive.google.com|gigapeta.com|isra.cloud|katfile.com|mediafire.com|mega.co.nz|alldebrid.com|prefiles.com|rapidgator.net|alfafile.net|scribd.com|turbobit.net|hitfile.net|sendit.cloud|ddl.to|exload.com|uploadhaven.com|vidoza.net|mixdrop.co|dropapk.to|indishare.me|world-files.com|uploadbox.io|worldbytez.com|mp4upload.com|upload42.com|uploading.vn|filedot.to|zofile.com|spicyfile.com|modsbase.com|sharemods.com|dl-file.com|dosya.co|loadstar.club|dailyuploads.net|file-upload.com|uploadbank.com|filezip.cc|hot4share.com|streamtape.com)/folders?/[^'\"<>;]+)"
|
||||||
},
|
},
|
||||||
"google": {
|
"google": {
|
||||||
"name": "google",
|
"name": "google",
|
||||||
|
|||||||
@@ -145,7 +145,8 @@ class PipeObject:
|
|||||||
title_text = cmdlet_name
|
title_text = cmdlet_name
|
||||||
|
|
||||||
# Color the title (requested: yellow instead of Rich's default blue-ish title).
|
# Color the title (requested: yellow instead of Rich's default blue-ish title).
|
||||||
debug_inspect(self, title=f"[yellow]{title_text}[/yellow]")
|
# We disable value=False to avoid duplicating the object repr which is redundant with the attribute listing
|
||||||
|
debug_inspect(self, title=f"[yellow]{title_text}[/yellow]", value=False)
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
"""Serialize to dictionary, excluding None and empty values."""
|
"""Serialize to dictionary, excluding None and empty values."""
|
||||||
|
|||||||
@@ -18,12 +18,13 @@ from contextlib import AbstractContextManager, nullcontext
|
|||||||
|
|
||||||
from API.HTTP import _download_direct_file
|
from API.HTTP import _download_direct_file
|
||||||
from SYS.models import DownloadError, DownloadOptions, DownloadMediaResult
|
from SYS.models import DownloadError, DownloadOptions, DownloadMediaResult
|
||||||
from SYS.logger import log, debug
|
from SYS.logger import log, debug, is_debug_enabled
|
||||||
from SYS.pipeline_progress import PipelineProgress
|
from SYS.pipeline_progress import PipelineProgress
|
||||||
from SYS.result_table import Table
|
from SYS.result_table import Table
|
||||||
from SYS.rich_display import stderr_console as get_stderr_console
|
from SYS.rich_display import stderr_console as get_stderr_console
|
||||||
from SYS import pipeline as pipeline_context
|
from SYS import pipeline as pipeline_context
|
||||||
from SYS.metadata import normalize_urls as normalize_url_list
|
from SYS.metadata import normalize_urls as normalize_url_list
|
||||||
|
from SYS.utils import sha256_file
|
||||||
|
|
||||||
from tool.ytdlp import (
|
from tool.ytdlp import (
|
||||||
YtDlpTool,
|
YtDlpTool,
|
||||||
@@ -1495,6 +1496,41 @@ class Download_File(Cmdlet):
|
|||||||
forced_single_applied = True
|
forced_single_applied = True
|
||||||
|
|
||||||
# Proactive fallback for single audio formats which might be unstable
|
# Proactive fallback for single audio formats which might be unstable
|
||||||
|
if (
|
||||||
|
actual_format
|
||||||
|
and isinstance(actual_format, str)
|
||||||
|
and actual_format == "audio"
|
||||||
|
):
|
||||||
|
actual_format = "bestaudio/best"
|
||||||
|
|
||||||
|
# DEBUG: Render config panel for tracking pipeline state
|
||||||
|
if is_debug_enabled():
|
||||||
|
try:
|
||||||
|
from rich.table import Table as RichTable
|
||||||
|
from rich import box as RichBox
|
||||||
|
from tool.ytdlp import YtDlpDefaults
|
||||||
|
|
||||||
|
t = RichTable(title="Download Config", show_header=True, header_style="bold magenta", box=RichBox.ROUNDED)
|
||||||
|
t.add_column("Property", style="cyan")
|
||||||
|
t.add_column("Value", style="green")
|
||||||
|
t.add_row("URL", url)
|
||||||
|
t.add_row("Mode", mode)
|
||||||
|
t.add_row("Format", str(actual_format))
|
||||||
|
t.add_row("Playlist Items", str(actual_playlist_items))
|
||||||
|
|
||||||
|
# Browser/Cookie info from ytdlp tool
|
||||||
|
defaults = getattr(ytdlp_tool, "defaults", None)
|
||||||
|
if isinstance(defaults, YtDlpDefaults):
|
||||||
|
t.add_row("Cookie File", str(defaults.cookiefile or "None"))
|
||||||
|
t.add_row("Browser Cookies", str(defaults.cookies_from_browser or "None"))
|
||||||
|
t.add_row("User Agent", str(defaults.user_agent or "default"))
|
||||||
|
|
||||||
|
debug(t)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# If no format explicitly chosen, we might want to check available formats
|
||||||
|
# and maybe show a table if multiple are available?
|
||||||
if (
|
if (
|
||||||
actual_format
|
actual_format
|
||||||
and isinstance(actual_format, str)
|
and isinstance(actual_format, str)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from pathlib import Path
|
|||||||
from typing import Any, Dict, List, Optional, Sequence, Tuple
|
from typing import Any, Dict, List, Optional, Sequence, Tuple
|
||||||
from urllib.parse import urlsplit, quote, urljoin, unquote
|
from urllib.parse import urlsplit, quote, urljoin, unquote
|
||||||
|
|
||||||
from SYS.logger import log, debug
|
from SYS.logger import log, debug, is_debug_enabled
|
||||||
from API.HTTP import HTTPClient
|
from API.HTTP import HTTPClient
|
||||||
from SYS.pipeline_progress import PipelineProgress
|
from SYS.pipeline_progress import PipelineProgress
|
||||||
from SYS.utils import ensure_directory, unique_path, unique_preserve_order
|
from SYS.utils import ensure_directory, unique_path, unique_preserve_order
|
||||||
@@ -27,6 +27,7 @@ Cmdlet = sh.Cmdlet
|
|||||||
CmdletArg = sh.CmdletArg
|
CmdletArg = sh.CmdletArg
|
||||||
SharedArgs = sh.SharedArgs
|
SharedArgs = sh.SharedArgs
|
||||||
create_pipe_object_result = sh.create_pipe_object_result
|
create_pipe_object_result = sh.create_pipe_object_result
|
||||||
|
coerce_to_pipe_object = sh.coerce_to_pipe_object
|
||||||
normalize_result_input = sh.normalize_result_input
|
normalize_result_input = sh.normalize_result_input
|
||||||
should_show_help = sh.should_show_help
|
should_show_help = sh.should_show_help
|
||||||
get_field = sh.get_field
|
get_field = sh.get_field
|
||||||
@@ -592,13 +593,33 @@ def _capture(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
tool.debug_dump()
|
if is_debug_enabled():
|
||||||
|
try:
|
||||||
|
from rich.table import Table
|
||||||
|
from rich import box
|
||||||
|
t = Table(title="Screenshot Config", show_header=True, header_style="bold magenta", box=box.ROUNDED)
|
||||||
|
t.add_column("Property", style="cyan")
|
||||||
|
t.add_column("Value", style="green")
|
||||||
|
t.add_row("URL", options.url)
|
||||||
|
t.add_row("Format", _normalize_format(options.output_format))
|
||||||
|
|
||||||
|
# Browser details
|
||||||
|
defaults = getattr(tool, "defaults", None)
|
||||||
|
if defaults:
|
||||||
|
t.add_row("Browser", getattr(defaults, "browser", "unknown"))
|
||||||
|
t.add_row("Headless", str(getattr(defaults, "headless", "unknown")))
|
||||||
|
t.add_row("Viewport", f"{getattr(defaults, 'viewport_width', '?')}x{getattr(defaults, 'viewport_height', '?')}")
|
||||||
|
t.add_row("Timeout", f"{getattr(defaults, 'navigation_timeout_ms', '?')}ms")
|
||||||
|
|
||||||
|
t.add_row("Full Page", str(options.full_page))
|
||||||
|
t.add_row("Destination", str(destination))
|
||||||
|
debug(t)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
debug("Launching browser...")
|
|
||||||
format_name = _normalize_format(options.output_format)
|
format_name = _normalize_format(options.output_format)
|
||||||
headless = options.headless or format_name == "pdf"
|
headless = options.headless or format_name == "pdf"
|
||||||
debug(f"[_capture] Format: {format_name}, Headless: {headless}")
|
|
||||||
|
|
||||||
if format_name == "pdf" and not options.headless:
|
if format_name == "pdf" and not options.headless:
|
||||||
warnings.append(
|
warnings.append(
|
||||||
"pdf output requires headless Chromium; overriding headless mode"
|
"pdf output requires headless Chromium; overriding headless mode"
|
||||||
@@ -1129,6 +1150,8 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
|||||||
is_temp=True,
|
is_temp=True,
|
||||||
parent_hash=hashlib.sha256(url.encode()).hexdigest(),
|
parent_hash=hashlib.sha256(url.encode()).hexdigest(),
|
||||||
tag=merged_tags,
|
tag=merged_tags,
|
||||||
|
url=url, # Explicitly map url to top-level PipeObject field
|
||||||
|
source_url=url, # Map source_url as well
|
||||||
extra={
|
extra={
|
||||||
"source_url": url,
|
"source_url": url,
|
||||||
"archive_url": screenshot_result.archive_url,
|
"archive_url": screenshot_result.archive_url,
|
||||||
@@ -1141,6 +1164,24 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
|||||||
pipeline_context.emit(pipe_obj)
|
pipeline_context.emit(pipe_obj)
|
||||||
all_emitted.append(pipe_obj)
|
all_emitted.append(pipe_obj)
|
||||||
|
|
||||||
|
# Debug: show PipeObject preview if enabled
|
||||||
|
if is_debug_enabled():
|
||||||
|
try:
|
||||||
|
debug("[screen-shot] Output PipeObject preview")
|
||||||
|
po = coerce_to_pipe_object(pipe_obj)
|
||||||
|
from SYS.logger import _sanitize_pipe_object_for_debug as _sanitize # Or use helper if avail
|
||||||
|
# Add simple sanitize helper if not available
|
||||||
|
def _safe_table(obj):
|
||||||
|
try:
|
||||||
|
# Try calling debug_table on the object
|
||||||
|
if hasattr(obj, "debug_table"):
|
||||||
|
obj.debug_table()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
_safe_table(po)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# If we created a local progress UI, advance it per completed item.
|
# If we created a local progress UI, advance it per completed item.
|
||||||
progress.on_emit(pipe_obj)
|
progress.on_emit(pipe_obj)
|
||||||
|
|
||||||
|
|||||||
@@ -253,76 +253,6 @@ class PlaywrightTool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def config_schema() -> List[Dict[str, Any]]:
|
|
||||||
"""Return a schema describing editable Playwright tool defaults for the config UI.
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- `user_agent` is a dropdown with a `custom` option; put the real UA in
|
|
||||||
`user_agent_custom` when choosing `custom`.
|
|
||||||
- Viewport dimensions are offered as convenient choices.
|
|
||||||
- `ffmpeg_path` intentionally defaults to empty; Playwright will consult
|
|
||||||
a global `FFMPEG_PATH` environment variable (or fallback to bundled/system).
|
|
||||||
"""
|
|
||||||
_defaults = PlaywrightDefaults()
|
|
||||||
|
|
||||||
browser_choices = ["chromium", "firefox", "webkit"]
|
|
||||||
viewport_width_choices = [1920, 1366, 1280, 1024, 800]
|
|
||||||
viewport_height_choices = [1080, 900, 768, 720, 600]
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"key": "browser",
|
|
||||||
"label": "Playwright browser",
|
|
||||||
"default": _defaults.browser,
|
|
||||||
"choices": browser_choices,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "headless",
|
|
||||||
"label": "Headless",
|
|
||||||
"default": str(_defaults.headless),
|
|
||||||
"choices": ["true", "false"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "user_agent",
|
|
||||||
"label": "User Agent",
|
|
||||||
"default": "default",
|
|
||||||
"choices": ["default", "native", "custom"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "user_agent_custom",
|
|
||||||
"label": "Custom User Agent (used when User Agent = custom)",
|
|
||||||
"default": "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "viewport_width",
|
|
||||||
"label": "Viewport width",
|
|
||||||
"default": _defaults.viewport_width,
|
|
||||||
"choices": viewport_width_choices,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "viewport_height",
|
|
||||||
"label": "Viewport height",
|
|
||||||
"default": _defaults.viewport_height,
|
|
||||||
"choices": viewport_height_choices,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "navigation_timeout_ms",
|
|
||||||
"label": "Navigation timeout (ms)",
|
|
||||||
"default": _defaults.navigation_timeout_ms,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "ignore_https_errors",
|
|
||||||
"label": "Ignore HTTPS errors",
|
|
||||||
"default": str(_defaults.ignore_https_errors),
|
|
||||||
"choices": ["true", "false"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "ffmpeg_path",
|
|
||||||
"label": "FFmpeg path (leave empty to use global/bundled)",
|
|
||||||
"default": "",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
def require(self) -> None:
|
def require(self) -> None:
|
||||||
"""Ensure Playwright is present; raise a helpful RuntimeError if not."""
|
"""Ensure Playwright is present; raise a helpful RuntimeError if not."""
|
||||||
try:
|
try:
|
||||||
@@ -635,3 +565,74 @@ def config_schema() -> List[Dict[str, Any]]:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def config_schema() -> List[Dict[str, Any]]:
|
||||||
|
"""Return a schema describing editable Playwright tool defaults for the config UI.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- `user_agent` is a dropdown with a `custom` option; put the real UA in
|
||||||
|
`user_agent_custom` when choosing `custom`.
|
||||||
|
- Viewport dimensions are offered as convenient choices.
|
||||||
|
- `ffmpeg_path` intentionally defaults to empty; Playwright will consult
|
||||||
|
a global `FFMPEG_PATH` environment variable (or fallback to bundled/system).
|
||||||
|
"""
|
||||||
|
_defaults = PlaywrightDefaults()
|
||||||
|
|
||||||
|
browser_choices = ["chromium", "firefox", "webkit"]
|
||||||
|
viewport_width_choices = [1920, 1366, 1280, 1024, 800]
|
||||||
|
viewport_height_choices = [1080, 900, 768, 720, 600]
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"key": "browser",
|
||||||
|
"label": "Playwright browser",
|
||||||
|
"default": _defaults.browser,
|
||||||
|
"choices": browser_choices,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "headless",
|
||||||
|
"label": "Headless",
|
||||||
|
"default": str(_defaults.headless),
|
||||||
|
"choices": ["true", "false"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "user_agent",
|
||||||
|
"label": "User Agent",
|
||||||
|
"default": "default",
|
||||||
|
"choices": ["default", "native", "custom"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "user_agent_custom",
|
||||||
|
"label": "Custom User Agent (used when User Agent = custom)",
|
||||||
|
"default": "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "viewport_width",
|
||||||
|
"label": "Viewport width",
|
||||||
|
"default": _defaults.viewport_width,
|
||||||
|
"choices": viewport_width_choices,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "viewport_height",
|
||||||
|
"label": "Viewport height",
|
||||||
|
"default": _defaults.viewport_height,
|
||||||
|
"choices": viewport_height_choices,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "navigation_timeout_ms",
|
||||||
|
"label": "Navigation timeout (ms)",
|
||||||
|
"default": _defaults.navigation_timeout_ms,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ignore_https_errors",
|
||||||
|
"label": "Ignore HTTPS errors",
|
||||||
|
"default": str(_defaults.ignore_https_errors),
|
||||||
|
"choices": ["true", "false"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ffmpeg_path",
|
||||||
|
"label": "FFmpeg path (leave empty to use global/bundled)",
|
||||||
|
"default": "",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|||||||
@@ -770,8 +770,8 @@ class YtDlpTool:
|
|||||||
"""Resolve numeric heights (720, 1080p) to yt-dlp height selectors.
|
"""Resolve numeric heights (720, 1080p) to yt-dlp height selectors.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
"720" -> "bv*[height<=720]+ba"
|
"720" -> "bestvideo[height<=720]+bestaudio/best[height<=720]"
|
||||||
"1080p" -> "bv*[height<=1080]+ba"
|
"1080p" -> "bestvideo[height<=1080]+bestaudio/best[height<=1080]"
|
||||||
"""
|
"""
|
||||||
if not format_str or not isinstance(format_str, str):
|
if not format_str or not isinstance(format_str, str):
|
||||||
return None
|
return None
|
||||||
@@ -783,10 +783,33 @@ class YtDlpTool:
|
|||||||
# Strip trailing 'p' if present (e.g. 720p -> 720)
|
# Strip trailing 'p' if present (e.g. 720p -> 720)
|
||||||
if s.endswith('p'):
|
if s.endswith('p'):
|
||||||
s = s[:-1]
|
s = s[:-1]
|
||||||
|
|
||||||
|
# Heuristic: 240/360/480/720/1080/1440/2160 are common height inputs
|
||||||
|
# But small IDs like 18, 22, 137 are format IDs.
|
||||||
|
# YouTube Format IDs are usually 2-3 digits.
|
||||||
|
# Heights are also 3-4 digits.
|
||||||
|
# "240" is ambiguous (Format 240 vs Height 240).
|
||||||
|
# We assume common video heights are intended as heights.
|
||||||
if s.isdigit():
|
if s.isdigit():
|
||||||
height = int(s)
|
val = int(s)
|
||||||
if height >= 144:
|
|
||||||
return f"bv*[height<={height}]+ba"
|
# Common video heights that overlap with format IDs:
|
||||||
|
# - None currently overlap with common legacy itag IDs (17,18,22,34-38,43-46)
|
||||||
|
# or dash video IDs (133-137, 160, 242-248, 264, 271, 278, 298-315...).
|
||||||
|
# - 240, 360, 480, 720, 1080, 1440, 2160
|
||||||
|
#
|
||||||
|
# Format 240 is likely not a thing (242, 243, ... exist).
|
||||||
|
# Format 480 ... none in common lists.
|
||||||
|
# Format 720 ... none.
|
||||||
|
# So if it looks like a standard resolution, treat as height constraint.
|
||||||
|
|
||||||
|
if val in {144, 240, 360, 480, 540, 720, 1080, 1440, 2160, 2880, 4320}:
|
||||||
|
return f"bestvideo[height<={val}]+bestaudio/best[height<={val}]"
|
||||||
|
|
||||||
|
# If user types something like 500, we can also treat as height constraint if > 100
|
||||||
|
if val >= 100 and val not in {133, 134, 135, 136, 137, 160, 242, 243, 244, 247, 248, 278, 394, 395, 396, 397, 398, 399}:
|
||||||
|
return f"bestvideo[height<={val}]+bestaudio/best[height<={val}]"
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _load_defaults(self) -> YtDlpDefaults:
|
def _load_defaults(self) -> YtDlpDefaults:
|
||||||
|
|||||||
Reference in New Issue
Block a user