diff --git a/.vscode/settings.json b/.vscode/settings.json
index cd8d9648..ad57e7c4 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -98,6 +98,7 @@
"smhd",
"socketio",
"sstr",
+ "SUPPRESSHELP",
"tebibytes",
"tiktok",
"timespec",
diff --git a/app/library/Utils.py b/app/library/Utils.py
index 9cf1c146..26257ad4 100644
--- a/app/library/Utils.py
+++ b/app/library/Utils.py
@@ -43,7 +43,21 @@ REMOVE_KEYS: list = [
"progress_template": "--progress-template",
"consoletitle": "--console-title",
"progress_with_newline": "--newline",
- "forcejson": "-j, --dump-json",
+ "forcejson": "-j, --dump-single-json",
+ "opt_update_to": "--update-to",
+ "opt_ap_list_mso": "--ap-list-mso",
+ "opt_batch_file": "-a, --batch-file",
+ "opt_alias": "--alias",
+ "opt_list_extractors": "--list-extractors",
+ "opt_version": "--version",
+ "opt_help": "-h, --help",
+ "opt_update": "-U, --update",
+ "opt_list_subtitles": "--list-subs",
+ "opt_list_thumbnails": "--list-thumbnails",
+ "opt_list_format": "-F, --list-formats",
+ "opt_dump_agent": "--dump-user-agent",
+ "opt_extractor_descriptions": "--extractor-descriptions",
+ "opt_list_impersonate_targets": "--list-impersonate-targets"
},
]
diff --git a/app/library/ytdlp.py b/app/library/ytdlp.py
index 5dae3510..ecbc61e9 100644
--- a/app/library/ytdlp.py
+++ b/app/library/ytdlp.py
@@ -1,3 +1,7 @@
+from __future__ import annotations
+
+from typing import Any
+
import yt_dlp
import app.postprocessors # noqa: F401
@@ -12,3 +16,44 @@ class YTDLP(yt_dlp.YoutubeDL):
return None
return super()._delete_downloaded_files(*args, **kwargs)
+
+
+def ytdlp_options() -> list[dict[str, Any]]:
+ """
+ Collect yt-dlp options and return them in a structured format.
+
+ Returns:
+ list[dict[str, Any]]: A list of dictionaries containing option flags, descriptions,
+
+ """
+ from yt_dlp.options import create_parser
+
+ from app.library.Utils import REMOVE_KEYS
+
+ parser = create_parser()
+
+ ignored_flags: set[str] = {
+ f.strip() for group in REMOVE_KEYS for v in group.values() for f in v.split(",") if f.strip()
+ }
+
+ def collect(opts, group: str) -> list[dict[str, Any]]:
+ return [
+ {
+ "flags": list(getattr(opt, "_short_opts", [])) + list(getattr(opt, "_long_opts", [])),
+ "description": getattr(opt, "help", None),
+ "group": group,
+ "ignored": any(
+ f in ignored_flags for f in getattr(opt, "_short_opts", []) + getattr(opt, "_long_opts", [])
+ ),
+ }
+ for opt in opts
+ if (
+ (getattr(opt, "_short_opts", []) or getattr(opt, "_long_opts", []))
+ and getattr(opt, "help", None)
+ and "SUPPRESSHELP" not in getattr(opt, "help", "")
+ )
+ ]
+
+ return collect(parser.option_list, "root") + [
+ entry for grp in parser.option_groups for entry in collect(grp.option_list, grp.title or "other")
+ ]
diff --git a/app/routes/api/yt_dlp.py b/app/routes/api/yt_dlp.py
index 6a669bcb..49291c20 100644
--- a/app/routes/api/yt_dlp.py
+++ b/app/routes/api/yt_dlp.py
@@ -144,7 +144,7 @@ async def get_info(request: Request, cache: Cache, config: Config) -> Response:
"ttl_left": data.get("_cached", {}).get("expires", time.time() + 300) - time.time(),
"expires": data.get("_cached", {}).get("expires", time.time() + 300),
}
- return web.Response(body=json.dumps(data, indent=4, default=str), status=web.HTTPOk.status_code)
+ 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})
@@ -203,7 +203,7 @@ async def get_info(request: Request, cache: Cache, config: Config) -> Response:
cache.set(key=key, value=data, ttl=300)
- return web.Response(body=json.dumps(data, indent=4, default=str), status=web.HTTPOk.status_code)
+ return web.json_response(body=json.dumps(data, indent=4, default=str), status=web.HTTPOk.status_code)
except Exception as e:
LOG.exception(e)
LOG.error(f"Error encountered while getting video info for '{url}'. '{e!s}'.")
@@ -296,3 +296,19 @@ async def archive_recheck(cache: Cache) -> Response:
response.append({id: bool(data.get("id", None)) if isinstance(data, dict) else False})
return web.json_response(data=response, status=web.HTTPOk.status_code)
+
+
+@route("GET", "api/yt-dlp/options/", "get_options")
+async def get_options() -> Response:
+ """
+ Get the yt-dlp CLI options.
+
+ Returns:
+ Response: The response object with the yt-dlp CLI options.
+
+ """
+ from app.library.ytdlp import ytdlp_options
+
+ return web.json_response(
+ body=json.dumps(ytdlp_options(), indent=4, default=str), status=web.HTTPOk.status_code
+ )
diff --git a/ui/app/assets/css/style.css b/ui/app/assets/css/style.css
index e2b67b87..f4db7fe0 100644
--- a/ui/app/assets/css/style.css
+++ b/ui/app/assets/css/style.css
@@ -357,3 +357,10 @@ table.is-fixed {
div.is-centered {
justify-content: center;
}
+
+
+.modal-content-max {
+ width: max-content;
+ padding: 0;
+ margin: 0;
+}
diff --git a/ui/app/components/ConditionForm.vue b/ui/app/components/ConditionForm.vue
index 4ede9228..ee1edaa7 100644
--- a/ui/app/components/ConditionForm.vue
+++ b/ui/app/components/ConditionForm.vue
@@ -59,7 +59,7 @@
-
+ The name that refers to this condition.
@@ -81,7 +81,7 @@
-
+ yt-dlp [--match-filters] logic.
@@ -92,19 +92,20 @@
-
+
- If the filter is matched, these options will be used.
- This will override the command options for yt-dlp given with the
- URL. it's recommended to use presets and keep that field with url empty if you plan to use this
- feature.
-
+ Not all options are supported some are
+ ignored. Use with caution.
+
+
+ Command options for yt-dlp -
+
+
+
- Check this
- page for more info. Not all options are supported
- some are
- ignored. Use with caution these options can break yt-dlp or the frontend.
-
-
+ Not all options are supported some are
+ ignored. Use with caution.
@@ -230,6 +232,9 @@
@cancel="() => dialog_confirm.visible = false" />
showFields = false" />
+
+
+
@@ -252,10 +257,11 @@ const showAdvanced = useStorage('show_advanced', false)
const separator = useStorage('url_separator', separators[0]?.value ?? ',')
const auto_start = useStorage('auto_start', true)
const show_description = useStorage('show_description', true)
+const dlFields = useStorage>('dl_fields', {})
const addInProgress = ref(false)
const showFields = ref(false)
-const dlFields = useStorage>('dl_fields', {})
+const showOptions = ref(false)
const dlFieldsExtra = ['--no-download-archive']
const form = useStorage('local_config_v1', {
diff --git a/ui/app/components/PresetForm.vue b/ui/app/components/PresetForm.vue
index 4b33b200..1d48fc05 100644
--- a/ui/app/components/PresetForm.vue
+++ b/ui/app/components/PresetForm.vue
@@ -96,7 +96,7 @@
-
+ The name to refers to this preset of settings.
@@ -113,7 +113,7 @@
-
+ Use this folder if non is given with URL. Leave empty to use default download path. Default
download path {{ config.app.download_path }}.
@@ -131,7 +131,7 @@
-
+ Use this output template if non are given with URL. if not set, it will defaults to
{{ config.app.output_template }}.
@@ -146,20 +146,19 @@
-
+
- yt-dlp cli arguments. Check this page.
- For more info. Not all options are supported some are ignored. Use
- with caution those arguments can break yt-dlp or the frontend.
-
+ Not all options are supported some are
+ ignored. Use with caution.
@@ -174,7 +173,7 @@
-
+ Use this cookies if non are given with the URL. Use the
@@ -195,7 +194,7 @@
-
+ Use this field to help users to understand how to use this preset.
@@ -227,6 +226,10 @@
+
+
+
+
@@ -252,6 +255,7 @@ const form = reactive(JSON.parse(JSON.stringify(props.preset)))
const import_string = ref('')
const showImport = useStorage('showImport', false)
const selected_preset = ref('')
+const showOptions = ref(false)
const checkInfo = async (): Promise => {
for (const key of ['name']) {
diff --git a/ui/app/components/TaskForm.vue b/ui/app/components/TaskForm.vue
index 02205c4a..a37425c9 100644
--- a/ui/app/components/TaskForm.vue
+++ b/ui/app/components/TaskForm.vue
@@ -234,20 +234,20 @@
-
+
- yt-dlp cli arguments. Check this page.
- For more info. Not all options are supported some are ignored. Use
- with caution those arguments can break yt-dlp or the frontend.
-
+ Not all options are supported some are
+ ignored. Use with caution.