diff --git a/src/ytdl_sub/entries/base_entry.py b/src/ytdl_sub/entries/base_entry.py index 276e9297..4bd608e7 100644 --- a/src/ytdl_sub/entries/base_entry.py +++ b/src/ytdl_sub/entries/base_entry.py @@ -1,5 +1,4 @@ from abc import ABC -from datetime import datetime from pathlib import Path from typing import Any from typing import Dict @@ -8,233 +7,15 @@ from typing import Type from typing import TypeVar from typing import final -from yt_dlp.utils import sanitize_filename - -from ytdl_sub.entries.variables.kwargs import DESCRIPTION -from ytdl_sub.entries.variables.kwargs import EPOCH -from ytdl_sub.entries.variables.kwargs import EXTRACTOR -from ytdl_sub.entries.variables.kwargs import IE_KEY -from ytdl_sub.entries.variables.kwargs import TITLE -from ytdl_sub.entries.variables.kwargs import UID -from ytdl_sub.entries.variables.kwargs import UPLOADER -from ytdl_sub.entries.variables.kwargs import UPLOADER_ID -from ytdl_sub.entries.variables.kwargs import UPLOADER_URL -from ytdl_sub.entries.variables.kwargs import WEBPAGE_URL - -# pylint: disable=no-member - - -def _sanitize_plex(string: str) -> str: - out = "" - for char in string: - match char: - case "0": - out += "0" - case "1": - out += "1" - case "2": - out += "2" - case "3": - out += "3" - case "4": - out += "4" - case "5": - out += "5" - case "6": - out += "6" - case "7": - out += "7" - case "8": - out += "8" - case "9": - out += "9" - case _: - out += char - return out - - -class BaseEntryVariables: - """ - Source variables are ``{variables}`` that contain metadata from downloaded media. - These variables can be used with fields that expect - :class:`~ytdl_sub.validators.string_formatter_validators.StringFormatterValidator`, - but not - :class:`~ytdl_sub.validators.string_formatter_validators.OverridesStringFormatterValidator`. - """ - - @property - def uid(self: "BaseEntry") -> str: - """ - Returns - ------- - str - The entry's unique ID - """ - return str(self.kwargs(UID)) - - @property - def uid_sanitized(self: "BaseEntry") -> str: - """ - Returns - ------- - str - The sanitized uid of the entry, which is safe to use for Unix and Windows file names. - """ - return sanitize_filename(self.uid) - - @property - def uid_sanitized_plex(self: "BaseEntry") -> str: - """ - Returns - ------- - str - The sanitized uid with additional sanitizing for Plex. Replaces numbers with - fixed-width numbers so Plex does not recognize them as season or episode numbers. - """ - return _sanitize_plex(self.uid_sanitized) - - @property - def extractor(self: "BaseEntry") -> str: - """ - Returns - ------- - str - The ytdl extractor name - """ - # pylint: disable=line-too-long - # Taken from https://github.com/yt-dlp/yt-dlp/blob/e6ab678e36c40ded0aae305bbb866cdab554d417/yt_dlp/YoutubeDL.py#L3514 - # pylint: enable=line-too-long - return self.kwargs_get(EXTRACTOR) or self.kwargs(IE_KEY) - - @property - def epoch(self: "BaseEntry") -> int: - """ - Returns - ------- - int - The unix epoch of when the metadata was scraped by yt-dlp. - """ - return self.kwargs(EPOCH) - - @property - def epoch_date(self: "BaseEntry") -> str: - """ - Returns - ------- - str - The epoch's date, in YYYYMMDD format. - """ - return datetime.utcfromtimestamp(self.epoch).strftime("%Y%m%d") - - @property - def epoch_hour(self: "BaseEntry") -> str: - """ - Returns - ------- - str - The epoch's hour, padded - """ - return datetime.utcfromtimestamp(self.epoch).strftime("%H") - - @property - def title(self: "BaseEntry") -> str: - """ - Returns - ------- - str - The title of the entry. If a title does not exist, returns its unique ID. - """ - return self.kwargs_get(TITLE, self.uid) - - @property - def title_sanitized(self) -> str: - """ - Returns - ------- - str - The sanitized title of the entry, which is safe to use for Unix and Windows file names. - """ - return sanitize_filename(self.title) - - @property - def title_sanitized_plex(self) -> str: - """ - Returns - ------- - str - The sanitized title with additional sanitizing for Plex. Replaces numbers with - fixed-width numbers so Plex does not recognize them as season or episode numbers. - """ - return _sanitize_plex(self.title_sanitized) - - @property - def webpage_url(self: "BaseEntry") -> str: - """ - Returns - ------- - str - The url to the webpage. - """ - return self.kwargs(WEBPAGE_URL) - - @property - def info_json_ext(self) -> str: - """ - Returns - ------- - str - The "info.json" extension - """ - return "info.json" - - @property - def description(self: "BaseEntry") -> str: - """ - Returns - ------- - str - The description if it exists. Otherwise, returns an emtpy string. - """ - return self.kwargs_get(DESCRIPTION, "") - - @property - def uploader_id(self: "BaseEntry") -> str: - """ - Returns - ------- - str - The uploader id if it exists, otherwise return the unique ID. - """ - return self.kwargs_get(UPLOADER_ID, self.uid) - - @property - def uploader(self: "BaseEntry") -> str: - """ - Returns - ------- - str - The uploader if it exists, otherwise return the uploader ID. - """ - return self.kwargs_get(UPLOADER, self.uploader_id) - - @property - def uploader_url(self: "BaseEntry") -> str: - """ - Returns - ------- - str - The uploader url if it exists, otherwise returns the webpage_url. - """ - return self.kwargs_get(UPLOADER_URL, self.webpage_url) - - -# pylint: enable=no-member +from ytdl_sub.entries.script.variable_definitions import VARIABLES +from ytdl_sub.entries.script.variable_definitions import VariableDefinitions +v: VariableDefinitions = VARIABLES TBaseEntry = TypeVar("TBaseEntry", bound="BaseEntry") -class BaseEntry(BaseEntryVariables, ABC): +class BaseEntry(ABC): """ Abstract entry object to represent anything download from ytdl (playlist metadata, media, etc). """ @@ -255,6 +36,52 @@ class BaseEntry(BaseEntryVariables, ABC): self._additional_variables: Dict[str, str | int] = {} + @property + def uid(self: "BaseEntry") -> str: + """ + Returns + ------- + str + The entry's unique ID + """ + return str(self.kwargs(v.uid.metadata_key)) + + @property + def extractor(self: "BaseEntry") -> str: + """ + The ytdl extractor name + """ + # pylint: disable=line-too-long + # Taken from https://github.com/yt-dlp/yt-dlp/blob/e6ab678e36c40ded0aae305bbb866cdab554d417/yt_dlp/YoutubeDL.py#L3514 + # pylint: enable=line-too-long + return self.kwargs_get(v.extractor_key.metadata_key) or self.kwargs(v.ie_key.metadata_key) + + @property + def title(self: "BaseEntry") -> str: + """ + The title of the entry. If a title does not exist, returns its unique ID. + """ + return self.kwargs_get(v.title.metadata_key, self.uid) + + @property + def webpage_url(self: "BaseEntry") -> str: + """ + The url to the webpage. + """ + return self.kwargs(v.webpage_url.metadata_key) + + @property + def info_json_ext(self) -> str: + """The "info.json" extension""" + return "info.json" + + @property + def uploader_id(self: "BaseEntry") -> str: + """ + The uploader id if it exists, otherwise return the unique ID. + """ + return self.kwargs_get(v.uploader_id.metadata_key, self.uid) + def kwargs_contains(self, key: str) -> bool: """Returns whether internal kwargs contains the specified key""" return key in self._kwargs diff --git a/src/ytdl_sub/entries/script/variable_definitions.py b/src/ytdl_sub/entries/script/variable_definitions.py index d80b5729..2e236e91 100644 --- a/src/ytdl_sub/entries/script/variable_definitions.py +++ b/src/ytdl_sub/entries/script/variable_definitions.py @@ -36,7 +36,7 @@ class SiblingMetadata(MetadataVariable): pass -class _Variables: +class VariableDefinitions: @property def entry_metadata(self) -> Metadata: """ @@ -69,16 +69,6 @@ class _Variables: """ return MetadataVariable(metadata_key="id", variable_name="uid") - @property - def uid_sanitized(self) -> Variable: - """ - Returns - ------- - str - The sanitized uid of the entry, which is safe to use for Unix and Windows file names. - """ - return Variable("uid_sanitized") - @property def uid_sanitized_plex(self) -> Variable: """ @@ -90,6 +80,16 @@ class _Variables: """ return Variable("uid_sanitized_plex") + @property + def ie_key(self) -> MetadataVariable: + """ + Returns + ------- + str + The info-extractor key + """ + return MetadataVariable(metadata_key="ie_key", variable_name="ie_key") + @property def extractor_key(self) -> MetadataVariable: """ @@ -150,16 +150,6 @@ class _Variables: """ return MetadataVariable(variable_name="title", metadata_key="title") - @property - def title_sanitized(self) -> Variable: - """ - Returns - ------- - str - The sanitized title of the entry, which is safe to use for Unix and Windows file names. - """ - return Variable("title_sanitized") - @property def title_sanitized_plex(self) -> Variable: """ @@ -242,16 +232,6 @@ class _Variables: """ return MetadataVariable("source_title", metadata_key=self.title.metadata_key) - @property - def source_title_sanitized(self) -> Variable: - """ - Returns - ------- - str - The source title, sanitized - """ - return Variable("source_title_sanitized") - @property def source_uid(self) -> MetadataVariable: """ @@ -335,16 +315,6 @@ class _Variables: """ return MetadataVariable(variable_name="playlist_title", metadata_key="playlist_title") - @property - def playlist_title_sanitized(self) -> Variable: - """ - Returns - ------- - str - The playlist name, sanitized - """ - return Variable("playlist_title_sanitized") - @property def playlist_index(self) -> MetadataVariable: """ @@ -498,16 +468,6 @@ class _Variables: """ return MetadataVariable("playlist_uploader", metadata_key=self.uploader.metadata_key) - @property - def playlist_uploader_sanitized(self) -> Variable: - """ - Returns - ------- - str - The playlist uploader, sanitized. - """ - return Variable("playlist_uploader_sanitized") - @property def playlist_uploader_url(self) -> MetadataVariable: """ @@ -560,16 +520,6 @@ class _Variables: """ return MetadataVariable(variable_name="creator", metadata_key="creator") - @property - def creator_sanitized(self) -> Variable: - """ - Returns - ------- - str - The creator name, sanitized - """ - return Variable("creator_sanitized") - @property def channel(self) -> MetadataVariable: """ @@ -580,16 +530,6 @@ class _Variables: """ return MetadataVariable(variable_name="channel", metadata_key="channel") - @property - def channel_sanitized(self) -> Variable: - """ - Returns - ------- - str - The channel name, sanitized. - """ - return Variable("channel_sanitized") - @property def channel_id(self) -> MetadataVariable: """ @@ -1051,4 +991,4 @@ class _Variables: # Singleton to use externally -VARIABLES = _Variables() +VARIABLES: VariableDefinitions = VariableDefinitions() diff --git a/src/ytdl_sub/entries/script/variable_scripts.py b/src/ytdl_sub/entries/script/variable_scripts.py index 30928067..5ce33aed 100644 --- a/src/ytdl_sub/entries/script/variable_scripts.py +++ b/src/ytdl_sub/entries/script/variable_scripts.py @@ -6,23 +6,22 @@ from typing import Set import mergedeep from ytdl_sub.entries.script.custom_functions import CustomFunctions -from ytdl_sub.entries.script.variable_definitions import VARIABLES as v +from ytdl_sub.entries.script.variable_definitions import VARIABLES from ytdl_sub.entries.script.variable_definitions import Metadata from ytdl_sub.entries.script.variable_definitions import MetadataVariable from ytdl_sub.entries.script.variable_definitions import Variable +from ytdl_sub.entries.script.variable_definitions import VariableDefinitions ############################################################################################### # Helpers +v: VariableDefinitions = VARIABLES + def pad_int(key: Variable, pad: int) -> str: return f"{{%pad_zero({key.variable_name}, {pad})}}" -def sanitized(key: Variable) -> str: - return f"{{%sanitize({key.variable_name})}}" - - def sanitized_plex(key: Variable) -> str: return f"{{%sanitize_plex_episode({key.variable_name})}}" @@ -130,6 +129,7 @@ ENTRY_DEFAULT_VARIABLES: Dict[MetadataVariable, str] = { v.title: entry_get_str(v.title, v.uid), v.extractor: entry_get_str(v.extractor, v.extractor_key), v.description: entry_get_str(v.description, ""), + v.ie_key: entry_get_str(v.ie_key, v.extractor_key), v.uploader_id: entry_get_str(v.uploader_id, v.uid), v.uploader: entry_get_str(v.uploader, v.uploader_id), v.uploader_url: entry_get_str(v.uploader_url, v.webpage_url), @@ -157,19 +157,14 @@ ENTRY_INJECTED_VARIABLES: Dict[Variable, str] = { } ENTRY_DERIVED_VARIABLES: Dict[Variable, str] = { - v.uid_sanitized: sanitized(v.uid), v.uid_sanitized_plex: sanitized_plex(v.uid), - v.title_sanitized: sanitized(v.title), v.title_sanitized_plex: sanitized_plex(v.title), v.epoch_date: f"{{%datetime_strftime({v.epoch.variable_name}, '%Y%m%d')}}", v.epoch_hour: f"{{%datetime_strftime({v.epoch.variable_name}, '%H')}}", - v.channel_sanitized: sanitized(v.channel), - v.creator_sanitized: sanitized(v.creator), v.download_index_padded6: pad_int(v.download_index, 6), v.upload_date_index_padded: pad_int(v.upload_date_index, 2), v.upload_date_index_reversed: f"{{%sub(100, {v.upload_date_index.variable_name})}}", v.upload_date_index_reversed_padded: pad_int(v.upload_date_index_reversed, 2), - v.playlist_title_sanitized: sanitized(v.playlist_title), v.playlist_index_reversed: f"{{%sub({v.playlist_count.variable_name}, {v.playlist_index.variable_name}, -1)}}", v.playlist_index_padded: pad_int(v.playlist_index, 2), v.playlist_index_reversed_padded: pad_int(v.playlist_index_reversed, 2), @@ -226,10 +221,6 @@ PLAYLIST_VARIABLES: Dict[Variable, str] = { v.playlist_uploader_url: playlist_get_str(v.playlist_uploader_url, v.playlist_webpage_url), } -PLAYLIST_DERIVED_VARIABLES: Dict[Variable, str] = { - v.playlist_uploader_sanitized: sanitized(v.playlist_uploader), -} - SOURCE_VARIABLES: Dict[Variable, str] = { v.source_uid: source_get_str(v.source_uid, v.playlist_uid), @@ -244,7 +235,6 @@ SOURCE_VARIABLES: Dict[Variable, str] = { } SOURCE_DERIVED_VARIABLES: Dict[Variable, str] = { - v.source_title_sanitized: sanitized(v.source_title), v.source_index_padded: pad_int(v.source_index, 2), } @@ -282,7 +272,6 @@ mergedeep.merge( SIBLING_VARIABLES, SIBLING_DERIVED_VARIABLES, PLAYLIST_VARIABLES, - PLAYLIST_DERIVED_VARIABLES, SOURCE_VARIABLES, SOURCE_DERIVED_VARIABLES, ) diff --git a/src/ytdl_sub/entries/variables/kwargs.py b/src/ytdl_sub/entries/variables/kwargs.py index 3456bd60..7a2c8351 100644 --- a/src/ytdl_sub/entries/variables/kwargs.py +++ b/src/ytdl_sub/entries/variables/kwargs.py @@ -18,19 +18,3 @@ def _(key: str, backend: bool = False) -> str: CHAPTERS = _("chapters", backend=True) YTDL_SUB_CUSTOM_CHAPTERS = _("ytdl_sub_custom_chapters", backend=True) -UID = _("id") -EXTRACTOR = _("extractor") -EXTRACTOR_KEY = _("extractor_key") -IE_KEY = _("ie_key") -EPOCH = _("epoch") -CHANNEL = _("channel") -CHANNEL_ID = _("channel_id") -CREATOR = _("creator") -TITLE = _("title") -DESCRIPTION = _("description") -WEBPAGE_URL = _("webpage_url") -RELEASE_DATE = _("release_date") -UPLOAD_DATE = _("upload_date") -UPLOADER = _("uploader") -UPLOADER_ID = _("uploader_id") -UPLOADER_URL = _("uploader_url") diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 5668feac..a4941b5c 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -12,15 +12,10 @@ from resources import copy_file_fixture from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.downloaders.url.downloader import MultiUrlDownloader from ytdl_sub.downloaders.ytdlp import YTDLP -from ytdl_sub.entries.script.variable_definitions import VARIABLES as v -from ytdl_sub.entries.variables.kwargs import DESCRIPTION -from ytdl_sub.entries.variables.kwargs import EPOCH -from ytdl_sub.entries.variables.kwargs import EXTRACTOR -from ytdl_sub.entries.variables.kwargs import EXTRACTOR_KEY -from ytdl_sub.entries.variables.kwargs import TITLE -from ytdl_sub.entries.variables.kwargs import UID -from ytdl_sub.entries.variables.kwargs import UPLOAD_DATE -from ytdl_sub.entries.variables.kwargs import WEBPAGE_URL +from ytdl_sub.entries.script.variable_definitions import VARIABLES +from ytdl_sub.entries.script.variable_definitions import VariableDefinitions + +v: VariableDefinitions = VARIABLES @pytest.fixture @@ -59,19 +54,19 @@ def mock_entry_dict_factory(mock_downloaded_file_path) -> Callable: is_extracted_audio: bool = False, ) -> Dict: entry_dict = { - UID: uid, - EPOCH: 1596878400, + v.uid.metadata_key: uid, + v.epoch.metadata_key: 1596878400, v.playlist_title.metadata_key: playlist_title, v.playlist_index.metadata_key: playlist_index, v.playlist_count.metadata_key: playlist_count, - EXTRACTOR: "mock-entry-dict", - EXTRACTOR_KEY: "mock-extractor-key", - TITLE: f"Mock Entry {uid}", - "ext": "mp4", - UPLOAD_DATE: upload_date, - WEBPAGE_URL: f"https://{uid}.com", + v.extractor.metadata_key: "mock-entry-dict", + v.extractor_key.metadata_key: "mock-extractor-key", + v.title.metadata_key: f"Mock Entry {uid}", + v.ext.metadata_key: "mp4", + v.upload_date.metadata_key: upload_date, + v.webpage_url.metadata_key: f"https://{uid}.com", v.playlist_metadata.metadata_key: {"thumbnails": []}, - DESCRIPTION: "The Description", + v.description.metadata_key: "The Description", } if is_youtube_channel: