diff --git a/app/library/DownloadQueue.py b/app/library/DownloadQueue.py
index 491910ff..edf4214a 100644
--- a/app/library/DownloadQueue.py
+++ b/app/library/DownloadQueue.py
@@ -392,7 +392,7 @@ class DownloadQueue(metaclass=Singleton):
{ "status": "text" }
"""
- _preset = Presets.get_instance().get(name=preset)
+ _preset = Presets.get_instance().get(preset)
config = config if config else {}
if cli:
diff --git a/app/library/HttpAPI.py b/app/library/HttpAPI.py
index 74387c55..30f31e88 100644
--- a/app/library/HttpAPI.py
+++ b/app/library/HttpAPI.py
@@ -501,7 +501,7 @@ class HttpAPI(Common):
preset = request.query.get("preset")
if preset:
- exists = Presets.get_instance().get(name=preset)
+ exists = Presets.get_instance().get(preset)
if not exists:
return web.json_response(
data={"status": False, "message": f"Preset '{preset}' does not exist."},
@@ -674,7 +674,7 @@ class HttpAPI(Common):
preset = request.query.get("preset")
if preset:
- exists = Presets.get_instance().get(name=preset)
+ exists = Presets.get_instance().get(preset)
if not exists:
return web.json_response(
data={"status": False, "message": f"Preset '{preset}' does not exist."},
@@ -790,18 +790,6 @@ class HttpAPI(Common):
if not item.get("id", None) or not validate_uuid(item.get("id"), version=4):
item["id"] = str(uuid.uuid4())
- if not item.get("args", None) or str(item.get("args")).strip() == "":
- item["args"] = {}
-
- if item.get("args", None) and isinstance(item.get("args"), str):
- item["args"] = json.loads(item.get("args"))
-
- if not item.get("postprocessors", None) or str(item.get("postprocessors")).strip() == "":
- item["postprocessors"] = []
-
- if item.get("postprocessors", None) and isinstance(item.get("postprocessors"), str):
- item["postprocessors"] = json.loads(item.get("postprocessors"))
-
try:
cls.validate(item)
except ValueError as e:
diff --git a/app/library/Presets.py b/app/library/Presets.py
index 83f65e16..cfec885b 100644
--- a/app/library/Presets.py
+++ b/app/library/Presets.py
@@ -11,6 +11,7 @@ from .config import Config
from .encoder import Encoder
from .Events import EventBus, Events
from .Singleton import Singleton
+from .Utils import arg_converter, clean_item
LOG = logging.getLogger("presets")
@@ -26,12 +27,6 @@ class Preset:
format: str
"""The format of the preset."""
- args: dict[str, list[str] | bool] = field(default_factory=dict)
- """The arguments of the preset."""
-
- postprocessors: list = field(default_factory=list)
- """The postprocessors of the preset."""
-
folder: str = ""
"""The default download folder to use if non is given."""
@@ -83,13 +78,19 @@ class Presets(metaclass=Singleton):
pass
with open(os.path.join(os.path.dirname(__file__), "presets.json")) as f:
- self._default_presets = [Preset(**preset) for preset in json.load(f)]
+ for i, preset in enumerate(json.load(f)):
+ try:
+ self.validate(preset)
+ self._default_presets.append(Preset(**preset))
+ except Exception as e:
+ LOG.error(f"Failed to parse default preset '{i}'. '{e!s}'.")
+ continue
- EventBus.get_instance().subscribe(
- Events.PRESETS_ADD,
- lambda data, _, **kwargs: self.add(**data.data), # noqa: ARG005
- f"{__class__.__name__}.save",
- )
+ def event_handler(_, __):
+ msg = "Not implemented"
+ raise Exception(msg)
+
+ EventBus.get_instance().subscribe(Events.PRESETS_ADD, event_handler, f"{__class__.__name__}.save")
@staticmethod
def get_instance() -> "Presets":
@@ -150,22 +151,27 @@ class Presets(metaclass=Singleton):
LOG.info(f"No presets were defined in '{self._file}'.")
return self
- needSaving = False
+ need_save = False
for i, preset in enumerate(presets):
try:
if "id" not in preset:
preset["id"] = str(uuid.uuid4())
- needSaving = True
+ need_save = True
+ preset, preset_status = clean_item(preset, keys=("args", "postprocessors"))
preset = Preset(**preset)
+
+ if preset_status:
+ need_save = True
+
self._presets.append(preset)
except Exception as e:
LOG.error(f"Failed to parse preset at list position '{i}'. '{e!s}'.")
continue
- if needSaving:
- LOG.info("Saving presets due to missing ids.")
+ if need_save:
+ LOG.info("Saving presets due to format, or id change.")
self.save(self._presets)
return self
@@ -198,7 +204,7 @@ class Presets(metaclass=Singleton):
"""
if not isinstance(preset, dict):
if not isinstance(preset, Preset):
- msg = "Invalid preset type."
+ msg = f"Invalid preset type. Was expecting a (Preset|dict), but got '{type(preset).__name__}'."
raise ValueError(msg) # noqa: TRY004
preset = preset.serialize()
@@ -215,18 +221,8 @@ class Presets(metaclass=Singleton):
msg = "No format found."
raise ValueError(msg)
- if preset.get("args") and not isinstance(preset.get("args"), dict):
- msg = "Invalid args type. expected dict."
- raise ValueError(msg)
-
- if preset.get("postprocessors") and not isinstance(preset.get("postprocessors"), list):
- msg = "Invalid postprocessors type. expected list."
- raise ValueError(msg)
-
if preset.get("cli"):
try:
- from .Utils import arg_converter
-
arg_converter(args=preset.get("cli"))
except Exception as e:
msg = f"Invalid cli options. '{e!s}'."
@@ -270,26 +266,24 @@ class Presets(metaclass=Singleton):
return self
- def get(self, id: str | None = None, name: str | None = None) -> Preset | None:
+ def get(self, id_or_name: str) -> Preset | None:
"""
Get the preset by id or name.
Args:
- id (str|None): The id of the preset.
- name (str|None): The name of the preset.
+ id_or_name (str): The id or name of the preset.
Returns:
Preset|None: The preset if found, None otherwise.
"""
- if not id and not name:
+ if not id_or_name:
return None
for preset in self.get_all():
- if preset.id == id:
- return preset
+ if id_or_name not in (preset.id, preset.name):
+ continue
- if preset.name == name:
- return preset
+ return preset
return None
diff --git a/app/library/Tasks.py b/app/library/Tasks.py
index dc695114..6ffb1e0e 100644
--- a/app/library/Tasks.py
+++ b/app/library/Tasks.py
@@ -15,6 +15,7 @@ from .encoder import Encoder
from .Events import EventBus, Events, error, info, success
from .Scheduler import Scheduler
from .Singleton import Singleton
+from .Utils import clean_item
LOG = logging.getLogger("tasks")
@@ -140,13 +141,13 @@ class Tasks(metaclass=Singleton):
LOG.info(f"No tasks were defined in '{self._file}'.")
return self
- need_update = False
+ need_save = False
for i, task in enumerate(tasks):
try:
- task, task_status = self.clean_task(task)
+ task, task_status = clean_item(task, keys=("cookies", "config"))
task = Task(**task)
if task_status:
- need_update = True
+ need_save = True
except Exception as e:
LOG.error(f"Failed to parse task at list position '{i}'. '{e!s}'.")
continue
@@ -160,7 +161,7 @@ class Tasks(metaclass=Singleton):
LOG.exception(e)
LOG.error(f"Failed to queue task '{i}: {task.name}'. '{e!s}'.")
- if need_update:
+ if need_save:
LOG.info("Updating tasks file to remove old keys.")
self.save(self.get_all())
@@ -223,6 +224,7 @@ class Tasks(metaclass=Singleton):
if task.get("cli"):
try:
from .Utils import arg_converter
+
arg_converter(args=task.get("cli"))
except Exception as e:
msg = f"Invalid cli options. '{e!s}'."
@@ -266,27 +268,6 @@ class Tasks(metaclass=Singleton):
return self
- def clean_task(self, task: dict) -> tuple[dict, bool]:
- """
- Clean the task from old keys.
-
- Args:
- task (dict): The task to clean.
-
- Returns:
- tuple[dict, bool]: The cleaned task and a status if the task was cleaned.
-
- """
- status = False
- removedKeys = ["cookies", "config"]
-
- for key in removedKeys:
- if key in task:
- status = True
- task.pop(key)
-
- return task, status
-
async def _runner(self, task: Task):
"""
Run the task.
diff --git a/app/library/Utils.py b/app/library/Utils.py
index 155f1ceb..a09e6bd6 100644
--- a/app/library/Utils.py
+++ b/app/library/Utils.py
@@ -522,8 +522,6 @@ def arg_converter(
bad_options.update(item.items())
- LOG.debug("Removed %i the following options: '%s'.", level, ", ".join(bad_options.values()))
-
for key in diff.copy():
if key not in bad_options:
continue
@@ -889,3 +887,41 @@ def get_files(base_path: str, dir: str | None = None):
)
return contents
+
+
+def clean_item(item: dict, keys: list | tuple) -> tuple[dict, bool]:
+ """
+ Remove given keys from a dictionary.
+ This function modifies the dictionary in place and returns a tuple
+ containing the modified dictionary and a boolean indicating
+ whether any keys were removed.
+
+ Args:
+ item (dict): The item to clean.
+ keys (list|tuple): The keys to remove.
+
+ Returns:
+ tuple[dict, bool]: The cleaned item and a the status of cleaning operation.
+
+ Raises:
+ TypeError: If item is not a dictionary or keys is not a list or tuple.
+
+ """
+ status = False
+
+ if not isinstance(item, dict):
+ msg = "Item must be a dictionary."
+ raise TypeError(msg)
+
+ if not isinstance(keys, list | tuple):
+ msg = "Keys must be a list or tuple."
+ raise TypeError(msg)
+
+ for key in keys:
+ if key not in item:
+ continue
+
+ status = True
+ item.pop(key)
+
+ return item, status
diff --git a/app/library/YTDLPOpts.py b/app/library/YTDLPOpts.py
index 2a87ca91..1e884828 100644
--- a/app/library/YTDLPOpts.py
+++ b/app/library/YTDLPOpts.py
@@ -78,7 +78,7 @@ class YTDLPOpts(metaclass=Singleton):
YTDLPOpts: The instance of the class
"""
- preset = Presets.get_instance().get(name=name)
+ preset = Presets.get_instance().get(name)
if not preset or "default" == name:
return self
@@ -118,25 +118,6 @@ class YTDLPOpts(metaclass=Singleton):
"temp": self._config.temp_path,
}
- # @Deprecated - To be removed in future versions.
- if not preset.cli:
- if preset.postprocessors and isinstance(preset.postprocessors, list) and len(preset.postprocessors) > 0:
- self._preset_opts["postprocessors"] = preset.postprocessors
-
- if preset.args and isinstance(preset.args, dict) and len(preset.args) > 0:
- bad_options = {k: v for d in REMOVE_KEYS for k, v in d.items()}
- removed_options = []
- for key, value in preset.args.items():
- if key in bad_options:
- removed_options.append(bad_options[key])
- continue
- self._preset_opts[key] = value
-
- if len(removed_options) > 0:
- LOG.warning(
- "Removed the following options '%s' from '%s' args.", ", ".join(removed_options), preset.name
- )
-
return self
def get_all(self, keep: bool = False) -> dict:
diff --git a/ui/components/PresetForm.vue b/ui/components/PresetForm.vue
index ba9447db..0cbd7439 100644
--- a/ui/components/PresetForm.vue
+++ b/ui/components/PresetForm.vue
@@ -27,13 +27,13 @@
-
+
-
@@ -51,31 +51,35 @@
-
-
+
+
-
- The name to refers to this custom settings.
+ The name to refers to this preset of settings.
-
-
+
+
-
The yt-dlp [--format, -f] video format code. see this
- url for more info.. Note, as this key is required, you can set the value to
- default to let yt-dlp choose the best format.
+ page for more info. Note, as this key is required, you can set the value to
+ default to let yt-dlp choose the best format.
@@ -83,12 +87,12 @@
-
+
-
@@ -101,19 +105,19 @@
-
+
-
Use this output template if non are given with URL. if not set, it will defaults to
{{ config.app.output_template }}.
- For more information visit this url.
+ For more information visit this page.
@@ -122,11 +126,12 @@
-
+
@@ -141,8 +146,10 @@
-
+
@@ -157,56 +164,7 @@
-
-
-
-
- The JSON yt-dlp config and JSON yt-dlp Post-Processors fields are
- deprecated and will be removed in the future. Please use the Command arguments for yt-dlp
- field instead. The deprecated fields will still be working for now but they will stop, we suggest
- that you migrate to the new field. as soon as possible to avoid any issues. No support will be
- given for the deprecated fields.
-
-
- If both fields are set, the Command arguments for yt-dlp field will take precedence over
- the deprecated fields. and when you click save it will remove the deprecated fields.
-
-
-
-
-
-
-
-
-
-
-
-
- Deprecated, use Command arguments for yt-dlp field instead.
-
-
-
-
-
-
-
-
-
-
-
- Deprecated, use Command arguments for yt-dlp field instead.
-
-
-
@@ -319,55 +277,11 @@ const checkInfo = async () => {
return;
}
- if (form?.cli && '' !== form.cli) {
- if ((has_data(copy?.args) || has_data(copy?.postprocessors))) {
- if (false === confirm('cli options are set, this will remove the JSON yt-dlp config and Post-Processors. Are you sure?')) {
- toast.warning('User cancelled the operation.')
- return
- }
- }
-
- if (copy?.args) {
- delete copy.args
- }
-
- if (copy?.postprocessors) {
- delete copy.postprocessors
- }
- }
- else {
- if (typeof copy.args === 'object') {
- copy.args = JSON.stringify(copy.args, null, 2);
- }
-
- if (typeof copy.postprocessors === 'object') {
- copy.postprocessors = JSON.stringify(copy.postprocessors, null, 2);
- }
-
- if (copy?.args) {
- try {
- copy.args = JSON.parse(copy.args)
- } catch (e) {
- toast.error(`Invalid JSON yt-dlp config. ${e.message}`)
- return;
- }
- }
-
- if (copy?.postprocessors) {
- try {
- copy.postprocessors = JSON.parse(copy.postprocessors)
- } catch (e) {
- toast.error(`Invalid JSON yt-dlp Post-Processors. ${e.message}`)
- return;
- }
- }
- }
-
- // trim all fields in copy only if they are strings
for (const key in copy) {
- if (typeof copy[key] === 'string') {
- copy[key] = copy[key].trim()
+ if (typeof copy[key] !== 'string') {
+ continue
}
+ copy[key] = copy[key].trim()
}
emitter('submit', { reference: toRaw(props.reference), preset: toRaw(copy) });
@@ -417,18 +331,13 @@ const importItem = async () => {
try {
const item = JSON.parse(val)
- console.log(item)
-
- if ('preset' !== item._type) {
- toast.error(`Invalid import string. Expected type 'preset', got '${item._type}'.`)
- import_string.value = ''
+ if (item?._type || 'preset' !== item._type) {
+ toast.error(`Invalid import string. Expected type 'preset', got '${item._type ?? 'unknown'}'.`)
return
}
- if (form.format || form.cli) {
- if (false === confirm('This will overwrite the current form fields. Are you sure?')) {
- return
- }
+ if ((form.format || form.cli) && false === confirm('This will overwrite the current data. Are you sure?')) {
+ return
}
if (item.name) {
diff --git a/ui/pages/presets.vue b/ui/pages/presets.vue
index a4d2b74e..c0197658 100644
--- a/ui/pages/presets.vue
+++ b/ui/pages/presets.vue
@@ -71,15 +71,6 @@ div.is-centered {
-
-
-
- The preset is using deprecated options. It is recommended to update the preset to use the
- new options. It will cease to work in future versions.
-
-
-
-
@@ -307,11 +298,6 @@ const exportItem = item => {
}
})
- if (has_data(data?.args) || has_data(data?.postprocessors)) {
- toast.error("v1.0 presets are no longer supported target for export. Please update your preset.")
- return
- }
-
let userData = {}
for (const key of Object.keys(data)) {