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 Type
from typing import TypeVar
from typing import overload
import dicttoxml
import music_tag
@ -169,6 +168,19 @@ class Subscription(Generic[S], ABC):
if self.output_options.thumbnail_name:
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(
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 Validator
from ytdl_subscribe.validators.exceptions import StringFormattingException
from ytdl_subscribe.validators.exceptions import StringFormattingVariableNotFoundException
class StringFormatterValidator(Validator):
@ -89,7 +90,7 @@ class StringFormatterValidator(Validator):
raise self._validation_exception(
f"Format variable '{variable_name}' does not exist. "
f"Available variables: {available_fields}",
exception_class=StringFormattingException,
exception_class=StringFormattingVariableNotFoundException,
)
return StringFormatterValidator(
@ -119,6 +120,12 @@ class StringFormatterValidator(Validator):
return formatter.format_string
class OverridesStringFormatterValidator(StringFormatterValidator):
"""
A string formatter that should strictly use overrides that resolve without any entry variables.
"""
class DictFormatterValidator(LiteralDictValidator):
"""
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_name = "object"
def __init__(self, name, value):
super().__init__(name, value)
self.__validator_dict: Dict[str, Validator] = {}
@final
@property
def _dict(self) -> dict:
@ -113,6 +117,15 @@ class DictValidator(Validator):
"""
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
@property
def _keys(self) -> List[str]:
@ -147,11 +160,15 @@ class DictValidator(Validator):
if key not in self._dict and default is None:
raise self._validation_exception(f"{key} is missing when it should be present.")
return validator(
name=f"{self._name}.{key}",
validator_name = f"{self._name}.{key}"
validator_instance = validator(
name=validator_name,
value=self._dict.get(key, default),
)
self.__validator_dict[validator_name] = validator_instance
return validator_instance
@final
def _validate_key_if_present(
self,

View file

@ -1,5 +1,8 @@
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 (
OverridesStringFormatterValidator,
)
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_root = self._validate_key(key="nfo_root", validator=StringFormatterValidator)
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.string_formatter_validators import (
OverridesStringFormatterValidator,
)
from ytdl_subscribe.validators.base.string_formatter_validators import StringFormatterValidator
from ytdl_subscribe.validators.base.string_select_validator import StringSelectValidator
@ -18,13 +21,16 @@ class OutputOptionsValidator(StrictDictValidator):
def __init__(self, name, value):
super().__init__(name, value)
self.output_directory: StringFormatterValidator = self._validate_key(
key="output_directory", validator=StringFormatterValidator
# Output directory should resolve without any entry variables.
# 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(
key="file_name", validator=StringFormatterValidator
)
self.convert_thumbnail = self._validate_key_if_present(
key="convert_thumbnail", validator=ConvertThumbnailValidator
)

View file

@ -5,6 +5,11 @@ from typing import Optional
from typing import Type
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 (
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 (
YTDLOptionsValidator,
)
from ytdl_subscribe.validators.exceptions import StringFormattingVariableNotFoundException
from ytdl_subscribe.validators.exceptions import ValidationException
PRESET_SOURCE_VALIDATOR_MAPPING: Dict[str, Type[DownloadStrategyValidator]] = {
@ -74,6 +80,43 @@ class PresetValidator(StrictDictValidator):
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):
super().__init__(name=name, value=value)
@ -94,3 +137,7 @@ class PresetValidator(StrictDictValidator):
self.overrides = self._validate_key(
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):
"""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"""