support format key in ytdlp.json, using the key will ignore the selected preset.
This commit is contained in:
parent
0cb90645cd
commit
c5924b02cc
6 changed files with 92 additions and 19 deletions
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue