diff --git a/app/library/HttpAPI.py b/app/library/HttpAPI.py index b7a60fac..90677263 100644 --- a/app/library/HttpAPI.py +++ b/app/library/HttpAPI.py @@ -918,11 +918,6 @@ class HttpAPI(Common): {"error": "name is required.", "data": item}, status=web.HTTPBadRequest.status_code ) - if not item.get("format"): - return web.json_response( - {"error": "format is required.", "data": item}, status=web.HTTPBadRequest.status_code - ) - if not item.get("id", None) or not validate_uuid(item.get("id"), version=4): item["id"] = str(uuid.uuid4()) @@ -936,7 +931,7 @@ class HttpAPI(Common): presets.append(Preset(**item)) try: - presets = cls.save(presets=presets).load().get_all() + presets = cls.save(items=presets).load().get_all() except Exception as e: LOG.exception(e) return web.json_response( @@ -1728,7 +1723,7 @@ class HttpAPI(Common): LOG.error(str(e)) return web.json_response(data={"message": str(e)}, status=web.HTTPInternalServerError.status_code) - url = "https://www.youtube.com/account" + url = "https://www.youtube.com/paid_memberships" try: opts = { @@ -1745,8 +1740,8 @@ class HttpAPI(Common): LOG.debug(f"Checking '{url}' redirection.") response = await client.request(method="GET", url=url, follow_redirects=False) return web.json_response( - data={"message": "Authenticated." if response.status_code == 200 else "Not authenticated."}, - status=200 if response.status_code == 200 else 401, + data={"message": "Authenticated." if 200 == response.status_code else "Not authenticated."}, + status=200 if 200 == response.status_code else 401, ) except Exception as e: LOG.error(f"Failed to request '{url}'. '{e}'.") diff --git a/app/library/Presets.py b/app/library/Presets.py index f3670857..6202dfbc 100644 --- a/app/library/Presets.py +++ b/app/library/Presets.py @@ -24,9 +24,6 @@ class Preset: name: str """The name of the preset.""" - format: str - """The format of the preset.""" - folder: str = "" """The default download folder to use if non is given.""" @@ -56,13 +53,13 @@ class Presets(metaclass=Singleton): This class is used to manage the presets. """ - _presets: list[Preset] = [] + _items: list[Preset] = [] """The list of presets.""" _instance = None """The instance of the class.""" - _default_presets: list[Preset] = [] + _default: list[Preset] = [] def __init__(self, file: str | None = None, config: Config | None = None): Presets._instance = self @@ -77,13 +74,14 @@ class Presets(metaclass=Singleton): except Exception: pass - with open(os.path.join(os.path.dirname(__file__), "presets.json")) as f: + default_file = os.path.join(os.path.dirname(__file__), "presets.json") + with open(default_file) as f: for i, preset in enumerate(json.load(f)): try: self.validate(preset) - self._default_presets.append(Preset(**preset)) + self._default.append(Preset(**preset)) except Exception as e: - LOG.error(f"Failed to parse default preset '{i}'. '{e!s}'.") + LOG.error(f"Failed to parse '{default_file}:{i}'. '{e!s}'.") continue def event_handler(_, __): @@ -111,7 +109,7 @@ class Presets(metaclass=Singleton): def attach(self, _: web.Application): """ - Attach the work to the aiohttp application. + Attach the class to the aiohttp application. Args: _ (web.Application): The aiohttp application. @@ -123,12 +121,12 @@ class Presets(metaclass=Singleton): self.load() def get_all(self) -> list[Preset]: - """Return the presets.""" - return self._default_presets + self._presets + """Return the items.""" + return self._default + self._items def load(self) -> "Presets": """ - Load the Presets. + Load the items. Returns: Presets: The current instance. @@ -139,16 +137,15 @@ class Presets(metaclass=Singleton): if not os.path.exists(self._file) or os.path.getsize(self._file) < 10: return self - LOG.info(f"Loading presets from '{self._file}'.") + LOG.info(f"Loading '{self._file}'.") try: with open(self._file) as f: presets = json.load(f) except Exception as e: - LOG.error(f"Failed to parse presets from '{self._file}'. '{e}'.") + LOG.error(f"Failed to parse '{self._file}'. '{e}'.") return self if not presets or len(presets) < 1: - LOG.info(f"No presets were defined in '{self._file}'.") return self need_save = False @@ -160,134 +157,144 @@ class Presets(metaclass=Singleton): need_save = True preset, preset_status = clean_item(preset, keys=("args", "postprocessors")) + if preset.get("format"): + if not preset.get("cli"): + preset.update({"cli": f"--format {preset['format']}"}) + else: + preset["cli"] = f"--format '{preset['format']}'\n" + preset["cli"] + + preset["cli"] = str(preset["cli"]).strip() + + preset.pop("format") + need_save = True + preset = Preset(**preset) if preset_status: need_save = True - self._presets.append(preset) + self._items.append(preset) except Exception as e: - LOG.error(f"Failed to parse preset at list position '{i}'. '{e!s}'.") + LOG.error(f"Failed to parse '{self._file}:{i}'. '{e!s}'.") continue if need_save: - LOG.info("Saving presets due to format, or id change.") - self.save(self._presets) + LOG.info(f"Saving '{self._file}' due to changes.") + self.save(self._items) return self def clear(self) -> "Presets": """ - Clear all presets + Clear all items. Returns: Presets: The current instance. """ - if len(self._presets) < 1: + if len(self._items) < 1: return self - self._presets.clear() + self._items.clear() return self - def validate(self, preset: Preset | dict) -> bool: + def validate(self, item: Preset | dict) -> bool: """ - Validate the preset. + Validate the item. Args: - preset (Preset|dict): The preset to validate. + item (Preset|dict): The item to validate. Returns: - bool: True if the preset is valid, False otherwise. + bool: True if valid + + Raises: + ValueError: If the item is not valid. """ - if not isinstance(preset, dict): - if not isinstance(preset, Preset): - msg = f"Invalid preset type. Was expecting a (Preset|dict), but got '{type(preset).__name__}'." + if not isinstance(item, dict): + if not isinstance(item, Preset): + msg = f"Unexpected '{type(item).__name__}' type was given." raise ValueError(msg) # noqa: TRY004 - preset = preset.serialize() + item = item.serialize() - if not preset.get("id"): + if not item.get("id"): msg = "No id found." raise ValueError(msg) - if not preset.get("name"): + if not item.get("name"): msg = "No name found." raise ValueError(msg) - if not preset.get("format"): - msg = "No format found." - raise ValueError(msg) - - if preset.get("cli"): + if item.get("cli"): try: - arg_converter(args=preset.get("cli")) + arg_converter(args=item.get("cli")) except Exception as e: msg = f"Invalid cli options. '{e!s}'." raise ValueError(msg) from e return True - def save(self, presets: list[Preset | dict]) -> "Presets": + def save(self, items: list[Preset | dict]) -> "Presets": """ - Save the presets. + Save the items. Args: - presets (list[Preset]): The presets to save. + items (list[Preset]): The items to save. Returns: Presets: The current instance. """ - for i, preset in enumerate(presets): + for i, preset in enumerate(items): try: if not isinstance(preset, Preset): preset = Preset(**preset) - presets[i] = preset + items[i] = preset except Exception as e: - LOG.error(f"Failed to save preset '{i}' due to parsing error. '{e!s}'.") + LOG.error(f"Failed to save item '{i}' due to parsing error. '{e!s}'.") continue try: self.validate(preset) except ValueError as e: - LOG.error(f"Failed to validate preset '{i}: {preset.name}'. '{e}'.") + LOG.error(f"Failed to validate item '{i}: {preset.name}'. '{e}'.") continue try: with open(self._file, "w") as f: - json.dump(obj=[preset.serialize() for preset in presets if preset.default is False], fp=f, indent=4) + json.dump(obj=[preset.serialize() for preset in items if preset.default is False], fp=f, indent=4) - LOG.info(f"Presets saved to '{self._file}'.") + LOG.info(f"Saved '{self._file}'.") except Exception as e: - LOG.error(f"Failed to save presets to '{self._file}'. '{e!s}'.") + LOG.error(f"Failed to save '{self._file}'. '{e!s}'.") return self def has(self, id_or_name: str) -> bool: """ - Check if the preset exists by id or name. + Check if the item exists by id or name. Args: - id_or_name (str): The id or name of the preset. + id_or_name (str): The id or name of the item. Returns: - bool: True if the preset exists, False otherwise. + bool: True if exists, False otherwise. """ return self.get(id_or_name) is not None def get(self, id_or_name: str) -> Preset | None: """ - Get the preset by id or name. + Get the item by id or name. Args: - id_or_name (str): The id or name of the preset. + id_or_name (str): The id or name of the item. Returns: - Preset|None: The preset if found, None otherwise. + Preset|None: The item if found, None otherwise. """ if not id_or_name: diff --git a/app/library/YTDLPOpts.py b/app/library/YTDLPOpts.py index 4e4003ba..4c4c1894 100644 --- a/app/library/YTDLPOpts.py +++ b/app/library/YTDLPOpts.py @@ -135,9 +135,6 @@ class YTDLPOpts(metaclass=Singleton): self._preset_opts["cookiefile"] = str(file) - if preset.format: - self._preset_opts["format"] = preset.format - if preset.template: self._preset_opts["outtmpl"] = {"default": preset.template, "chapter": self._config.output_template_chapter} diff --git a/app/library/presets.json b/app/library/presets.json index 86c6492c..1ece33bd 100644 --- a/app/library/presets.json +++ b/app/library/presets.json @@ -2,7 +2,6 @@ { "id": "3e163c6c-64eb-4448-924f-814b629b3810", "name": "default", - "format": "default", "folder": "", "template": "", "cookies": "", @@ -12,41 +11,37 @@ { "id": "5bf9c42b-8852-468a-99f5-915622dfba25", "name": "Best video and audio", - "format": "bv+ba/b", "folder": "", "template": "", "cookies": "", - "cli": "", + "cli": "--format 'bv+ba/b'", "default": true }, { "id": "441675ed-b739-40f0-a0b0-1ecfcb9dc48b", "name": "1080p H264/m4a or best available", - "format": "bv[height<=1080][ext=mp4]+ba[ext=m4a]/b[ext=mp4]/b[ext=webm]", "folder": "", "template": "", "cookies": "", - "cli": "-S vcodec:h264", + "cli": "-S vcodec:h264 --format 'bv[height<=1080][ext=mp4]+ba[ext=m4a]/b[ext=mp4]/b[ext=webm]'", "default": true }, { "id": "9719fcc3-4cf2-4d88-b1e4-74dff3dba00e", "name": "720p h264/m4a or best available", - "format": "bv[height<=720][ext=mp4]+ba[ext=m4a]/b[ext=mp4]/b[ext=webm]", "folder": "", "template": "", "cookies": "", - "cli": "-S vcodec:h264", + "cli": "-S vcodec:h264 --format 'bv[height<=720][ext=mp4]+ba[ext=m4a]/b[ext=mp4]/b[ext=webm]'", "default": true }, { "id": "a6fd4b25-2b3e-458d-bb57-b75e41cc4330", "name": "Audio only", - "format": "bestaudio/best", "folder": "", "template": "", "cookies": "", - "cli": "--extract-audio --add-chapters --embed-metadata --embed-thumbnail", + "cli": "--extract-audio --add-chapters --embed-metadata --embed-thumbnail --format 'bestaudio/best'", "default": true } ] diff --git a/ui/components/PresetForm.vue b/ui/components/PresetForm.vue index 64dc703d..d43c344c 100644 --- a/ui/components/PresetForm.vue +++ b/ui/components/PresetForm.vue @@ -49,7 +49,7 @@ -
[--format, -f] video format code. see default to let yt-dlp choose the best format.
-
-