support format key in ytdlp.json, using the key will ignore the selected preset.

This commit is contained in:
ArabCoders 2025-02-27 14:58:06 +03:00
parent 0cb90645cd
commit c5924b02cc
6 changed files with 92 additions and 19 deletions

View file

@ -395,7 +395,7 @@ class DownloadQueue(metaclass=Singleton):
"func": lambda _, msg: logs.append(msg), "func": lambda _, msg: logs.append(msg),
"level": logging.WARNING, "level": logging.WARNING,
}, },
**merge_config(self.config.ytdl_options, config), **get_opts(preset, merge_config(self.config.ytdl_options, config)),
} }
if cookies: if cookies:
@ -408,7 +408,7 @@ class DownloadQueue(metaclass=Singleton):
entry = await asyncio.wait_for( entry = await asyncio.wait_for(
fut=asyncio.get_running_loop().run_in_executor( fut=asyncio.get_running_loop().run_in_executor(
None, extract_info, get_opts(preset, yt_conf), url, bool(self.config.ytdl_debug) None, extract_info, yt_conf, url, bool(self.config.ytdl_debug)
), ),
timeout=self.config.extract_info_timeout, timeout=self.config.extract_info_timeout,
) )

View file

@ -32,7 +32,15 @@ from .Playlist import Playlist
from .Segments import Segments from .Segments import Segments
from .Subtitle import Subtitle from .Subtitle import Subtitle
from .Tasks import Task, Tasks from .Tasks import Task, Tasks
from .Utils import StreamingError, arg_converter, calc_download_path, get_video_info, validate_url, validate_uuid from .Utils import (
IGNORED_KEYS,
StreamingError,
arg_converter,
calc_download_path,
get_video_info,
validate_url,
validate_uuid,
)
LOG = logging.getLogger("http_api") LOG = logging.getLogger("http_api")
MIME = magic.Magic(mime=True) MIME = magic.Magic(mime=True)
@ -347,11 +355,28 @@ class HttpAPI(Common):
return web.json_response(data={"error": "args param is required."}, status=web.HTTPBadRequest.status_code) return web.json_response(data={"error": "args param is required."}, status=web.HTTPBadRequest.status_code)
try: try:
return web.json_response(data=arg_converter(args), status=web.HTTPOk.status_code) response = {"opts": {}, "output_template": None, "download_path": None}
data = arg_converter(args)
if "outtmpl" in data and "default" in data["outtmpl"]:
response["output_template"] = data["outtmpl"]["default"]
if "paths" in data and "home" in data["paths"]:
response["download_path"] = data["paths"]["home"]
for key in data:
if key in IGNORED_KEYS:
continue
if not key.startswith("_"):
response["opts"][key] = data[key]
return web.json_response(data=response, status=web.HTTPOk.status_code)
except Exception as e: except Exception as e:
err = str(e).strip() err = str(e).strip()
err = err.split("\n")[-1] if "\n" in err else err err = err.split("\n")[-1] if "\n" in err else err
LOG.error(f"Failed to convert args. '{err}'.") LOG.error(f"Failed to convert args. '{err}'.")
LOG.exception(e)
return web.json_response( return web.json_response(
data={"error": f"Failed to convert args. '{err}'."}, status=web.HTTPBadRequest.status_code data={"error": f"Failed to convert args. '{err}'."}, status=web.HTTPBadRequest.status_code
) )

View file

@ -23,7 +23,6 @@ IGNORED_KEYS: tuple[str] = (
"outtmpl", "outtmpl",
"progress_hooks", "progress_hooks",
"postprocessor_hooks", "postprocessor_hooks",
"format",
"download_archive", "download_archive",
) )
YTDLP_INFO_CLS: yt_dlp.YoutubeDL = None YTDLP_INFO_CLS: yt_dlp.YoutubeDL = None
@ -45,6 +44,11 @@ def get_opts(preset: str, ytdl_opts: dict) -> dict:
ytdl extra options ytdl extra options
""" """
if "format" in ytdl_opts and len(ytdl_opts["format"]) > 2:
format = ytdl_opts["format"]
LOG.info(f"Format '{format}' was given via yt-dlp options. Therefore, the preset will be ignored.")
return ytdl_opts
opts = copy.deepcopy(ytdl_opts) opts = copy.deepcopy(ytdl_opts)
if "default" == preset: if "default" == preset:

View file

@ -20,8 +20,9 @@
</div> </div>
<div class="control is-expanded"> <div class="control is-expanded">
<div class="select is-fullwidth"> <div class="select is-fullwidth">
<select id="preset" class="is-fullwidth" :disabled="!socket.isConnected || addInProgress" <select id="preset" class="is-fullwidth"
v-model="selectedPreset"> :disabled="!socket.isConnected || addInProgress || hasFormatInConfig" v-model="selectedPreset"
v-tooltip.bottom="hasFormatInConfig ? 'Presets are disabled. Format key is present in the config.' : ''">
<option v-for="item in config.presets" :key="item.name" :value="item.name"> <option v-for="item in config.presets" :key="item.name" :value="item.name">
{{ item.name }} {{ item.name }}
</option> </option>
@ -81,7 +82,7 @@
<label class="label is-inline" for="ytdlpConfig" <label class="label is-inline" for="ytdlpConfig"
v-tooltip="'Extends current global yt-dlp config. (JSON)'"> v-tooltip="'Extends current global yt-dlp config. (JSON)'">
JSON yt-dlp config or CLI options. JSON yt-dlp config or CLI options.
<NuxtLink v-if="ytdlpConfig && !ytdlpConfig.trim().startsWith('{')" @click="convertOptions()"> <NuxtLink v-if="ytdlpConfig && ytdlpConfig.trim() && !ytdlpConfig.trim().startsWith('{')" @click="convertOptions()">
Convert to JSON Convert to JSON
</NuxtLink> </NuxtLink>
</label> </label>
@ -93,8 +94,10 @@
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>Extends current global yt-dlp config with given options. Some fields are ignored like <span>Extends current global yt-dlp config with given options. Some fields are ignored like
<code>format</code> <code>cookiefile</code>, <code>paths</code>, and <code>outtmpl</code> etc. <code>cookiefile</code>, <code>paths</code>, and <code>outtmpl</code> etc. Warning: Use with caution
Warning: Use with caution some of those options can break yt-dlp or the frontend.</span> some of those options can break yt-dlp or the frontend. If <code>Format</code> key is present
in the config, <span class="has-text-danger">the preset and all it's options will be
ignored</span>.</span>
</span> </span>
</div> </div>
</div> </div>
@ -242,7 +245,15 @@ const convertOptions = async () => {
try { try {
convertInProgress.value = true convertInProgress.value = true
ytdlpConfig.value = await convertCliOptions(ytdlpConfig.value) const response = await convertCliOptions(ytdlpConfig.value)
ytdlpConfig.value = JSON.stringify(response.opts, null, 2)
if (response.output_template) {
output_template.value = response.output_template
}
if (response.download_path) {
downloadPath.value = response.download_path
}
} catch (e) { } catch (e) {
toast.error(e.message) toast.error(e.message)
} finally { } finally {
@ -262,4 +273,16 @@ onUnmounted(() => {
socket.off('status', statusHandler) socket.off('status', statusHandler)
socket.off('error', unlockDownload) socket.off('error', unlockDownload)
}) })
const hasFormatInConfig = computed(() => {
if (!ytdlpConfig.value) {
return false
}
try {
const config = JSON.parse(ytdlpConfig.value)
return "format" in config
} catch (e) {
return false
}
})
</script> </script>

View file

@ -32,7 +32,7 @@
<div class="column is-6-tablet is-12-mobile"> <div class="column is-6-tablet is-12-mobile">
<div class="field"> <div class="field">
<label class="label is-inline" for="url"> <label class="label is-inline" for="url">
Channel or Playlist URL URL
</label> </label>
<div class="control has-icons-left"> <div class="control has-icons-left">
<input type="url" class="input" id="url" v-model="form.url" :disabled="addInProgress"> <input type="url" class="input" id="url" v-model="form.url" :disabled="addInProgress">
@ -40,7 +40,7 @@
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>The YouTube channel or playlist URL</span> <span>The channel or playlist URL.</span>
</span> </span>
</div> </div>
</div> </div>
@ -69,7 +69,9 @@
</label> </label>
<div class="control has-icons-left"> <div class="control has-icons-left">
<div class="select is-fullwidth"> <div class="select is-fullwidth">
<select id="preset" class="is-fullwidth" v-model="form.preset" :disabled="addInProgress"> <select id="preset" class="is-fullwidth" v-model="form.preset"
:disabled="addInProgress || hasFormatInConfig"
v-tooltip.bottom="hasFormatInConfig ? 'Presets are disabled. Format key is present in the config.' : ''">
<option v-for="item in config.presets" :key="item.name" :value="item.name"> <option v-for="item in config.presets" :key="item.name" :value="item.name">
{{ item.name }} {{ item.name }}
</option> </option>
@ -79,7 +81,8 @@
</div> </div>
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span>Select the preset to use for this URL.</span> <span>Select the preset to use for this URL. The preset will be ignored if format key is present in
config.</span>
</span> </span>
</div> </div>
</div> </div>
@ -138,8 +141,8 @@
<span class="help"> <span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>
<span> Extends current global yt-dlp config with given options. Some fields are ignored like <span> Extends current global yt-dlp config with given options. Some fields are ignored like
<code>format</code> <code>cookiefile</code>, <code>paths</code>, and <code>outtmpl</code> etc. <code>cookiefile</code>, <code>paths</code>, and <code>outtmpl</code> etc. Warning: Use with caution
Warning: Use with caution some of those options can break yt-dlp or the frontend.</span> some of those options can break yt-dlp or the frontend.</span>
</span> </span>
</div> </div>
</div> </div>
@ -278,7 +281,14 @@ const convertOptions = async () => {
try { try {
convertInProgress.value = true convertInProgress.value = true
form.config = await convertCliOptions(form.config) const response = await convertCliOptions(form.config)
form.config = JSON.stringify(response.opts, null, 2)
if (response.output_template) {
form.template = response.output_template
}
if (response.download_path) {
form.folder = response.download_path
}
} catch (e) { } catch (e) {
toast.error(e.message) toast.error(e.message)
} finally { } finally {
@ -286,4 +296,15 @@ const convertOptions = async () => {
} }
} }
const hasFormatInConfig = computed(() => {
if (!form.config) {
return false
}
try {
const config = JSON.parse(form.config)
return "format" in config
} catch (e) {
return false
}
})
</script> </script>

View file

@ -400,7 +400,7 @@ const convertCliOptions = async opts => {
throw new Error(`Error: (${response.status}): ${data.error}`) throw new Error(`Error: (${response.status}): ${data.error}`)
} }
return JSON.stringify(data, null, 2) return data
} }
export { export {