override format string, thumbnail hotfix

This commit is contained in:
jbannon 2022-04-09 22:09:10 +00:00
parent e3239153ec
commit fb7993cf70
7 changed files with 111 additions and 7 deletions

View file

@ -7,7 +7,6 @@ from typing import Generic
from typing import Optional from typing import Optional
from typing import Type from typing import Type
from typing import TypeVar from typing import TypeVar
from typing import overload
import dicttoxml import dicttoxml
import music_tag import music_tag
@ -169,6 +168,19 @@ class Subscription(Generic[S], ABC):
if self.output_options.thumbnail_name: if self.output_options.thumbnail_name:
source_thumbnail_path = entry.thumbnail_path(relative_directory=self.working_directory) source_thumbnail_path = entry.thumbnail_path(relative_directory=self.working_directory)
# Bug that mismatches webp and jpg extensions. Try to hotfix here
if not os.path.isfile(source_thumbnail_path):
actual_thumbnail_ext = ".webp"
if entry.thumbnail_ext == "webp":
actual_thumbnail_ext = ".jpg"
source_thumbnail_path = source_thumbnail_path.replace(
f".{entry.thumbnail_ext}", actual_thumbnail_ext
)
if not os.path.isfile(source_thumbnail_path):
# TODO: make more formal
raise ValueError("Youtube thumbnails are a lie")
output_thumbnail_name = self._apply_formatter( output_thumbnail_name = self._apply_formatter(
formatter=self.output_options.thumbnail_name, entry=entry formatter=self.output_options.thumbnail_name, entry=entry
) )

View file

@ -8,6 +8,7 @@ from typing import final
from ytdl_subscribe.validators.base.validators import LiteralDictValidator from ytdl_subscribe.validators.base.validators import LiteralDictValidator
from ytdl_subscribe.validators.base.validators import Validator from ytdl_subscribe.validators.base.validators import Validator
from ytdl_subscribe.validators.exceptions import StringFormattingException from ytdl_subscribe.validators.exceptions import StringFormattingException
from ytdl_subscribe.validators.exceptions import StringFormattingVariableNotFoundException
class StringFormatterValidator(Validator): class StringFormatterValidator(Validator):
@ -89,7 +90,7 @@ class StringFormatterValidator(Validator):
raise self._validation_exception( raise self._validation_exception(
f"Format variable '{variable_name}' does not exist. " f"Format variable '{variable_name}' does not exist. "
f"Available variables: {available_fields}", f"Available variables: {available_fields}",
exception_class=StringFormattingException, exception_class=StringFormattingVariableNotFoundException,
) )
return StringFormatterValidator( return StringFormatterValidator(
@ -119,6 +120,12 @@ class StringFormatterValidator(Validator):
return formatter.format_string return formatter.format_string
class OverridesStringFormatterValidator(StringFormatterValidator):
"""
A string formatter that should strictly use overrides that resolve without any entry variables.
"""
class DictFormatterValidator(LiteralDictValidator): class DictFormatterValidator(LiteralDictValidator):
""" """
Validates a dictionary made up of key: string_formatters Validates a dictionary made up of key: string_formatters

View file

@ -103,6 +103,10 @@ class DictValidator(Validator):
_expected_value_type = dict _expected_value_type = dict
_expected_value_type_name = "object" _expected_value_type_name = "object"
def __init__(self, name, value):
super().__init__(name, value)
self.__validator_dict: Dict[str, Validator] = {}
@final @final
@property @property
def _dict(self) -> dict: def _dict(self) -> dict:
@ -113,6 +117,15 @@ class DictValidator(Validator):
""" """
return self._value return self._value
@final
@property
def _validator_dict(self) -> Dict[str, Validator]:
"""
Returns dict containing names and validators of any keys that were validated.
This allows top-level validators to recursively search a dict validator.
"""
return self.__validator_dict
@final @final
@property @property
def _keys(self) -> List[str]: def _keys(self) -> List[str]:
@ -147,11 +160,15 @@ class DictValidator(Validator):
if key not in self._dict and default is None: if key not in self._dict and default is None:
raise self._validation_exception(f"{key} is missing when it should be present.") raise self._validation_exception(f"{key} is missing when it should be present.")
return validator( validator_name = f"{self._name}.{key}"
name=f"{self._name}.{key}", validator_instance = validator(
name=validator_name,
value=self._dict.get(key, default), value=self._dict.get(key, default),
) )
self.__validator_dict[validator_name] = validator_instance
return validator_instance
@final @final
def _validate_key_if_present( def _validate_key_if_present(
self, self,

View file

@ -1,5 +1,8 @@
from ytdl_subscribe.validators.base.strict_dict_validator import StrictDictValidator from ytdl_subscribe.validators.base.strict_dict_validator import StrictDictValidator
from ytdl_subscribe.validators.base.string_formatter_validators import DictFormatterValidator from ytdl_subscribe.validators.base.string_formatter_validators import DictFormatterValidator
from ytdl_subscribe.validators.base.string_formatter_validators import (
OverridesStringFormatterValidator,
)
from ytdl_subscribe.validators.base.string_formatter_validators import StringFormatterValidator from ytdl_subscribe.validators.base.string_formatter_validators import StringFormatterValidator
@ -12,3 +15,11 @@ class NFOValidator(StrictDictValidator):
self.nfo_name = self._validate_key(key="nfo_name", validator=StringFormatterValidator) self.nfo_name = self._validate_key(key="nfo_name", validator=StringFormatterValidator)
self.nfo_root = self._validate_key(key="nfo_root", validator=StringFormatterValidator) self.nfo_root = self._validate_key(key="nfo_root", validator=StringFormatterValidator)
self.tags = self._validate_key(key="tags", validator=DictFormatterValidator) self.tags = self._validate_key(key="tags", validator=DictFormatterValidator)
class OutputDirectoryNFOValidator(NFOValidator):
def __init__(self, name, value):
super().__init__(name, value)
self.nfo_name = self._validate_key(
key="nfo_name", validator=OverridesStringFormatterValidator
)

View file

@ -1,4 +1,7 @@
from ytdl_subscribe.validators.base.strict_dict_validator import StrictDictValidator from ytdl_subscribe.validators.base.strict_dict_validator import StrictDictValidator
from ytdl_subscribe.validators.base.string_formatter_validators import (
OverridesStringFormatterValidator,
)
from ytdl_subscribe.validators.base.string_formatter_validators import StringFormatterValidator from ytdl_subscribe.validators.base.string_formatter_validators import StringFormatterValidator
from ytdl_subscribe.validators.base.string_select_validator import StringSelectValidator from ytdl_subscribe.validators.base.string_select_validator import StringSelectValidator
@ -18,13 +21,16 @@ class OutputOptionsValidator(StrictDictValidator):
def __init__(self, name, value): def __init__(self, name, value):
super().__init__(name, value) super().__init__(name, value)
self.output_directory: StringFormatterValidator = self._validate_key( # Output directory should resolve without any entry variables.
key="output_directory", validator=StringFormatterValidator # This is to check the directory for any download-archives before any downloads begin
self.output_directory: OverridesStringFormatterValidator = self._validate_key(
key="output_directory", validator=OverridesStringFormatterValidator
) )
# file name and thumbnails however can use entry variables
self.file_name: StringFormatterValidator = self._validate_key( self.file_name: StringFormatterValidator = self._validate_key(
key="file_name", validator=StringFormatterValidator key="file_name", validator=StringFormatterValidator
) )
self.convert_thumbnail = self._validate_key_if_present( self.convert_thumbnail = self._validate_key_if_present(
key="convert_thumbnail", validator=ConvertThumbnailValidator key="convert_thumbnail", validator=ConvertThumbnailValidator
) )

View file

@ -5,6 +5,11 @@ from typing import Optional
from typing import Type from typing import Type
from ytdl_subscribe.validators.base.strict_dict_validator import StrictDictValidator from ytdl_subscribe.validators.base.strict_dict_validator import StrictDictValidator
from ytdl_subscribe.validators.base.string_formatter_validators import (
OverridesStringFormatterValidator,
)
from ytdl_subscribe.validators.base.validators import DictValidator
from ytdl_subscribe.validators.base.validators import Validator
from ytdl_subscribe.validators.config.metadata_options.metadata_options_validator import ( from ytdl_subscribe.validators.config.metadata_options.metadata_options_validator import (
MetadataOptionsValidator, MetadataOptionsValidator,
) )
@ -25,6 +30,7 @@ from ytdl_subscribe.validators.config.source_options.source_validators import So
from ytdl_subscribe.validators.config.ytdl_options.ytdl_options_validator import ( from ytdl_subscribe.validators.config.ytdl_options.ytdl_options_validator import (
YTDLOptionsValidator, YTDLOptionsValidator,
) )
from ytdl_subscribe.validators.exceptions import StringFormattingVariableNotFoundException
from ytdl_subscribe.validators.exceptions import ValidationException from ytdl_subscribe.validators.exceptions import ValidationException
PRESET_SOURCE_VALIDATOR_MAPPING: Dict[str, Type[DownloadStrategyValidator]] = { PRESET_SOURCE_VALIDATOR_MAPPING: Dict[str, Type[DownloadStrategyValidator]] = {
@ -74,6 +80,43 @@ class PresetValidator(StrictDictValidator):
return download_strategy_validator.source_validator return download_strategy_validator.source_validator
def __validate_override_string_formatter_validator(
self, formatter_validator: OverridesStringFormatterValidator
):
# Gather all resolvable override variables
resolvable_override_variables: List[str] = []
for name, override_variable in self.overrides.dict.items():
try:
_ = override_variable.apply_formatter(self.overrides.dict_with_format_strings)
except StringFormattingVariableNotFoundException:
continue
resolvable_override_variables.append(name)
for variable_name in formatter_validator.format_variables:
if variable_name not in resolvable_override_variables:
raise StringFormattingVariableNotFoundException(
f"This variable can only use override variables that resolve without needing "
f"variables from a downloaded file. The only override variables defined that "
f"meet this condition are: {', '.join(sorted(resolvable_override_variables))}"
)
def __recursive_preset_validate(
self, validator_dict: Optional[Dict[str, Validator]] = None
) -> None:
"""
Ensure all OverridesStringFormatterValidator's only contain variables from the overrides
and resolve.
"""
if validator_dict is None:
validator_dict = self._validator_dict
for name, validator in validator_dict.items():
if isinstance(validator, DictValidator):
self.__recursive_preset_validate(validator._validator_dict)
if isinstance(validator, OverridesStringFormatterValidator):
self.__validate_override_string_formatter_validator(validator)
def __init__(self, name: str, value: Any): def __init__(self, name: str, value: Any):
super().__init__(name=name, value=value) super().__init__(name=name, value=value)
@ -94,3 +137,7 @@ class PresetValidator(StrictDictValidator):
self.overrides = self._validate_key( self.overrides = self._validate_key(
key="overrides", validator=OverridesValidator, default={} key="overrides", validator=OverridesValidator, default={}
) )
# After all options are initialized, perform a recursive post-validate that requires
# values from multiple validators
self.__recursive_preset_validate()

View file

@ -4,3 +4,7 @@ class ValidationException(ValueError):
class StringFormattingException(ValidationException): class StringFormattingException(ValidationException):
"""Tried to format a string but failed due to user misconfigured variables""" """Tried to format a string but failed due to user misconfigured variables"""
class StringFormattingVariableNotFoundException(StringFormattingException):
"""Tried to format a string but the variable was not found"""