diff --git a/README.md b/README.md index 6a49e7fb..986571dc 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ live streams, and includes features like scheduling downloads, sending notificat * Supports `curl-cffi`. See [yt-dlp documentation](https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#impersonation) * Bundled `pot provider plugin`. See [yt-dlp documentation](https://github.com/yt-dlp/yt-dlp/wiki/PO-Token-Guide) * Automatic updates for `yt-dlp` and custom `pip` packages. -* Conditions feature. +* Conditions feature to apply custom options based on `yt-dlp` returned info. * Custom browser extensions, bookmarklets and iOS shortcuts to send links to YTPTube instance. * A bundled executable version for Windows, macOS and Linux. `For non-docker users`. diff --git a/app/library/Download.py b/app/library/Download.py index b18ad0d1..aa9557ac 100644 --- a/app/library/Download.py +++ b/app/library/Download.py @@ -489,9 +489,7 @@ class Download: and self.info.downloaded_bytes and self.info.downloaded_bytes > 0 ): - self.logger.warning( - f"Keeping temp folder '{self.temp_path}', as the reported status is not finished '{self.info.status}'." - ) + self.logger.warning(f"Keeping temp folder '{self.temp_path}'. {self.info.status=}.") return tmp_dir = Path(self.temp_path) diff --git a/app/library/YTDLPOpts.py b/app/library/YTDLPOpts.py index 67526352..4996cbe0 100644 --- a/app/library/YTDLPOpts.py +++ b/app/library/YTDLPOpts.py @@ -152,9 +152,6 @@ class YTDLPOpts: self._item_cli = [] merge: list[str] = [] - if self._config._ytdlp_cli_mutable and len(self._config._ytdlp_cli_mutable) > 1: - merge.append(self._config._ytdlp_cli_mutable) - if self._preset_cli and len(self._preset_cli) > 1: merge.append(self._preset_cli) diff --git a/app/library/config.py b/app/library/config.py index 071194ee..946df2a1 100644 --- a/app/library/config.py +++ b/app/library/config.py @@ -7,13 +7,13 @@ import time from logging.handlers import TimedRotatingFileHandler from multiprocessing.managers import SyncManager from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any import coloredlogs from dotenv import load_dotenv from .Singleton import Singleton -from .Utils import FileLogFormatter, arg_converter +from .Utils import FileLogFormatter from .version import APP_BRANCH, APP_BUILD_DATE, APP_COMMIT_SHA, APP_VERSION if TYPE_CHECKING: @@ -96,9 +96,6 @@ class Config(metaclass=Singleton): debugpy_port: int = 5678 """The port to use for the debugpy server.""" - socket_timeout: int = 30 - """The socket timeout to use for yt-dlp.""" - extract_info_timeout: int = 70 """The timeout to use for extracting video information.""" @@ -138,9 +135,6 @@ class Config(metaclass=Singleton): ignore_ui: bool = False "Ignore the UI and run the application in the background." - basic_mode: bool = False - "Run the frontend in basic mode." - default_preset: str = "default" "The default preset to use when no preset is specified." @@ -159,21 +153,12 @@ class Config(metaclass=Singleton): console_enabled: bool = False "Enable direct access to yt-dlp console." - browser_enabled: bool = True - "Enable file browser access." - browser_control_enabled: bool = False "Enable file browser control access." ytdlp_auto_update: bool = True """Enable in-place auto update of yt-dlp package.""" - ytdlp_cli: str = "" - """The command line options to use for yt-dlp.""" - - _ytdlp_cli_mutable: str = "" - """The command line options to use for yt-dlp.""" - ytdlp_version: str | None = None """The version of yt-dlp to use, if not set, the latest version will be used.""" @@ -206,8 +191,6 @@ class Config(metaclass=Singleton): _immutable: tuple = ( "ytdl_options", "started", - "ytdlp_cli", - "_ytdlp_cli_mutable", "is_native", "app_version", "app_commit_sha", @@ -219,7 +202,6 @@ class Config(metaclass=Singleton): _int_vars: tuple = ( "port", "max_workers", - "socket_timeout", "extract_info_timeout", "debugpy_port", "playlist_items_concurrency", @@ -238,10 +220,8 @@ class Config(metaclass=Singleton): "ignore_ui", "ui_update_title", "pip_ignore_updates", - "basic_mode", "file_logging", "console_enabled", - "browser_enabled", "browser_control_enabled", "ytdlp_auto_update", "prevent_premiere_live", @@ -257,13 +237,10 @@ class Config(metaclass=Singleton): "remove_files", "ui_update_title", "max_workers", - "basic_mode", "default_preset", "instance_title", "console_enabled", - "browser_enabled", "browser_control_enabled", - "ytdlp_cli", "file_logging", "base_path", "is_native", @@ -351,7 +328,7 @@ class Config(metaclass=Singleton): if not self.base_path.endswith("/"): self.base_path += "/" - numeric_level = getattr(logging, self.log_level.upper(), None) + numeric_level: int | None = getattr(logging, self.log_level.upper(), None) if not isinstance(numeric_level, int): msg = f"Invalid log level '{self.log_level}' specified." raise TypeError(msg) @@ -372,55 +349,26 @@ class Config(metaclass=Singleton): debugpy.listen(("0.0.0.0", self.debugpy_port), in_process_debug_adapter=True) LOG.info(f"starting debugpy server on '0.0.0.0:{self.debugpy_port}'.") except ImportError: - LOG.error("debugpy package not found, please install it with 'pip install debugpy'.") + LOG.error("debugpy package not found, install it with 'uv sync'.") except Exception as e: LOG.error(f"Error starting debugpy server at '0.0.0.0:{self.debugpy_port}'. {e}") - opts_file: Path = Path(self.config_path) / "ytdlp.cli" - if opts_file.exists() and opts_file.stat().st_size > 2: - LOG.warning( - "Usage of 'ytdlp.cli' global config file is deprecated and will be removed in future releases. please migrate to presets." - ) - with open(opts_file) as f: - self.ytdlp_cli = f.read().strip() - if self.ytdlp_cli: - self._ytdlp_cli_mutable = self.ytdlp_cli - try: - arg_converter(args=self.ytdlp_cli, level=True) - except Exception as e: - msg = f"Failed to parse yt-dlp cli options from '{opts_file}'. '{e!s}'." - raise ValueError(msg) from e - - self._ytdlp_cli_mutable += f"\n--socket-timeout {self.socket_timeout}" - - if self.keep_archive: - LOG.warning( - "The global 'keep_archive' option is deprecated and will be removed in future releases. please use presets instead." - ) - archive_file: Path = Path(self.archive_file) - if not archive_file.exists(): - LOG.info(f"Creating archive file '{archive_file}'.") - archive_file.touch(exist_ok=True) - - LOG.info(f"keep archive option is enabled. Using archive file '{archive_file}' by default.") - self._ytdlp_cli_mutable += f"\n--download-archive {archive_file.as_posix()!s}" + if (Path(self.config_path) / "ytdlp.cli").exists(): + LOG.error("Support for ./ytdlp.cli file is removed, migrate to presets and remove the file.") if self.temp_keep: - LOG.info("Keep temp files option is enabled.") + LOG.warning("Keep temp files option is enabled.") if self.auth_password and self.auth_username: - LOG.warning(f"Basic authentication enabled with username '{self.auth_username}'.") - - if self.basic_mode: - LOG.info("The frontend is running in basic mode.") + LOG.info(f"Authentication enabled with username '{self.auth_username}'.") if self.file_logging: - log_level_file = getattr(logging, self.log_level_file.upper(), None) + log_level_file: int | None = getattr(logging, self.log_level_file.upper(), None) if not isinstance(log_level_file, int): msg = f"Invalid file log level '{self.log_level_file}' specified." raise TypeError(msg) - loggingPath = Path(self.config_path) / "logs" + loggingPath: Path = Path(self.config_path) / "logs" if not loggingPath.exists(): loggingPath.mkdir(parents=True, exist_ok=True) @@ -448,15 +396,17 @@ class Config(metaclass=Singleton): self.started = time.time() - logging.getLogger("httpcore").setLevel(logging.INFO) - for _tool in ("httpx", "urllib3.connectionpool", "apprise"): - logging.getLogger(_tool).setLevel(logging.WARNING) + _log_levels = ( + ("httpx", logging.WARNING), + ("urllib3.connectionpool", logging.WARNING), + ("apprise", logging.WARNING), + ("httpcore", logging.INFO), + ) + for _tool, _level in _log_levels: + logging.getLogger(_tool).setLevel(_level) - # check env if self.app_env not in ("production", "development"): - msg: str = ( - f"Invalid application environment '{self.app_env}' specified. Must be 'production' or 'development'." - ) + msg: str = f"Invalid app environment '{self.app_env}' specified. Must be 'production' or 'development'." raise ValueError(msg) if "dev-master" == self.app_version: @@ -470,7 +420,7 @@ class Config(metaclass=Singleton): if attribute.startswith("_"): continue - value = getattr(vClass, attribute) + value: Any = getattr(vClass, attribute) if not callable(value): attrs[attribute] = value @@ -496,23 +446,6 @@ class Config(metaclass=Singleton): """ return "production" == self.app_env - def get_ytdlp_args(self) -> dict: - """ - Get the yt-dlp command line options as a dictionary. - - Returns: - dict: The yt-dlp command line options. - - Deprecated: - Usage of global ytdlp.cli file is deprecated, please use presets instead. - - """ - try: - return arg_converter(args=self._ytdlp_cli_mutable, level=True) - except Exception as e: - msg = f"Invalid ytdlp.cli options for yt-dlp. '{e!s}'." - raise ValueError(msg) from e - def frontend(self) -> dict: """ Returns configuration variables relevant to the frontend. @@ -521,23 +454,17 @@ class Config(metaclass=Singleton): dict: A dictionary with the frontend configuration """ - data = {k: getattr(self, k) for k in self._frontend_vars} - - ytdlp_args = self.get_ytdlp_args() - - # TODO: this doesn't make sense, as each item might have it's own archive file or none at all. - if not data.get("keep_archive", False) and ytdlp_args.get("download_archive", None): - data["keep_archive"] = True + data: dict[str, Any] = {k: getattr(self, k) for k in self._frontend_vars} data["ytdlp_version"] = Config._ytdlp_version() return data - def get_replacers(self) -> dict: + def get_replacers(self) -> dict[str, str]: """ Get the variables that can be used in Command options for yt-dlp. Returns: - dict: The replacer variables. + dict[str, str]: The replacer variables. """ keys: tuple[str] = ("download_path", "temp_path", "config_path") diff --git a/app/routes/api/browser.py b/app/routes/api/browser.py index 42075f85..2c47962c 100644 --- a/app/routes/api/browser.py +++ b/app/routes/api/browser.py @@ -129,9 +129,6 @@ async def file_browser(request: Request, config: Config, encoder: Encoder) -> Re Response: The response object. """ - if not config.browser_enabled: - return web.json_response(data={"error": "File browser is disabled."}, status=web.HTTPForbidden.status_code) - req_path: str = request.match_info.get("path") req_path: str = "/" if not req_path else unquote_plus(req_path) @@ -190,9 +187,6 @@ async def path_actions(request: Request, config: Config) -> Response: Response: The response object. """ - if not config.browser_enabled: - return web.json_response(data={"error": "File browser is disabled."}, status=web.HTTPForbidden.status_code) - if not config.browser_control_enabled: return web.json_response( data={"error": "File browser actions is disabled."}, status=web.HTTPForbidden.status_code diff --git a/app/routes/api/conditions.py b/app/routes/api/conditions.py index 21796cd7..6ce20818 100644 --- a/app/routes/api/conditions.py +++ b/app/routes/api/conditions.py @@ -153,13 +153,8 @@ async def conditions_test(request: Request, encoder: Encoder, cache: Cache, conf preset: str = params.get("preset", config.default_preset) key: str = cache.hash(url + str(preset)) if not cache.has(key): - opts = {} - if ytdlp_proxy := config.get_ytdlp_args().get("proxy", None): - opts["proxy"] = ytdlp_proxy - ytdlp_opts = YTDLPOpts.get_instance().preset(name=preset).add(opts).get_all() - - data = extract_info( - config=ytdlp_opts, + data: dict = extract_info( + config=YTDLPOpts.get_instance().preset(name=preset).get_all(), url=url, debug=False, no_archive=True, @@ -182,7 +177,7 @@ async def conditions_test(request: Request, encoder: Encoder, cache: Cache, conf ) try: - status = match_str(cond, data) + status: bool = match_str(cond, data) except Exception as e: LOG.exception(e) return web.json_response( diff --git a/app/routes/api/images.py b/app/routes/api/images.py index 68cd371e..d88b99e7 100644 --- a/app/routes/api/images.py +++ b/app/routes/api/images.py @@ -2,6 +2,7 @@ import logging import random import time from datetime import UTC, datetime +from typing import Any from urllib.parse import urlparse import httpx @@ -14,6 +15,7 @@ from app.library.cache import Cache from app.library.config import Config from app.library.router import route from app.library.Utils import validate_url +from app.library.YTDLPOpts import YTDLPOpts LOG: logging.Logger = logging.getLogger(__name__) @@ -33,7 +35,7 @@ async def get_thumbnail(request: Request, config: Config) -> Response: Response: The response object. """ - url = request.query.get("url") + url: str | None = request.query.get("url") if not url: return web.json_response(data={"error": "URL is required."}, status=web.HTTPForbidden.status_code) @@ -43,15 +45,28 @@ async def get_thumbnail(request: Request, config: Config) -> Response: return web.json_response(data={"error": str(e)}, status=web.HTTPForbidden.status_code) try: - - ytdlp_args = config.get_ytdlp_args() - opts = { - "proxy": ytdlp_args.get("proxy", None), + ytdlp_args: dict = YTDLPOpts.get_instance().preset(name=config.default_preset).get_all() + opts: dict[str, Any] = { "headers": { - "User-Agent": ytdlp_args.get("user_agent", request.headers.get("User-Agent", random_user_agent())), + "User-Agent": request.headers.get("User-Agent", ytdlp_args.get("user_agent", random_user_agent())), }, } + if proxy := ytdlp_args.get("proxy"): + opts["proxy"] = proxy + + try: + from httpx_curl_cffi import AsyncCurlTransport, CurlOpt + + opts["transport"] = AsyncCurlTransport( + impersonate="chrome", + default_headers=True, + curl_options={CurlOpt.FRESH_CONNECT: True}, + ) + opts.pop("headers", None) + except Exception: + pass + async with httpx.AsyncClient(**opts) as client: LOG.debug(f"Fetching thumbnail from '{url}'.") response = await client.request(method="GET", url=url, follow_redirects=True) @@ -115,13 +130,16 @@ async def get_background(request: Request, config: Config, cache: Cache) -> Resp }, ) - ytdlp_args = config.get_ytdlp_args() - opts = { - "proxy": ytdlp_args.get("proxy", None), + ytdlp_args: dict = YTDLPOpts.get_instance().preset(name=config.default_preset).get_all() + opts: dict[str, Any] = { "headers": { - "User-Agent": ytdlp_args.get("user_agent", random_user_agent()), + "User-Agent": request.headers.get("User-Agent", ytdlp_args.get("user_agent", random_user_agent())), }, } + + if proxy := ytdlp_args.get("proxy"): + opts["proxy"] = proxy + try: from httpx_curl_cffi import AsyncCurlTransport, CurlOpt @@ -166,7 +184,7 @@ async def get_background(request: Request, config: Config, cache: Cache) -> Resp status=web.HTTPInternalServerError.status_code, ) - data = { + data: dict[str, Any] = { "content": response.content, "backend": urlparse(backend).netloc, "headers": { diff --git a/app/routes/api/yt_dlp.py b/app/routes/api/yt_dlp.py index 1b56a0db..f0b8ccdd 100644 --- a/app/routes/api/yt_dlp.py +++ b/app/routes/api/yt_dlp.py @@ -150,9 +150,6 @@ async def get_info(request: Request, cache: Cache, config: Config) -> Response: } return web.json_response(body=json.dumps(data, indent=4, default=str), status=web.HTTPOk.status_code) - if ytdlp_proxy := config.get_ytdlp_args().get("proxy", None): - opts = opts.add({"proxy": ytdlp_proxy}) - logs: list = [] ytdlp_opts: dict = { @@ -203,13 +200,14 @@ async def get_info(request: Request, cache: Cache, config: Config) -> Response: "expires": time.time() + 300, } - is_archived = False - if (archive_file := ytdlp_opts.get("download_archive")) and ( - archive_id := get_archive_id(url=url).get("archive_id") - ): - is_archived: bool = len(archive_read(archive_file, [archive_id])) > 0 + data["is_archived"] = False - data["is_archived"] = is_archived + archive_file: str | None = ytdlp_opts.get("download_archive") + data["archive_file"] = archive_file if archive_file else None + + if archive_file and (archive_id := get_archive_id(url=url).get("archive_id")): + data["archive_id"] = archive_id + data["is_archived"] = len(archive_read(archive_file, [archive_id])) > 0 data = OrderedDict(sorted(data.items(), key=lambda item: len(str(item[1])))) diff --git a/pyproject.toml b/pyproject.toml index 1fc325da..fe12e5e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -209,4 +209,9 @@ testpaths = ["app/tests"] addopts = "-v --tb=short" [dependency-groups] -dev = ["pytest>=8.4.2", "pytest-asyncio>=1.1.0", "ruff>=0.13.0"] +dev = [ + "debugpy>=1.8.16", + "pytest>=8.4.2", + "pytest-asyncio>=1.1.0", + "ruff>=0.13.0", +] diff --git a/ui/app/components/ConditionForm.vue b/ui/app/components/ConditionForm.vue index b5e1b238..db50016d 100644 --- a/ui/app/components/ConditionForm.vue +++ b/ui/app/components/ConditionForm.vue @@ -83,7 +83,8 @@ - yt-dlp [--match-filters] logic. + yt-dlp --match-filters logic with OR, || + support. @@ -211,7 +212,7 @@ - + The url to test the filter against. @@ -226,9 +227,10 @@ - + - The yt-dlp [--match-filters] filter logic.
+ yt-dlp --match-filters logic with OR, || + support.
diff --git a/ui/app/components/History.vue b/ui/app/components/History.vue index 63b17e25..238bf265 100644 --- a/ui/app/components/History.vue +++ b/ui/app/components/History.vue @@ -175,7 +175,7 @@ -
+
- + yt-dlp Information - + Local Information diff --git a/ui/app/layouts/default.vue b/ui/app/layouts/default.vue index 798591d3..7c4b79bf 100644 --- a/ui/app/layouts/default.vue +++ b/ui/app/layouts/default.vue @@ -24,9 +24,8 @@
-
-
- -

- The following environment variables and features are deprecated and will be removed in - v0.10.x -

-
    -
  • - The following ENVs YTP_KEEP_ARCHIVE and YTP_SOCKET_TIMEOUT will be - removed. - Their behavior will be part of the default presets. To keep your current behavior - and avoid re-downloading, please add the following Command options for - yt-dlp to your presets: - --socket-timeout 30 --download-archive %(config_path)s/archive.log -
  • -
  • - The global yt-dlp config file /config/ytdlp.cli will be removed. Please migrate to - presets. -
  • -
  • The archive.manual.log feature has been removed.
  • -
-

- These changes help reduce confusion from multiple sources of truth. Going forward, presets - and the Command options for yt-dlp will be the single source of truth. -

-

- Notable changes in v0.10.x: -

-
    -
  • - The file browser feature is going to be enabled by default. and the associated ENV - YTP_BROWSER_ENABLED will be removed, YTP_BROWSER_CONTROL_ENABLED will - remain and - will default to false. -
  • -
  • - The Basic mode (which limited the interface to just the new download form) along it's - associated ENV YTP_BASIC_MODE is being removed. Everything except what is available - behind configurable flag will become part of the standard interface. -
  • -
-
-
-
- - import { useStorage } from '@vueuse/core' -import DeprecatedNotice from '~/components/DeprecatedNotice.vue' import type { item_request } from '~/types/item' import type { StoreItem } from '~/types/store' diff --git a/ui/app/pages/logs.vue b/ui/app/pages/logs.vue index a61c22d1..79107b52 100644 --- a/ui/app/pages/logs.vue +++ b/ui/app/pages/logs.vue @@ -155,13 +155,6 @@ watch(toggleFilter, () => { } }); -watch(() => config.app.basic_mode, async v => { - if (!config.isLoaded() || !v) { - return - } - await navigateTo('/') -}, { immediate: true }) - watch(() => config.app.file_logging, async v => { if (v) { return diff --git a/ui/app/pages/notifications.vue b/ui/app/pages/notifications.vue index 1bac5477..474c805c 100644 --- a/ui/app/pages/notifications.vue +++ b/ui/app/pages/notifications.vue @@ -221,7 +221,6 @@ import { useStorage } from '@vueuse/core' import type { notification, notificationImport } from '~/types/notification' const toast = useNotification() -const config = useConfigStore() const socket = useSocketStore() const box = useConfirm() const display_style = useStorage("tasks_display_style", "cards") @@ -244,13 +243,6 @@ const isLoading = ref(false) const initialLoad = ref(true) const addInProgress = ref(false) -watch(() => config.app.basic_mode, async v => { - if (!config.isLoaded() || !v) { - return - } - await navigateTo('/') -}, { immediate: true }) - watch(() => socket.isConnected, async () => { if (socket.isConnected && initialLoad.value) { await reloadContent(true) diff --git a/ui/app/pages/presets.vue b/ui/app/pages/presets.vue index e98f6dfd..65e487b6 100644 --- a/ui/app/pages/presets.vue +++ b/ui/app/pages/presets.vue @@ -39,8 +39,8 @@
- Custom presets. The presets are simply pre-defined yt-dlp settings - that you want to apply to given download. + Presets are pre-defined command options for yt-dlp that you want to apply to given + download.
@@ -192,16 +192,7 @@
    -
  • - When you export preset, it doesn't include Cookies field for security reasons. -
  • -
  • - If you have created a global config/ytdlp.cli file, it will be appended to your exported - preset - Command options for yt-dlp field for better - compatibility - and completeness. -
  • +
  • When you export preset, it doesn't include Cookies field for security reasons.
@@ -233,13 +224,6 @@ const remove_keys = ['raw', 'toggle_description'] const presetsNoDefault = computed(() => presets.value.filter((t) => !t.default)) -watch(() => config.app.basic_mode, async v => { - if (!config.isLoaded() || !v) { - return - } - await navigateTo('/') -}, { immediate: true }) - watch(() => socket.isConnected, async () => { if (socket.isConnected && initialLoad.value) { await reloadContent(true) @@ -391,11 +375,6 @@ const exportItem = (item: Preset) => { } } - if (config?.app?.ytdlp_cli) { - const val = `# exported from ytdlp.cli #\n${config.app.ytdlp_cli}\n# exported from ytdlp.cli #\n` - userData.cli = userData.cli ? val + '\n' + userData.cli : val - } - userData['_type'] = 'preset' userData['_version'] = '2.5' diff --git a/ui/app/pages/tasks.vue b/ui/app/pages/tasks.vue index 4ea09969..3533f8c9 100644 --- a/ui/app/pages/tasks.vue +++ b/ui/app/pages/tasks.vue @@ -444,13 +444,6 @@ watch(masterSelectAll, value => { } }) -watch(() => config.app.basic_mode, async v => { - if (!config.isLoaded() || !v) { - return - } - await navigateTo('/') -}, { immediate: true }) - watch(() => socket.isConnected, async () => { if (socket.isConnected && initialLoad.value) { socket.on('item_status', statusHandler) diff --git a/ui/app/stores/ConfigStore.ts b/ui/app/stores/ConfigStore.ts index 8b61e0af..c21c6bae 100644 --- a/ui/app/stores/ConfigStore.ts +++ b/ui/app/stores/ConfigStore.ts @@ -11,13 +11,10 @@ export const useConfigStore = defineStore('config', () => { output_template: '', ytdlp_version: '', max_workers: 1, - basic_mode: true, default_preset: 'default', instance_title: null, console_enabled: false, - browser_enabled: false, browser_control_enabled: false, - ytdlp_cli: '', file_logging: false, is_native: false, app_version: '', @@ -30,7 +27,7 @@ export const useConfigStore = defineStore('config', () => { presets: [ { 'name': 'default', - 'description': 'Default preset for downloads', + 'description': 'Default preset', 'folder': '', 'template': '', 'cookies': '', diff --git a/ui/app/types/config.d.ts b/ui/app/types/config.d.ts index d6f22f2c..5bf7beb6 100644 --- a/ui/app/types/config.d.ts +++ b/ui/app/types/config.d.ts @@ -14,20 +14,14 @@ type AppConfig = { ytdlp_version: string /** Maximum number of concurrent download workers */ max_workers: number - /** Indicates if the app is in basic mode, which may limit some features */ - basic_mode: boolean /** Default preset name, e.g. "default" */ default_preset: string /** Instance title for the app, null if not set */ instance_title: string | null /** Indicates if the console is enabled */ console_enabled: boolean - /** Indicates if the file browser is enabled */ - browser_enabled: boolean /** Indicates if the file browser control is enabled */ browser_control_enabled: boolean - /** Command options for yt-dlp */ - ytdlp_cli: string /** Indicates if file logging is enabled */ file_logging: boolean /** Indicates if the app is running in a native environment (e.g., Electron) */ diff --git a/uv.lock b/uv.lock index d7b3c96b..8b3f6b81 100644 --- a/uv.lock +++ b/uv.lock @@ -1513,6 +1513,7 @@ installer = [ [package.dev-dependencies] dev = [ + { name = "debugpy" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "ruff" }, @@ -1556,6 +1557,7 @@ provides-extras = ["installer"] [package.metadata.requires-dev] dev = [ + { name = "debugpy", specifier = ">=1.8.16" }, { name = "pytest", specifier = ">=8.4.2" }, { name = "pytest-asyncio", specifier = ">=1.1.0" }, { name = "ruff", specifier = ">=0.13.0" },