265 lines
9.3 KiB
Python
265 lines
9.3 KiB
Python
from typing import Dict
|
|
from typing import Optional
|
|
|
|
from yt_dlp.utils import sanitize_filename
|
|
|
|
from ytdl_sub.entries.entry import Entry
|
|
from ytdl_sub.validators.strict_dict_validator import StrictDictValidator
|
|
from ytdl_sub.validators.string_datetime import StringDatetimeValidator
|
|
from ytdl_sub.validators.string_formatter_validators import DictFormatterValidator
|
|
from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator
|
|
from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator
|
|
from ytdl_sub.validators.validators import BoolValidator
|
|
from ytdl_sub.validators.validators import LiteralDictValidator
|
|
|
|
|
|
class YTDLOptions(LiteralDictValidator):
|
|
"""
|
|
Optional. This section allows you to add any ytdl argument to ytdl-sub's downloader.
|
|
The argument names can differ slightly from the command-line argument names. See
|
|
`this docstring <https://github.com/yt-dlp/yt-dlp/blob/2022.04.08/yt_dlp/YoutubeDL.py#L197>`_
|
|
for more details.
|
|
|
|
ytdl_options should be formatted like:
|
|
|
|
.. code-block:: yaml
|
|
|
|
presets:
|
|
my_example_preset:
|
|
ytdl_options:
|
|
ignoreerrors: True
|
|
|
|
where each key is a ytdl argument.
|
|
"""
|
|
|
|
|
|
# Disable for proper docstring formatting
|
|
# pylint: disable=line-too-long
|
|
class Overrides(DictFormatterValidator):
|
|
"""
|
|
Optional. This section allows you to define variables that can be used in any string formatter.
|
|
For example, if you want your file and thumbnail files to match without copy-pasting a large
|
|
format string, you can define something like:
|
|
|
|
.. code-block:: yaml
|
|
|
|
presets:
|
|
my_example_preset:
|
|
overrides:
|
|
output_directory: "/path/to/media"
|
|
custom_file_name: "{upload_year}.{upload_month_padded}.{upload_day_padded}.{title_sanitized}"
|
|
|
|
# Then use the override variables in the output options
|
|
output_options:
|
|
output_directory: "{output_directory}"
|
|
file_name: "{custom_file_name}.{ext}"
|
|
thumbnail_name: "{custom_file_name}.{thumbnail_ext}"
|
|
|
|
Override variables can contain explicit values and other variables, including both override
|
|
and source variables.
|
|
"""
|
|
|
|
# pylint: enable=line-too-long
|
|
|
|
def _add_override_variable(self, key_name: str, format_string: str, sanitize: bool = False):
|
|
if sanitize:
|
|
key_name = f"{key_name}_sanitized"
|
|
format_string = sanitize_filename(format_string)
|
|
|
|
self._value[key_name] = StringFormatterValidator(
|
|
name="__should_never_fail__",
|
|
value=format_string,
|
|
)
|
|
|
|
def __init__(self, name, value):
|
|
super().__init__(name, value)
|
|
|
|
# Add sanitized overrides
|
|
for key in self._keys:
|
|
self._add_override_variable(
|
|
key_name=key,
|
|
format_string=self._value[key].format_string,
|
|
sanitize=True,
|
|
)
|
|
|
|
def add_override_variables(self, variables_to_add: Dict[str, str]) -> None:
|
|
"""
|
|
Parameters
|
|
----------
|
|
variables_to_add
|
|
Override variables to add
|
|
"""
|
|
for key_name, override_var_value in variables_to_add.items():
|
|
for sanitize in [False, True]:
|
|
self._add_override_variable(
|
|
key_name=key_name,
|
|
format_string=override_var_value,
|
|
sanitize=sanitize,
|
|
)
|
|
|
|
def apply_formatter(
|
|
self,
|
|
formatter: StringFormatterValidator,
|
|
entry: Optional[Entry] = None,
|
|
function_overrides: Dict[str, str] = None,
|
|
) -> str:
|
|
"""
|
|
Parameters
|
|
----------
|
|
formatter
|
|
Formatter to apply
|
|
entry
|
|
Optional. Entry to add source variables to the formatter
|
|
function_overrides
|
|
Optional. Explicit values to override the overrides themselves and source variables
|
|
|
|
Returns
|
|
-------
|
|
The format_string after .format has been called
|
|
"""
|
|
variable_dict = self.dict_with_format_strings
|
|
if entry:
|
|
variable_dict = dict(entry.to_dict(), **variable_dict)
|
|
if function_overrides:
|
|
variable_dict = dict(variable_dict, **function_overrides)
|
|
|
|
return formatter.apply_formatter(variable_dict)
|
|
|
|
|
|
class OutputOptions(StrictDictValidator):
|
|
"""
|
|
Defines where to output files and thumbnails after all post-processing has completed.
|
|
|
|
Usage:
|
|
|
|
.. code-block:: yaml
|
|
|
|
presets:
|
|
my_example_preset:
|
|
output_options:
|
|
# required
|
|
output_directory: "/path/to/videos_or_music"
|
|
file_name: "{title_sanitized}.{ext}"
|
|
# optional
|
|
thumbnail_name: "{title_sanitized}.{thumbnail_ext}"
|
|
info_json_name: "{title_sanitized}.{info_json_ext}"
|
|
maintain_download_archive: True
|
|
keep_files_before: now
|
|
keep_files_after: 19000101
|
|
"""
|
|
|
|
_required_keys = {"output_directory", "file_name"}
|
|
_optional_keys = {
|
|
"thumbnail_name",
|
|
"info_json_name",
|
|
"subtitles_name",
|
|
"maintain_download_archive",
|
|
"keep_files_before",
|
|
"keep_files_after",
|
|
}
|
|
|
|
def __init__(self, name, value):
|
|
super().__init__(name, value)
|
|
|
|
# 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._thumbnail_name = self._validate_key_if_present(
|
|
key="thumbnail_name", validator=StringFormatterValidator
|
|
)
|
|
self._info_json_name = self._validate_key_if_present(
|
|
key="info_json_name", validator=StringFormatterValidator
|
|
)
|
|
|
|
self._maintain_download_archive = self._validate_key_if_present(
|
|
key="maintain_download_archive", validator=BoolValidator, default=False
|
|
)
|
|
|
|
self._keep_files_before = self._validate_key_if_present(
|
|
"keep_files_before", StringDatetimeValidator
|
|
)
|
|
self._keep_files_after = self._validate_key_if_present(
|
|
"keep_files_after", StringDatetimeValidator
|
|
)
|
|
|
|
if (
|
|
self._keep_files_before or self._keep_files_after
|
|
) and not self.maintain_download_archive:
|
|
raise self._validation_exception(
|
|
"keep_files requires maintain_download_archive set to True"
|
|
)
|
|
|
|
@property
|
|
def output_directory(self) -> OverridesStringFormatterValidator:
|
|
"""
|
|
Required. The output directory to store all media files downloaded.
|
|
"""
|
|
return self._output_directory
|
|
|
|
@property
|
|
def file_name(self) -> StringFormatterValidator:
|
|
"""
|
|
Required. The file name for the media file. This can include directories such as
|
|
``"Season {upload_year}/{title}.{ext}"``, and will be placed in the output directory.
|
|
"""
|
|
return self._file_name
|
|
|
|
@property
|
|
def thumbnail_name(self) -> Optional[StringFormatterValidator]:
|
|
"""
|
|
Optional. The file name for the media's thumbnail image. This can include directories such
|
|
as ``"Season {upload_year}/{title}.{thumbnail_ext}"``, and will be placed in the output
|
|
directory.
|
|
"""
|
|
return self._thumbnail_name
|
|
|
|
@property
|
|
def info_json_name(self) -> Optional[StringFormatterValidator]:
|
|
"""
|
|
Optional. The file name for the media's info json file. This can include directories such
|
|
as ``"Season {upload_year}/{title}.{info_json_ext}"``, and will be placed in the output
|
|
directory.
|
|
"""
|
|
return self._info_json_name
|
|
|
|
@property
|
|
def maintain_download_archive(self) -> bool:
|
|
"""
|
|
Optional. Maintains a download archive file in the output directory for a subscription.
|
|
It is named ``.ytdl-sub-{subscription_name}-download-archive.json``, stored in the
|
|
output directory.
|
|
|
|
The download archive contains a mapping of ytdl IDs to downloaded files. This is used to
|
|
create a ytdl download-archive file when invoking a download on a subscription. This will
|
|
prevent ytdl from redownloading media already downloaded.
|
|
|
|
Defaults to False.
|
|
"""
|
|
return self._maintain_download_archive.value
|
|
|
|
@property
|
|
def keep_files_before(self) -> Optional[StringDatetimeValidator]:
|
|
"""
|
|
Optional. Requires ``maintain_download_archive`` set to True.
|
|
|
|
Only keeps files that are uploaded before this datetime. By default, ytdl-sub will keep
|
|
files before ``now``, which implies all files.
|
|
"""
|
|
return self._keep_files_before
|
|
|
|
@property
|
|
def keep_files_after(self) -> Optional[StringDatetimeValidator]:
|
|
"""
|
|
Optional. Requires ``maintain_download_archive`` set to True.
|
|
|
|
Only keeps files that are uploaded after this datetime. By default, ytdl-sub will keep
|
|
files after ``19000101``, which implies all files.
|
|
"""
|
|
return self._keep_files_after
|