plugins done

This commit is contained in:
Jesse Bannon 2023-12-28 14:55:16 -08:00
parent 724faaac9e
commit 0669ac3e32
30 changed files with 860 additions and 750 deletions

View file

@ -53,51 +53,7 @@ presets
~~~~~~~ ~~~~~~~
``presets`` define a `formula` for how to format downloaded media and metadata. ``presets`` define a `formula` for how to format downloaded media and metadata.
download_strategy This section is work-in-progress!
"""""""""""""""""
Download strategies dictate what is getting downloaded from a source. Each
download strategy has its own set of parameters.
.. _url:
url
'''
.. autoclass:: ytdl_sub.downloaders.url.url.UrlDownloadOptions()
:members: url, playlist_thumbnails, source_thumbnails, download_reverse
:member-order: bysource
multi_url
'''''''''
.. autoclass:: ytdl_sub.downloaders.url.multi_url.MultiUrlDownloadOptions()
:members: urls, variables
-------------------------------------------------------------------------------
output_options
""""""""""""""
.. autoclass:: ytdl_sub.config.preset_options.OutputOptions()
:members:
:member-order: bysource
:exclude-members: get_upload_date_range_to_keep, partial_validate
-------------------------------------------------------------------------------
.. _ytdl_options:
ytdl_options
""""""""""""
.. autoclass:: ytdl_sub.config.preset_options.YTDLOptions()
-------------------------------------------------------------------------------
.. _overrides:
overrides
"""""""""
.. autoclass:: ytdl_sub.config.overrides.Overrides()
.. _parent preset:
preset preset
"""""" """"""

File diff suppressed because it is too large Load diff

View file

@ -1,17 +0,0 @@
==================
Config Field Types
==================
The ``config.yaml`` uses various types for its configurable fields. Below is a definition for each type.
.. autoclass:: ytdl_sub.validators.string_formatter_validators.StringFormatterValidator()
.. autoclass:: ytdl_sub.validators.string_formatter_validators.OverridesStringFormatterValidator()
.. autoclass:: ytdl_sub.validators.file_path_validators.StringFormatterFileNameValidator()
.. autoclass:: ytdl_sub.validators.string_datetime.StringDatetimeValidator()
.. autoclass:: ytdl_sub.validators.string_formatter_validators.DictFormatterValidator()
.. autoclass:: ytdl_sub.validators.string_formatter_validators.OverridesDictFormatterValidator()

View file

@ -2,8 +2,11 @@
Scripting Scripting
========= =========
Work in progress! Explanation of how to define scripts/variables will be added here.
.. toctree:: .. toctree::
:maxdepth: 1
entry_variables entry_variables
override_variables override_variables
scripting_functions scripting_functions
config_types

View file

@ -23,9 +23,9 @@ from ytdl_sub.validators.string_formatter_validators import StringFormatterValid
class Overrides(DictFormatterValidator, Scriptable): class Overrides(DictFormatterValidator, Scriptable):
""" """
Optional. This section allows you to define variables that can be used in any string formatter. Allows you to define variables that can be used in any EntryFormatter or OverridesFormatter.
For example, if you want your file and thumbnail files to match without copy-pasting a large
format string, you can define something like: :Usage:
.. code-block:: yaml .. code-block:: yaml

View file

@ -15,12 +15,12 @@ from ytdl_sub.validators.validators import LiteralDictValidator
class YTDLOptions(LiteralDictValidator): class YTDLOptions(LiteralDictValidator):
""" """
Optional. This section allows you to add any ytdl argument to ytdl-sub's downloader. 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 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>`_ `this docstring <https://github.com/yt-dlp/yt-dlp/blob/2022.04.08/yt_dlp/YoutubeDL.py#L197>`_
for more details. for more details.
ytdl_options should be formatted like: :Usage:
.. code-block:: yaml .. code-block:: yaml
@ -58,7 +58,7 @@ class OutputOptions(StrictDictValidator):
""" """
Defines where to output files and thumbnails after all post-processing has completed. Defines where to output files and thumbnails after all post-processing has completed.
Usage: :Usage:
.. code-block:: yaml .. code-block:: yaml
@ -156,97 +156,119 @@ class OutputOptions(StrictDictValidator):
@property @property
def output_directory(self) -> OverridesStringFormatterValidator: def output_directory(self) -> OverridesStringFormatterValidator:
""" """
Required. The output directory to store all media files downloaded. :expected type: OverridesFormatter
:description:
The output directory to store all media files downloaded.
""" """
return self._output_directory return self._output_directory
@property @property
def file_name(self) -> StringFormatterValidator: def file_name(self) -> StringFormatterValidator:
""" """
Required. The file name for the media file. This can include directories such as :expected type: EntryFormatter
``"Season {upload_year}/{title}.{ext}"``, and will be placed in the output directory. :description:
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 return self._file_name
@property @property
def thumbnail_name(self) -> Optional[StringFormatterValidator]: def thumbnail_name(self) -> Optional[StringFormatterValidator]:
""" """
Optional. The file name for the media's thumbnail image. This can include directories such :expected type: Optional[EntryFormatter]
as ``"Season {upload_year}/{title}.{thumbnail_ext}"``, and will be placed in the output :description:
directory. Can be set to empty string or `null` to disable thumbnail writes. 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. Can be set to empty string or `null` to disable thumbnail writes.
""" """
return self._thumbnail_name return self._thumbnail_name
@property @property
def info_json_name(self) -> Optional[StringFormatterValidator]: def info_json_name(self) -> Optional[StringFormatterValidator]:
""" """
Optional. The file name for the media's info json file. This can include directories such :expected type: Optional[EntryFormatter]
as ``"Season {upload_year}/{title}.{info_json_ext}"``, and will be placed in the output :description:
directory. Can be set to empty string or `null` to disable info json writes. 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. Can be set to empty string or `null` to disable info json writes.
""" """
return self._info_json_name return self._info_json_name
@property @property
def download_archive_name(self) -> Optional[OverridesStringFormatterValidator]: def download_archive_name(self) -> Optional[OverridesStringFormatterValidator]:
""" """
Optional. The file name to store a subscriptions download archive placed relative to :expected type: Optional[OverridesFormatter]
the output directory. Defaults to ``.ytdl-sub-{subscription_name}-download-archive.json`` :description:
The file name to store a subscriptions download archive placed relative to
the output directory. Defaults to ``.ytdl-sub-{subscription_name}-download-archive.json``
""" """
return self._download_archive_name return self._download_archive_name
@property @property
def migrated_download_archive_name(self) -> Optional[OverridesStringFormatterValidator]: def migrated_download_archive_name(self) -> Optional[OverridesStringFormatterValidator]:
""" """
Optional. Intended to be used if you are migrating a subscription with either a new :expected type: Optional[OverridesFormatter]
subscription name or output directory. It will try to load the archive file using this name :description:
first, and fallback to ``download_archive_name``. It will always save to this file Intended to be used if you are migrating a subscription with either a new
and remove the original ``download_archive_name``. subscription name or output directory. It will try to load the archive file using this
name first, and fallback to ``download_archive_name``. It will always save to this file
and remove the original ``download_archive_name``.
""" """
return self._migrated_download_archive_name return self._migrated_download_archive_name
@property @property
def maintain_download_archive(self) -> bool: def maintain_download_archive(self) -> bool:
""" """
Optional. Maintains a download archive file in the output directory for a subscription. :expected type: Optional[Boolean]
It is named ``.ytdl-sub-{subscription_name}-download-archive.json``, stored in the :description:
output directory. 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 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 create a ytdl download-archive file when invoking a download on a subscription. This will
prevent ytdl from redownloading media already downloaded. prevent ytdl from redownloading media already downloaded.
Defaults to False. Defaults to False.
""" """
return self._maintain_download_archive.value return self._maintain_download_archive.value
@property @property
def keep_files_before(self) -> Optional[StringDatetimeValidator]: def keep_files_before(self) -> Optional[StringDatetimeValidator]:
""" """
Optional. Requires ``maintain_download_archive`` set to True. :expected type: Optional[OverridesFormatter]
:description:
Requires ``maintain_download_archive`` set to True. Uses the same syntax as the
``date_range`` plugin.
Only keeps files that are uploaded before this datetime. By default, ytdl-sub will keep Only keeps files that are uploaded before this datetime. By default, ytdl-sub will keep
files before ``now``, which implies all files. Can be used in conjunction with files before ``now``, which implies all files. Can be used in conjunction with
``keep_max_files``. ``keep_max_files``.
""" """
return self._keep_files_before return self._keep_files_before
@property @property
def keep_files_after(self) -> Optional[StringDatetimeValidator]: def keep_files_after(self) -> Optional[StringDatetimeValidator]:
""" """
Optional. Requires ``maintain_download_archive`` set to True. :expected type: Optional[OverridesFormatter]
:description:
Requires ``maintain_download_archive`` set to True. Uses the same syntax as the
``date_range`` plugin.
Only keeps files that are uploaded after this datetime. By default, ytdl-sub will keep Only keeps files that are uploaded after this datetime. By default, ytdl-sub will keep
files after ``19000101``, which implies all files. Can be used in conjunction with files after ``19000101``, which implies all files. Can be used in conjunction with
``keep_max_files``. ``keep_max_files``.
""" """
return self._keep_files_after return self._keep_files_after
@property @property
def keep_max_files(self) -> Optional[OverridesIntegerFormatterValidator]: def keep_max_files(self) -> Optional[OverridesIntegerFormatterValidator]:
""" """
Optional. Requires ``maintain_download_archive`` set to True. :expected type: Optional[OverridesFormatter]
:description:
Requires ``maintain_download_archive`` set to True.
Only keeps N most recently uploaded videos. If set to <= 0, ``keep_max_files`` will not be Only keeps N most recently uploaded videos. If set to <= 0, ``keep_max_files`` will not be
applied. Can be used in conjunction with ``keep_files_before`` and ``keep_files_after``. applied. Can be used in conjunction with ``keep_files_before`` and ``keep_files_after``.
""" """
return self._keep_max_files return self._keep_max_files

View file

@ -1,37 +0,0 @@
from ytdl_sub.downloaders.url.validators import MultiUrlValidator
# TODO: Remove later - keep for docstring
class MultiUrlDownloadOptions(MultiUrlValidator):
"""
Downloads from multiple URLs. If an entry is returned from more than one URL, it will
resolve to the bottom-most URL settings.
Usage:
.. code-block:: yaml
presets:
my_example_preset:
download:
# required
urls:
- url: "youtube.com/channel/UCsvn_Po0SmunchJYtttWpOxMg"
variables:
season_index: "1"
season_name: "Uploads"
playlist_thumbnails:
- name: "poster.jpg"
uid: "avatar_uncropped"
- name: "fanart.jpg"
uid: "banner_uncropped"
- name: "season{season_index}-poster.jpg"
uid: "latest_entry"
- url: "https://www.youtube.com/playlist?list=UCsvn_Po0SmunchJYtttWpOxMg"
variables:
season_index: "2"
season_name: "Playlist as Season"
playlist_thumbnails:
- name: "season{season_index}-poster.jpg"
uid: "latest_entry"
"""

View file

@ -1,25 +0,0 @@
from ytdl_sub.downloaders.url.validators import UrlValidator
# TODO: Remove later - keep for docstring
class UrlDownloadOptions(UrlValidator):
"""
Downloads from a single URL supported by yt-dlp.
Usage:
.. code-block:: yaml
presets:
my_example_preset:
download:
# required
url: "youtube.com/channel/UCsvn_Po0SmunchJYtttWpOxMg"
# optional
playlist_thumbnails:
- name: "poster.jpg"
uid: "avatar_uncropped"
- name: "fanart.jpg"
uid: "banner_uncropped"
download_reverse: True
"""

View file

@ -215,8 +215,53 @@ class UrlListValidator(ListValidator[UrlStringOrDictValidator]):
class MultiUrlValidator(OptionsValidator): class MultiUrlValidator(OptionsValidator):
""" """
Downloads from multiple URLs. If an entry is returned from more than one URL, it will Sets the URL(s) to download from. Can be used in many forms, including
resolve to the bottom-most URL settings.
:Single URL:
.. code-block:: yaml
download: "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
:Multi URL:
.. code-block:: yaml
download:
- "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
- "https://www.youtube.com/watch?v=3BFTio5296w"
:Thumbnails + Variables:
All variables must be defined for the top-most url. All subsequent URL variables can be either
overwritten or default to the top-most value.
If an entry is returned from more than one URL, it will use the variables in the bottom-most
URL.
.. code-block:: yaml
download:
# required
urls:
- url: "youtube.com/channel/UCsvn_Po0SmunchJYtttWpOxMg"
variables:
season_index: "1"
season_name: "Uploads"
playlist_thumbnails:
- name: "poster.jpg"
uid: "avatar_uncropped"
- name: "fanart.jpg"
uid: "banner_uncropped"
- name: "season{season_index}-poster.jpg"
uid: "latest_entry"
- url: "https://www.youtube.com/playlist?list=UCsvn_Po0SmunchJYtttWpOxMg"
variables:
season_index: "2"
season_name: "Playlist as Season"
playlist_thumbnails:
- name: "season{season_index}-poster.jpg"
uid: "latest_entry"
""" """
@classmethod @classmethod

View file

@ -64,7 +64,7 @@ class ChaptersOptions(OptionsDictValidator):
Embeds chapters to video files if they are present. Additional options to add SponsorBlock Embeds chapters to video files if they are present. Additional options to add SponsorBlock
chapters and remove specific ones. Can also remove chapters using regex. chapters and remove specific ones. Can also remove chapters using regex.
:usage: :Usage:
.. code-block:: yaml .. code-block:: yaml

View file

@ -22,7 +22,7 @@ class DateRangeOptions(OptionsDictValidator):
Note that yt-dlp will round times to the closest day, meaning that `day` is the lowest Note that yt-dlp will round times to the closest day, meaning that `day` is the lowest
granularity possible. granularity possible.
:usage: :Usage:
.. code-block:: yaml .. code-block:: yaml

View file

@ -20,13 +20,11 @@ class EmbedThumbnailOptions(BoolValidator, OptionsValidator):
""" """
Whether to embed thumbnails to the audio/video file or not. Whether to embed thumbnails to the audio/video file or not.
Usage: :Usage:
.. code-block:: yaml .. code-block:: yaml
presets: embed_thumbnail: True
my_example_preset:
embed_thumbnail: True
""" """

View file

@ -32,29 +32,27 @@ class FileConvertOptions(OptionsDictValidator):
""" """
Converts video files from one extension to another. Converts video files from one extension to another.
Usage: :Usage:
.. code-block:: yaml .. code-block:: yaml
presets: file_convert:
my_example_preset: convert_to: "mp4"
file_convert:
convert_to: "mp4"
Supports custom ffmpeg conversions: Also supports custom ffmpeg conversions:
:Usage:
.. code-block:: yaml .. code-block:: yaml
presets: file_convert:
my_example_preset: convert_to: "mkv"
file_convert: convert_with: "ffmpeg"
convert_to: "mkv" ffmpeg_post_process_args: >
convert_with: "ffmpeg" -bitexact
ffmpeg_post_process_args: > -vcodec copy
-bitexact -acodec copy
-vcodec copy -scodec mov_text
-acodec copy
-scodec mov_text
""" """
_required_keys = {"convert_to"} _required_keys = {"convert_to"}
@ -89,35 +87,38 @@ class FileConvertOptions(OptionsDictValidator):
@property @property
def convert_to(self) -> str: def convert_to(self) -> str:
""" """
Convert to a desired file type. Supports: :expected type: String
:description:
Convert to a desired file type. Supports
* Video: avi, flv, mkv, mov, mp4, webm - Video: avi, flv, mkv, mov, mp4, webm
* Audio: aac, flac, mp3, m4a, opus, vorbis, wav - Audio: aac, flac, mp3, m4a, opus, vorbis, wav
""" """
return self._convert_to return self._convert_to
@property @property
def convert_with(self) -> Optional[str]: def convert_with(self) -> Optional[str]:
""" """
Optional. Supports ``yt-dlp`` and ``ffmpeg``. ``yt-dlp`` will convert files within :expected type: Optional[String]
yt-dlp whereas ``ffmpeg`` specifies it will be converted using a custom command specified :description:
with ``ffmpeg_post_process_args``. Defaults to ``yt-dlp``. Supports ``yt-dlp`` and ``ffmpeg``. ``yt-dlp`` will convert files within
yt-dlp whereas ``ffmpeg`` specifies it will be converted using a custom command specified
with ``ffmpeg_post_process_args``. Defaults to ``yt-dlp``.
""" """
return self._convert_with return self._convert_with
@property @property
def ffmpeg_post_process_args(self) -> Optional[OverridesStringFormatterValidator]: def ffmpeg_post_process_args(self) -> Optional[OverridesStringFormatterValidator]:
""" """
Optional. ffmpeg args to post-process an entry file with. The args will be inserted in the :expected type: Optional[OverridesFormatter]
form of: :description:
ffmpeg args to post-process an entry file with. The args will be inserted in the
form of
.. code-block:: bash ``ffmpeg -i input_file.ext {ffmpeg_post_process_args) output_file.output_ext``.
ffmpeg -i input_file.ext {ffmpeg_post_process_args) output_file.output_ext The output file will use the extension specified in ``convert_to``. Post-processing args
can still be set with ``convert_with`` set to ``yt-dlp``.
The output file will use the extension specified in ``convert_to``. Post-processing args
can still be set with ``convert_with`` set to ``yt-dlp``.
""" """
return self._ffmpeg_post_process_args return self._ffmpeg_post_process_args

View file

@ -19,17 +19,15 @@ class FilterExcludeOptions(ListFormatterValidator, OptionsValidator):
Applies a conditional OR on any number of filters comprised of either variables or scripts. Applies a conditional OR on any number of filters comprised of either variables or scripts.
If any filter evaluates to True, the entry will be excluded. If any filter evaluates to True, the entry will be excluded.
Usage: :Usage:
.. code-block:: yaml .. code-block:: yaml
presets: filter_exclude:
my_example_preset: - >-
filter_exclude: { %contains( %lower(title), '#short' ) }
- >- - >-
{ %contains( %lower(title), '#short' ) } { %contains( %lower(description), '#short' ) }
- >-
{ %contains( %lower(description), '#short' ) }
""" """

View file

@ -19,25 +19,23 @@ class FilterIncludeOptions(ListFormatterValidator, OptionsValidator):
Applies a conditional AND on any number of filters comprised of either variables or scripts. Applies a conditional AND on any number of filters comprised of either variables or scripts.
If all filters evaluate to True, the entry will be included. If all filters evaluate to True, the entry will be included.
Usage: :Usage:
.. code-block:: yaml .. code-block:: yaml
presets: filter_include:
my_example_preset: - >-
filter_include: {description}
- >- - >-
{description} {
- >- %regex_search_any(
{ title,
%regex_search_any( [
title, "Full Episode",
[ "FULL",
"Full Episode", ]
"FULL", )
] }
)
}
""" """

View file

@ -15,9 +15,7 @@ class FormatOptions(OptionsValidator):
.. code-block:: yaml .. code-block:: yaml
presets: format: "(bv*[height<=1080]+bestaudio/best[height<=1080])"
my_example_preset:
format: "(bv*[height<=1080]+bestaudio/best[height<=1080])"
""" """
def __init__(self, name, value): def __init__(self, name, value):

View file

@ -58,32 +58,20 @@ def combine_filters(filters: List[str], to_combine: List[str]) -> List[str]:
class MatchFiltersOptions(OptionsDictValidator): class MatchFiltersOptions(OptionsDictValidator):
""" """
Set ``--match-filters``` to pass into yt-dlp to filter entries from being downloaded. Set ``--match-filters`` to pass into yt-dlp to filter entries from being downloaded.
Uses the same syntax as yt-dlp. Uses the same syntax as yt-dlp. An entry will be downloaded if any one of the filters are met.
For logical AND's between match filters, use the ``&`` operator in a single match filter.
Usage: :Usage:
.. code-block:: yaml .. code-block:: yaml
presets: match_filters:
my_example_preset: filters:
match_filters: - "age_limit<?18 & like_count>?100"
filters: "original_url!*=/shorts/" # Other common match-filters
# - "original_url!*=/shorts/ & !is_live"
Supports one or multiple filters: # - "availability=?public"
.. code-block:: yaml
presets:
my_example_preset:
match_filters:
filters:
- "age_limit<?18"
- "like_count>?100"
# Other common match-filters
# - "original_url!*=/shorts/ & !is_live"
# - "age_limit<?18"
# - "availability=?public"
""" """
_optional_keys = {"filters"} _optional_keys = {"filters"}

View file

@ -75,23 +75,22 @@ class MusicTagsOptions(OptionsDictValidator):
a full list of tags for various file types in MediaFile's a full list of tags for various file types in MediaFile's
`source code <https://github.com/beetbox/mediafile/blob/v0.9.0/mediafile.py#L1770>`_. `source code <https://github.com/beetbox/mediafile/blob/v0.9.0/mediafile.py#L1770>`_.
Usage: :Usage:
.. code-block:: yaml .. code-block:: yaml
presets: presets:
my_example_preset: my_example_preset:
music_tags: music_tags:
tags: artist: "{artist}"
artist: "{artist}" album: "{album}"
album: "{album}" # Supports id3v2.4 multi-tags
# Supports id3v2.4 multi-tags genres:
genres: - "{genre}"
- "{genre}" - "ytdl-sub"
- "ytdl-sub" albumartists:
albumartists: - "{artist}"
- "{artist}" - "ytdl-sub"
- "ytdl-sub"
""" """
_optional_keys = {"tags", "embed_thumbnail"} _optional_keys = {"tags", "embed_thumbnail"}

View file

@ -60,7 +60,9 @@ class SharedNfoTagsOptions(OptionsDictValidator):
@property @property
def nfo_name(self) -> StringFormatterFileNameValidator: def nfo_name(self) -> StringFormatterFileNameValidator:
""" """
The NFO file name. :expected type: EntryFormatter
:description:
The NFO file name.
""" """
return self._nfo_name return self._nfo_name
@ -81,9 +83,11 @@ class SharedNfoTagsOptions(OptionsDictValidator):
@property @property
def kodi_safe(self) -> Optional[bool]: def kodi_safe(self) -> Optional[bool]:
""" """
Optional. Kodi does not support > 3-byte unicode characters, which include emojis and some :expected type: Optional[Boolean]
foreign language characters. Setting this to True will replace those characters with ''. :description:
Defaults to False. Defaults to False. Kodi does not support > 3-byte unicode characters, which include
emojis and some foreign language characters. Setting this to True will replace those
characters with ''.
""" """
return self._kodi_safe return self._kodi_safe
@ -190,22 +194,18 @@ class NfoTagsOptions(SharedNfoTagsOptions):
Adds an NFO file for every download file. An NFO file is simply an XML file Adds an NFO file for every download file. An NFO file is simply an XML file
with a ``.nfo`` extension. You can add any values into the NFO. with a ``.nfo`` extension. You can add any values into the NFO.
Usage: :Usage:
.. code-block:: yaml .. code-block:: yaml
presets: nfo_tags:
my_example_preset: nfo_name: "{title_sanitized}.nfo"
nfo_tags: nfo_root: "episodedetails"
# required tags:
nfo_name: "{title_sanitized}.nfo" title: "{title}"
nfo_root: "episodedetails" season: "{upload_year}"
tags: episode: "{upload_month}{upload_day_padded}"
title: "{title}" kodi_safe: False
season: "{upload_year}"
episode: "{upload_month}{upload_day_padded}"
# optional
kodi_safe: False
""" """
_formatter_validator = StringFormatterValidator _formatter_validator = StringFormatterValidator
@ -215,50 +215,54 @@ class NfoTagsOptions(SharedNfoTagsOptions):
@property @property
def nfo_root(self) -> StringFormatterValidator: def nfo_root(self) -> StringFormatterValidator:
""" """
The root tag of the NFO's XML. In the usage above, it would look like :expected type: EntryFormatter
:description:
The root tag of the NFO's XML. In the usage above, it would look like
.. code-block:: xml .. code-block:: xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<episodedetails> <episodedetails>
</episodedetails> </episodedetails>
""" """
return self._nfo_root return self._nfo_root
@property @property
def tags(self) -> NfoTagsValidator: def tags(self) -> NfoTagsValidator:
""" """
Tags within the nfo_root tag. In the usage above, it would look like :expected type: NfoTags
:description:
Tags within the nfo_root tag. In the usage above, it would look like
.. code-block:: xml .. code-block:: xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<episodedetails> <episodedetails>
<title>Awesome Youtube Video</title> <title>Awesome Youtube Video</title>
<season>2022</season> <season>2022</season>
<episode>502</episode> <episode>502</episode>
</episodedetails> </episodedetails>
Also supports xml attributes and duplicate keys: Also supports xml attributes and duplicate keys:
.. code-block:: yaml .. code-block:: yaml
tags: tags:
season: season:
attributes: attributes:
name: "Best Year" name: "Best Year"
tag: "{upload_year}" tag: "{upload_year}"
genre: genre:
- "Comedy" - "Comedy"
- "Drama" - "Drama"
Which translates to Which translates to
.. code-block:: xml .. code-block:: xml
<season name="Best Year">2022</season> <season name="Best Year">2022</season>
<genre>Comedy</genre> <genre>Comedy</genre>
<genre>Drama</genre> <genre>Drama</genre>
""" """
return self._tags return self._tags

View file

@ -37,48 +37,52 @@ class OutputDirectoryNfoTagsOptions(SharedNfoTagsOptions):
@property @property
def nfo_root(self) -> StringFormatterValidator: def nfo_root(self) -> StringFormatterValidator:
""" """
The root tag of the NFO's XML. In the usage above, it would look like :expected type: EntryFormatter
:description:
The root tag of the NFO's XML. In the usage above, it would look like
.. code-block:: xml .. code-block:: xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tvshow> <tvshow>
</tvshow> </tvshow>
""" """
return self._nfo_root return self._nfo_root
@property @property
def tags(self) -> NfoTagsValidator: def tags(self) -> NfoTagsValidator:
""" """
Tags within the nfo_root tag. In the usage above, it would look like :expected type: NfoTags
:description:
Tags within the nfo_root tag. In the usage above, it would look like
.. code-block:: xml .. code-block:: xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tvshow> <tvshow>
<title>Sweet youtube TV show</title> <title>Sweet youtube TV show</title>
</tvshow> </tvshow>
Also supports xml attributes and duplicate keys: Also supports xml attributes and duplicate keys:
.. code-block:: yaml .. code-block:: yaml
tags: tags:
named_season: named_season:
- tag: "{source_title}" - tag: "{source_title}"
attributes: attributes:
number: "{collection_index}" number: "{collection_index}"
genre: genre:
- "Comedy" - "Comedy"
- "Drama" - "Drama"
Which translates to Which translates to
.. code-block:: xml .. code-block:: xml
<title year="2022">Sweet youtube TV show</season> <title year="2022">Sweet youtube TV show</season>
<genre>Comedy</genre> <genre>Comedy</genre>
<genre>Drama</genre> <genre>Drama</genre>
""" """
return self._tags return self._tags

View file

@ -122,6 +122,41 @@ class FromSourceVariablesRegex(DictValidator):
class RegexOptions(OptionsDictValidator): class RegexOptions(OptionsDictValidator):
r""" r"""
.. attention::
This plugin will eventually be deprecated and replaced by scripting functions.
You can replicate the example below using the following.
.. code-block:: yaml
# Only includes videos with 'Official Video'
filter_include:
- >-
{ %contains( %lower(title), "official video" ) }
# Excludes videos with '#short' in its description
filter_exclude:
- >-
{ %contains( %lower(description), '#short' ) }
# Creates a capture array with defaults, and assigns
# each capture group to its own variable
overrides:
description_date_capture: >-
{
%regex_capture_many_with_defaults(
description,
[ "([0-9]{4})-([0-9]{2})-([0-9]{2})" ],
[ upload_year, upload_month, upload_day ]
)
}
captured_upload_year: >-
{ %array_at(description_date_capture, 1) }
captured_upload_month: >-
{ %array_at(description_date_capture, 2) }
captured_upload_day: >-
{ %array_at(description_date_capture, 3) }
Performs regex matching on an entry's source or override variables. Regex can be used to filter Performs regex matching on an entry's source or override variables. Regex can be used to filter
entries from proceeding with download or capture groups to create new source variables. entries from proceeding with download or capture groups to create new source variables.
@ -137,51 +172,49 @@ class RegexOptions(OptionsDictValidator):
and using ``title_and_description`` can regex match/exclude from either ``title`` or and using ``title_and_description`` can regex match/exclude from either ``title`` or
``description``. ``description``.
Usage: :Usage:
.. code-block:: yaml .. code-block:: yaml
presets: regex:
my_example_preset: # By default, if any match fails and has no defaults, the entry will
regex: # be skipped. If False, ytdl-sub will error and stop all downloads
# By default, if any match fails and has no defaults, the entry will # from proceeding.
# be skipped. If False, ytdl-sub will error and stop all downloads skip_if_match_fails: True
# from proceeding.
skip_if_match_fails: True
from: from:
# For each entry's `title` value... # For each entry's `title` value...
title: title:
# Perform this regex match on it to act as a filter. # Perform this regex match on it to act as a filter.
# This will only download videos with "[Official Video]" in it. Note that we # This will only download videos with "[Official Video]" in it. Note that we
# double backslash to make YAML happy # double backslash to make YAML happy
match: match:
- '\\[Official Video\\]' - '\\[Official Video\\]'
# For each entry's `description` value... # For each entry's `description` value...
description: description:
# Match with capture groups and defaults. # Match with capture groups and defaults.
# This tries to scrape a date from the description and produce new # This tries to scrape a date from the description and produce new
# source variables # source variables
match: match:
- '([0-9]{4})-([0-9]{2})-([0-9]{2})' - '([0-9]{4})-([0-9]{2})-([0-9]{2})'
# Exclude any entry where the description contains #short # Exclude any entry where the description contains #short
exclude: exclude:
- '#short' - '#short'
# Each capture group creates these new source variables, respectively, # Each capture group creates these new source variables, respectively,
# as well a sanitized version, i.e. `captured_upload_year_sanitized` # as well a sanitized version, i.e. `captured_upload_year_sanitized`
capture_group_names: capture_group_names:
- "captured_upload_year" - "captured_upload_year"
- "captured_upload_month" - "captured_upload_month"
- "captured_upload_day" - "captured_upload_day"
# And if the string does not match, use these as respective default # And if the string does not match, use these as respective default
# values for the new source variables. # values for the new source variables.
capture_group_defaults: capture_group_defaults:
- "{upload_year}" - "{upload_year}"
- "{upload_month}" - "{upload_month}"
- "{upload_day}" - "{upload_day}"
""" """
_required_keys = {"from"} _required_keys = {"from"}
@ -209,8 +242,10 @@ class RegexOptions(OptionsDictValidator):
@property @property
def skip_if_match_fails(self) -> Optional[bool]: def skip_if_match_fails(self) -> Optional[bool]:
""" """
Defaults to True. If True, when any match fails and has no defaults, the entry will be :expected type: Optional[Boolean]
skipped. If False, ytdl-sub will error and all downloads will not proceed. :description:
Defaults to True. If True, when any match fails and has no defaults, the entry will be
skipped. If False, ytdl-sub will error and all downloads will not proceed.
""" """
return self._skip_if_match_fails return self._skip_if_match_fails

View file

@ -48,23 +48,22 @@ class WhenNoChaptersValidator(StringSelectValidator):
class SplitByChaptersOptions(OptionsDictValidator): class SplitByChaptersOptions(OptionsDictValidator):
""" """
Splits a file by chapters into multiple files. Each file becomes its own entry with the Splits a file by chapters into multiple files. Each file becomes its own entry with the
new source variables ``chapter_title``, ``chapter_title_sanitized``, ``chapter_index``, new variables
``chapter_index_padded``, ``chapter_count``.
If a file has no chapters, and ``when_no_chapters`` is set to "pass", then ``chapter_title`` is - ``chapter_title``
set to the entry's title and ``chapter_index``, ``chapter_count`` are both set to 1. - ``chapter_index``
- ``chapter_index_padded``
- ``chapter_count``
Note that when using this plugin and performing dry-run, it assumes embedded chapters are being Note that when using this plugin and performing dry-run, it assumes embedded chapters are being
used with no modifications. used with no modifications.
Usage: :Usage:
.. code-block:: yaml .. code-block:: yaml
presets: split_by_chapters:
my_example_preset: when_no_chapters: "pass"
split_by_chapters:
when_no_chapters: "pass"
""" """
_required_keys = {"when_no_chapters"} _required_keys = {"when_no_chapters"}
@ -100,8 +99,16 @@ class SplitByChaptersOptions(OptionsDictValidator):
@property @property
def when_no_chapters(self) -> str: def when_no_chapters(self) -> str:
""" """
Behavior to perform when no chapters are present. Supports "pass" (continue processing), :expected type: String
"drop" (exclude it from output), and "error" (stop processing for everything). :description:
Behavior to perform when no chapters are present. Supports
- "pass" (continue processing),
- "drop" (exclude it from output)
- "error" (stop processing for everything).
If a file has no chapters and is set to "pass", then ``chapter_title`` is
set to the entry's title and ``chapter_index``, ``chapter_count`` are both set to 1.
""" """
return self._when_no_chapters return self._when_no_chapters

View file

@ -37,18 +37,18 @@ class SubtitleOptions(OptionsDictValidator):
``lang`` and ``subtitles_ext``. ``lang`` is dynamic since you can download multiple subtitles. ``lang`` and ``subtitles_ext``. ``lang`` is dynamic since you can download multiple subtitles.
It will set the respective language to the correct subtitle file. It will set the respective language to the correct subtitle file.
Usage: :Usage:
.. code-block:: yaml .. code-block:: yaml
presets: subtitles:
my_example_preset: subtitles_name: "{title_sanitized}.{lang}.{subtitles_ext}"
subtitles: subtitles_type: "srt"
subtitles_name: "{title_sanitized}.{lang}.{subtitles_ext}" embed_subtitles: False
subtitles_type: "srt" languages:
embed_subtitles: False - "en" # supports multiple languages
languages: "en" # supports list of multiple languages - "de"
allow_auto_generated_subtitles: False allow_auto_generated_subtitles: False
""" """
_optional_keys = { _optional_keys = {
@ -82,40 +82,50 @@ class SubtitleOptions(OptionsDictValidator):
@property @property
def subtitles_name(self) -> Optional[StringFormatterValidator]: def subtitles_name(self) -> Optional[StringFormatterValidator]:
""" """
Optional. The file name for the media's subtitles if they are present. This can include :expected type: Optional[EntryFormatter]
directories such as ``"Season {upload_year}/{title_sanitized}.{lang}.{subtitles_ext}"``, and :description:
will be placed in the output directory. ``lang`` is dynamic since you can download multiple The file name for the media's subtitles if they are present. This can include
subtitles. It will set the respective language to the correct subtitle file. directories such as ``"Season {upload_year}/{title_sanitized}.{lang}.{subtitles_ext}"``,
and will be placed in the output directory. ``lang`` is dynamic since you can download
multiple subtitles. It will set the respective language to the correct subtitle file.
""" """
return self._subtitles_name return self._subtitles_name
@property @property
def subtitles_type(self) -> Optional[str]: def subtitles_type(self) -> Optional[str]:
""" """
Optional. One of the subtitle file types "srt", "vtt", "ass", "lrc". Defaults to "srt" :expected type: Optional[String]
:description:
Defaults to "srt". One of the subtitle file types "srt", "vtt", "ass", "lrc".
""" """
return self._subtitles_type return self._subtitles_type
@property @property
def embed_subtitles(self) -> Optional[bool]: def embed_subtitles(self) -> Optional[bool]:
""" """
Optional. Whether to embed the subtitles into the video file. Defaults to False. :expected type: Optional[Boolean]
NOTE: webm files can only embed "vtt" subtitle types. :description:
Defaults to False. Whether to embed the subtitles into the video file. Note that
webm files can only embed "vtt" subtitle types.
""" """
return self._embed_subtitles return self._embed_subtitles
@property @property
def languages(self) -> Optional[List[str]]: def languages(self) -> Optional[List[str]]:
""" """
Optional. Language code(s) to download for subtitles. Supports a single or list of multiple :expected type: Optional[List[String]]
language codes. Defaults to "en". :description:
Language code(s) to download for subtitles. Supports a single or list of multiple
language codes. Defaults to only "en".
""" """
return [lang.value for lang in self._languages] return [lang.value for lang in self._languages]
@property @property
def allow_auto_generated_subtitles(self) -> Optional[bool]: def allow_auto_generated_subtitles(self) -> Optional[bool]:
""" """
Optional. Whether to allow auto generated subtitles. Defaults to False. :expected type: Optional[Boolean]
:description:
Defaults to False. Whether to allow auto generated subtitles.
""" """
return self._allow_auto_generated_subtitles return self._allow_auto_generated_subtitles

View file

@ -65,7 +65,7 @@ class ThrottleProtectionOptions(OptionsDictValidator):
range-based values, a random number will be chosen within the range to avoid sleeps looking range-based values, a random number will be chosen within the range to avoid sleeps looking
scripted. scripted.
Usage: :Usage:
.. code-block:: yaml .. code-block:: yaml
@ -110,31 +110,39 @@ class ThrottleProtectionOptions(OptionsDictValidator):
@property @property
def sleep_per_download_s(self) -> Optional[RandomizedRangeValidator]: def sleep_per_download_s(self) -> Optional[RandomizedRangeValidator]:
""" """
Number in seconds to sleep between each download. Does not include time it takes for :expected type: Optional[Range]
ytdl-sub to perform post-processing. :description:
Number in seconds to sleep between each download. Does not include time it takes for
ytdl-sub to perform post-processing.
""" """
return self._sleep_per_download_s return self._sleep_per_download_s
@property @property
def sleep_per_subscription_s(self) -> Optional[RandomizedRangeValidator]: def sleep_per_subscription_s(self) -> Optional[RandomizedRangeValidator]:
""" """
Number in seconds to sleep between each subscription. :expected type: Optional[Range]
:description:
Number in seconds to sleep between each subscription.
""" """
return self._sleep_per_subscription_s return self._sleep_per_subscription_s
@property @property
def max_downloads_per_subscription(self) -> Optional[RandomizedRangeValidator]: def max_downloads_per_subscription(self) -> Optional[RandomizedRangeValidator]:
""" """
Number of downloads to perform per subscription. :expected type: Optional[Range]
:description:
Number of downloads to perform per subscription.
""" """
return self._max_downloads_per_subscription return self._max_downloads_per_subscription
@property @property
def subscription_download_probability(self) -> Optional[ProbabilityValidator]: def subscription_download_probability(self) -> Optional[ProbabilityValidator]:
""" """
Probability to perform any downloads, recomputed for each subscription. This is only :expected type: Optional[Float]
recommended to set if you run ytdl-sub in a cron-job, that way you are statistically :description:
guaranteed over time to eventually download the subscription. Probability to perform any downloads, recomputed for each subscription. This is only
recommended to set if you run ytdl-sub in a cron-job, that way you are statistically
guaranteed over time to eventually download the subscription.
""" """
return self._subscription_download_probability return self._subscription_download_probability

View file

@ -17,16 +17,14 @@ class VideoTagsOptions(OptionsDictValidator):
""" """
Adds tags to every downloaded video file using ffmpeg ``-metadata key=value`` args. Adds tags to every downloaded video file using ffmpeg ``-metadata key=value`` args.
Usage: :Usage:
.. code-block:: yaml .. code-block:: yaml
presets: video_tags:
my_example_preset: title: "{title}"
video_tags: date: "{upload_date}"
title: "{title}" description: "{description}"
date: "{upload_date}"
description: "{description}"
""" """
_optional_keys = {"tags"} _optional_keys = {"tags"}

View file

@ -1,7 +1,7 @@
from abc import abstractmethod from abc import abstractmethod
from pathlib import Path from pathlib import Path
REGENERATE_DOCS: bool = True REGENERATE_DOCS: bool = False
class DocGen: class DocGen:

View file

@ -4,8 +4,9 @@ from typing import Dict
from typing import Type from typing import Type
from tools.docgen.docgen import DocGen from tools.docgen.docgen import DocGen
from tools.docgen.utils import camel_case_to_human, line_section from tools.docgen.utils import camel_case_to_human
from tools.docgen.utils import get_function_docs from tools.docgen.utils import get_function_docs
from tools.docgen.utils import line_section
from tools.docgen.utils import properties from tools.docgen.utils import properties
from tools.docgen.utils import section from tools.docgen.utils import section
from ytdl_sub.entries.script.variable_definitions import VariableDefinitions from ytdl_sub.entries.script.variable_definitions import VariableDefinitions

View file

@ -1,6 +1,8 @@
import inspect import inspect
from pathlib import Path from pathlib import Path
from typing import Dict, Any, Optional from typing import Any
from typing import Dict
from typing import Optional
from typing import Type from typing import Type
from tools.docgen.docgen import DocGen from tools.docgen.docgen import DocGen
@ -12,6 +14,20 @@ from ytdl_sub.config.plugin.plugin_mapping import PluginMapping
from ytdl_sub.config.preset_options import OutputOptions from ytdl_sub.config.preset_options import OutputOptions
from ytdl_sub.config.preset_options import YTDLOptions from ytdl_sub.config.preset_options import YTDLOptions
from ytdl_sub.config.validators.options import OptionsValidator from ytdl_sub.config.validators.options import OptionsValidator
from ytdl_sub.downloaders.url.validators import MultiUrlValidator
def should_filter_all_properties(plugin_name: str) -> bool:
return plugin_name in (
"format",
"match_filters",
"music_tags",
"filter_include",
"filter_exclude",
"embed_thumbnail",
"video_tags",
"download",
)
def should_filter_property(property_name: str) -> bool: def should_filter_property(property_name: str) -> bool:
@ -26,9 +42,7 @@ def should_filter_property(property_name: str) -> bool:
) )
def get_function_docs( def get_function_docs(function_name: str, obj: Any, level: int) -> str:
function_name: str, obj: Any, level: int
) -> str:
docs = f"\n``{function_name}``\n\n" docs = f"\n``{function_name}``\n\n"
docs += inspect.cleandoc(getattr(obj, function_name).__doc__) docs += inspect.cleandoc(getattr(obj, function_name).__doc__)
docs += "\n\n" docs += "\n\n"
@ -42,6 +56,9 @@ def generate_plugin_docs(name: str, options: Type[OptionsValidator], offset: int
docs += inspect.cleandoc(options.__doc__) docs += inspect.cleandoc(options.__doc__)
docs += "\n" docs += "\n"
if should_filter_all_properties(name):
return docs
property_names = [prop for prop in properties(options) if not should_filter_property(prop)] property_names = [prop for prop in properties(options) if not should_filter_property(prop)]
for property_name in sorted(property_names): for property_name in sorted(property_names):
docs += get_function_docs(function_name=property_name, obj=options, level=offset + 1) docs += get_function_docs(function_name=property_name, obj=options, level=offset + 1)
@ -59,6 +76,7 @@ class PluginsDocGen(DocGen):
"output_options": OutputOptions, "output_options": OutputOptions,
"ytdl_options": YTDLOptions, "ytdl_options": YTDLOptions,
"overrides": Overrides, "overrides": Overrides,
"download": MultiUrlValidator,
} }
for plugin_name, plugin_type in PluginMapping._MAPPING.items(): for plugin_name, plugin_type in PluginMapping._MAPPING.items():
if plugin_name.startswith("_"): if plugin_name.startswith("_"):

View file

@ -6,7 +6,8 @@ from typing import Optional
from typing import Type from typing import Type
from tools.docgen.docgen import DocGen from tools.docgen.docgen import DocGen
from tools.docgen.utils import camel_case_to_human, line_section from tools.docgen.utils import camel_case_to_human
from tools.docgen.utils import line_section
from tools.docgen.utils import section from tools.docgen.utils import section
from tools.docgen.utils import static_methods from tools.docgen.utils import static_methods
from ytdl_sub.entries.script.custom_functions import CustomFunctions from ytdl_sub.entries.script.custom_functions import CustomFunctions