Add documentation for plugins + improve docs overall (#23)
This commit is contained in:
parent
f67a4dc7dc
commit
29ef377cc7
16 changed files with 434 additions and 175 deletions
10
README.md
10
README.md
|
|
@ -8,18 +8,18 @@
|
|||
|
||||
Automate downloading and adding metadata with YoutubeDL.
|
||||
|
||||
This package strives to download media via
|
||||
This package downloads media via
|
||||
[yt-dlp](https://github.com/yt-dlp/yt-dlp)
|
||||
and prepare it for consumption in your favorite media player
|
||||
and prepares it for consumption in your favorite media player
|
||||
([Kodi](https://github.com/xbmc/xbmc),
|
||||
[Jellyfin](https://github.com/jellyfin/jellyfin),
|
||||
[Plex](https://github.com/plexinc/pms-docker),
|
||||
[Emby](https://github.com/plexinc/pms-docker),
|
||||
modern music players)
|
||||
in the most hassle-free way possible. We recognize that everyone stores their
|
||||
modern music players).
|
||||
We recognize that everyone stores their
|
||||
media differently. Our approach for file and metadata formatting is to provide
|
||||
maximum flexibility while maintaining simplicity. Read more about it
|
||||
[here](https://ytdl-sub.readthedocs.io/en/latest/).
|
||||
[here](https://ytdl-sub.readthedocs.io/en/latest/getting_started.html).
|
||||
|
||||
## Supported Features
|
||||
Below lists supported download schemes. You can see our
|
||||
|
|
|
|||
|
|
@ -61,3 +61,7 @@ add_module_names = False
|
|||
python_use_unqualified_type_names = False
|
||||
napoleon_numpy_docstrings = True
|
||||
napoleon_use_rtype = False
|
||||
|
||||
# -- Options for autodocs -------------------------------------------------
|
||||
|
||||
autodoc_default_options = {"autodoc_typehints_format": "short"}
|
||||
|
|
|
|||
187
docs/config.rst
187
docs/config.rst
|
|
@ -4,6 +4,9 @@ ytdl-sub is configured using a ``config.yaml`` file. You can view our
|
|||
:doc:`examples <examples>` and read detailed documentation for every configurable
|
||||
field below.
|
||||
|
||||
config.yaml
|
||||
-----------
|
||||
|
||||
The ``config.yaml`` is made up of two sections:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
|
@ -15,159 +18,129 @@ You can jump to any section and subsection of the config using the navigation
|
|||
section to the left.
|
||||
|
||||
configuration
|
||||
-------------
|
||||
^^^^^^^^^^^^^
|
||||
The ``configuration`` section contains app-wide configs applied to all presets
|
||||
and subscriptions.
|
||||
|
||||
presets
|
||||
-------
|
||||
^^^^^^^
|
||||
``presets`` define a `formula` for how to format downloaded media and metadata.
|
||||
|
||||
download_strategy
|
||||
^^^^^^^^^^^^^^^^^
|
||||
"""""""""""""""""
|
||||
Download strategies dictate what is getting downloaded from a source. Each
|
||||
download strategy has its own set of parameters.
|
||||
|
||||
youtube
|
||||
"""""""
|
||||
'''''''
|
||||
Download strategies for downloading videos (or audio if you configure `ytdl_options`_ correctly) from Youtube. See
|
||||
Download strategies for downloading music from Soundcloud. See
|
||||
:class:`Youtube Variables <ytdl_sub.entries.variables.youtube_variables>`
|
||||
for available source variables to use.
|
||||
|
||||
channel
|
||||
'''''''
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
youtube:
|
||||
download_strategy: "channel"
|
||||
|
||||
channel_id
|
||||
__________
|
||||
.. autoproperty:: ytdl_sub.downloaders.youtube_downloader.YoutubeChannelDownloaderOptions.channel_id
|
||||
|
||||
channel_avatar_path
|
||||
___________________
|
||||
.. autoproperty:: ytdl_sub.downloaders.youtube_downloader.YoutubeChannelDownloaderOptions.channel_avatar_path
|
||||
|
||||
channel_banner_path
|
||||
___________________
|
||||
.. autoproperty:: ytdl_sub.downloaders.youtube_downloader.YoutubeChannelDownloaderOptions.channel_banner_path
|
||||
|
||||
before
|
||||
______
|
||||
.. autoproperty:: ytdl_sub.downloaders.youtube_downloader.YoutubeChannelDownloaderOptions.before
|
||||
|
||||
after
|
||||
_____
|
||||
.. autoproperty:: ytdl_sub.downloaders.youtube_downloader.YoutubeChannelDownloaderOptions.after
|
||||
_______
|
||||
.. autoclass:: ytdl_sub.downloaders.youtube_downloader.YoutubeChannelDownloaderOptions()
|
||||
:members:
|
||||
:member-order: bysource
|
||||
:inherited-members:
|
||||
:exclude-members: get_date_range
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
playlist
|
||||
''''''''
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
youtube:
|
||||
download_strategy: "playlist"
|
||||
|
||||
playlist_id
|
||||
___________
|
||||
.. autoproperty:: ytdl_sub.downloaders.youtube_downloader.YoutubePlaylistDownloaderOptions.playlist_id
|
||||
________
|
||||
.. autoclass:: ytdl_sub.downloaders.youtube_downloader.YoutubePlaylistDownloaderOptions()
|
||||
:members:
|
||||
:member-order: bysource
|
||||
:inherited-members:
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
video
|
||||
'''''
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
youtube:
|
||||
download_strategy: "video"
|
||||
|
||||
video_id
|
||||
________
|
||||
.. autoproperty:: ytdl_sub.downloaders.youtube_downloader.YoutubeVideoDownloaderOptions.video_id
|
||||
_____
|
||||
.. autoclass:: ytdl_sub.downloaders.youtube_downloader.YoutubeVideoDownloaderOptions()
|
||||
:members:
|
||||
:member-order: bysource
|
||||
:inherited-members:
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
soundcloud
|
||||
""""""""""
|
||||
''''''''''
|
||||
Download strategies for downloading music from Soundcloud. See
|
||||
:class:`Soundcloud Variables <ytdl_sub.entries.variables.soundcloud_variables>`
|
||||
for available source variables to use.
|
||||
|
||||
albums_and_singles
|
||||
''''''''''''''''''
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
soundcloud:
|
||||
download_strategy: "albums_and_singles"
|
||||
|
||||
username
|
||||
________
|
||||
.. autoproperty:: ytdl_sub.downloaders.soundcloud_downloader.SoundcloudAlbumsAndSinglesDownloadOptions.username
|
||||
|
||||
skip_premiere_tracks
|
||||
____________________
|
||||
.. autoproperty:: ytdl_sub.downloaders.soundcloud_downloader.SoundcloudAlbumsAndSinglesDownloadOptions.skip_premiere_tracks
|
||||
__________________
|
||||
.. autoclass:: ytdl_sub.downloaders.soundcloud_downloader.SoundcloudAlbumsAndSinglesDownloadOptions()
|
||||
:members:
|
||||
:member-order: bysource
|
||||
:inherited-members:
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
output_options
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
output_directory
|
||||
""""""""""""""""
|
||||
.. autoproperty:: ytdl_sub.config.preset_options.OutputOptions.output_directory
|
||||
|
||||
file_name
|
||||
"""""""""
|
||||
.. autoproperty:: ytdl_sub.config.preset_options.OutputOptions.file_name
|
||||
|
||||
thumbnail_name
|
||||
""""""""""""""
|
||||
.. autoproperty:: ytdl_sub.config.preset_options.OutputOptions.thumbnail_name
|
||||
|
||||
maintain_download_archive
|
||||
"""""""""""""""""""""""""
|
||||
.. autoproperty:: ytdl_sub.config.preset_options.OutputOptions.maintain_download_archive
|
||||
|
||||
keep_files
|
||||
""""""""""
|
||||
.. autoproperty:: ytdl_sub.config.preset_options.OutputOptions.keep_files
|
||||
.. autoclass:: ytdl_sub.config.preset_options.OutputOptions()
|
||||
:members:
|
||||
:member-order: bysource
|
||||
:exclude-members: get_upload_date_range_to_keep
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
ytdl_options
|
||||
^^^^^^^^^^^^
|
||||
""""""""""""
|
||||
.. autoclass:: ytdl_sub.config.preset_options.YTDLOptions()
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
.. _overrides:
|
||||
|
||||
overrides
|
||||
^^^^^^^^^
|
||||
"""""""""
|
||||
.. autoclass:: ytdl_sub.config.preset_options.Overrides()
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
Plugins
|
||||
^^^^^^^
|
||||
"""""""
|
||||
Plugins are used to perform any type of post-processing to the already downloaded files.
|
||||
|
||||
music_tags
|
||||
""""""""""
|
||||
''''''''''
|
||||
.. autoclass:: ytdl_sub.plugins.music_tags.MusicTagsOptions()
|
||||
:members:
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
nfo
|
||||
"""
|
||||
'''
|
||||
.. autoclass:: ytdl_sub.plugins.nfo_tags.NfoTagsOptions()
|
||||
:members:
|
||||
:member-order: bysource
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
nfo_output_directory
|
||||
""""""""""""""""""""
|
||||
''''''''''''''''''''
|
||||
.. autoclass:: ytdl_sub.plugins.output_directory_nfo_tags.OutputDirectoryNfoTagsOptions()
|
||||
:members:
|
||||
:member-order: bysource
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
.. _source-variables:
|
||||
|
||||
Source Variables
|
||||
----------------
|
||||
Source variables are ``{variables}`` that contain metadata from downloaded media.
|
||||
These variables can be used in StringFormatters, but not OverrideFormatters.
|
||||
|
||||
.. autoclass:: ytdl_sub.entries.variables.entry_variables.SourceVariables
|
||||
|
||||
.. _youtube-variables:
|
||||
|
||||
Youtube Variables
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
|
@ -176,6 +149,8 @@ Youtube Variables
|
|||
:inherited-members:
|
||||
:undoc-members:
|
||||
|
||||
.. _soundcloud-variables:
|
||||
|
||||
Soundcloud Variables
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
.. automodule:: ytdl_sub.entries.variables.soundcloud_variables
|
||||
|
|
@ -183,16 +158,18 @@ Soundcloud Variables
|
|||
:inherited-members:
|
||||
:undoc-members:
|
||||
|
||||
Formatters
|
||||
----------
|
||||
Formatters are strings that can contain ``{variables}`` that are overwritten at
|
||||
run-time with values assigned to that variable. There are two different types of
|
||||
formatters.
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Config Types
|
||||
------------
|
||||
The `config.yaml`_ uses various types for its configurable fields. Below is a definition for each type.
|
||||
|
||||
String Formatter
|
||||
^^^^^^^^^^^^^^^^
|
||||
.. autoclass:: ytdl_sub.validators.string_formatter_validators.StringFormatterValidator()
|
||||
|
||||
Overrides Formatter
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
.. autoclass:: ytdl_sub.validators.string_formatter_validators.OverridesStringFormatterValidator()
|
||||
|
||||
.. autoclass:: ytdl_sub.validators.string_datetime.StringDatetimeValidator()
|
||||
|
||||
.. autoclass:: ytdl_sub.validators.string_formatter_validators.DictFormatterValidator()
|
||||
|
||||
.. autoclass:: ytdl_sub.validators.string_formatter_validators.OverridesDictFormatterValidator()
|
||||
|
|
@ -2,20 +2,20 @@ ytdl-sub: to enjoy media in your favorite player
|
|||
================================================
|
||||
|
||||
ytdl-sub is an `open-source <https://github.com/jmbannon/ytdl-sub>`_
|
||||
python app that strives to automate downloading media and preparing it for
|
||||
python app that automates downloading media and preparing it for
|
||||
consumption in your favorite media player in the most hassle-free way
|
||||
possible.
|
||||
|
||||
If you are new to ytdl-sub, head over to the
|
||||
:doc:`Getting Started <getting_started>`
|
||||
page. This guide shows how you can install ytdl-sub, configure it to download
|
||||
page. This guide explains how you can use ytdl-sub, install, configure it to download
|
||||
various types of media, and start downloading.
|
||||
|
||||
Contents
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 10
|
||||
:maxdepth: 2
|
||||
|
||||
getting_started
|
||||
config
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ john_smith_recent_archive:
|
|||
# only keep the last 14-days worth of videos, and delete the rest.
|
||||
#
|
||||
# The only difference between this example and the one above is
|
||||
# - output_options.keep_files.after
|
||||
# - output_options.keep_files_after
|
||||
# This is saying "only keep files if they were uploaded in the last 14 days".
|
||||
# All other files that this subscription had previously downloaded will be
|
||||
# deleted.
|
||||
|
|
@ -85,5 +85,4 @@ john_smith_rolling_archive:
|
|||
break_on_reject: True
|
||||
break_on_existing: True
|
||||
output_options:
|
||||
keep_files:
|
||||
after: today-2weeks
|
||||
keep_files_after: today-2weeks
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
from typing import Optional
|
||||
|
||||
from yt_dlp.utils import DateRange
|
||||
from yt_dlp.utils import sanitize_filename
|
||||
|
||||
from ytdl_sub.entries.entry import Entry
|
||||
from ytdl_sub.validators.date_range_validator import DateRangeValidator
|
||||
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
|
||||
|
|
@ -86,13 +87,32 @@ class Overrides(DictFormatterValidator):
|
|||
|
||||
|
||||
class OutputOptions(StrictDictValidator):
|
||||
"""Where to output the final files and thumbnails"""
|
||||
"""
|
||||
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: "{sanitized_title}.{ext}"
|
||||
# optional
|
||||
thumbnail_name: "{sanitized_title}.{thumbnail_ext}"
|
||||
maintain_download_archive: True
|
||||
keep_files_before: now
|
||||
keep_files_after: 19000101
|
||||
"""
|
||||
|
||||
_required_keys = {"output_directory", "file_name"}
|
||||
_optional_keys = {
|
||||
"thumbnail_name",
|
||||
"maintain_download_archive",
|
||||
"keep_files",
|
||||
"keep_files_before",
|
||||
"keep_files_after",
|
||||
}
|
||||
|
||||
def __init__(self, name, value):
|
||||
|
|
@ -115,11 +135,17 @@ class OutputOptions(StrictDictValidator):
|
|||
self._maintain_download_archive = self._validate_key_if_present(
|
||||
key="maintain_download_archive", validator=BoolValidator, default=False
|
||||
)
|
||||
self._keep_files = self._validate_key_if_present(
|
||||
key="keep_files", validator=DateRangeValidator
|
||||
|
||||
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 and not self.maintain_download_archive:
|
||||
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"
|
||||
)
|
||||
|
|
@ -164,34 +190,34 @@ class OutputOptions(StrictDictValidator):
|
|||
return self._maintain_download_archive.value
|
||||
|
||||
@property
|
||||
def keep_files(self) -> DateRangeValidator:
|
||||
def keep_files_before(self) -> Optional[StringDatetimeValidator]:
|
||||
"""
|
||||
Optional. Requires ``maintain_download_archive`` set to True.
|
||||
|
||||
Only keeps files that are uploaded in the defined range. Should be formatted as:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
output_options:
|
||||
keep_files:
|
||||
before:
|
||||
after:
|
||||
|
||||
where ``before`` and ``after`` are date-times. A common usage of this option is to only
|
||||
fill in the after, such as:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
output_options:
|
||||
keep_files:
|
||||
after: today-2weeks
|
||||
|
||||
Which translates to 'keep files uploaded in the last two weeks'.
|
||||
|
||||
By default, ytdl-sub will keep all files.
|
||||
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
|
||||
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
|
||||
|
||||
def get_upload_date_range_to_keep(self) -> Optional[DateRange]:
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
Date range if the 'before' or 'after' is defined. None otherwise.
|
||||
"""
|
||||
if self.keep_files_before or self.keep_files_after:
|
||||
return DateRange(
|
||||
start=self.keep_files_after.datetime_str if self.keep_files_after else None,
|
||||
end=self.keep_files_before.datetime_str if self.keep_files_before else None,
|
||||
)
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -71,6 +71,25 @@ class SoundcloudDownloader(
|
|||
|
||||
|
||||
class SoundcloudAlbumsAndSinglesDownloadOptions(SoundcloudDownloaderOptions):
|
||||
"""
|
||||
Downloads a soundcloud user's entire discography. Groups together album tracks and considers
|
||||
any track not in an album as a single. Also includes any collaboration tracks.
|
||||
|
||||
Usage:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
soundcloud:
|
||||
# required
|
||||
download_strategy: "albums_and_singles"
|
||||
username: soundcloud_username_from_url
|
||||
# optional
|
||||
skip_premiere_tracks: True
|
||||
|
||||
"""
|
||||
|
||||
_required_keys = {"username"}
|
||||
|
||||
def __init__(self, name, value):
|
||||
|
|
|
|||
|
|
@ -55,6 +55,29 @@ class YoutubeDownloader(
|
|||
|
||||
|
||||
class YoutubeVideoDownloaderOptions(YoutubeDownloaderOptions):
|
||||
"""
|
||||
Downloads a single youtube video. This download strategy is intended for CLI usage performing
|
||||
a one-time download of a video, not a subscription.
|
||||
|
||||
Usage:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
youtube:
|
||||
# required
|
||||
download_strategy: "video"
|
||||
video_id: "VMAPTo7RVDo"
|
||||
|
||||
CLI usage:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
ytdl-sub dl --preset "my_example_preset" --youtube.video_id "VMAPTo7RVDo"
|
||||
|
||||
"""
|
||||
|
||||
_required_keys = {"video_id"}
|
||||
|
||||
def __init__(self, name, value):
|
||||
|
|
@ -64,7 +87,7 @@ class YoutubeVideoDownloaderOptions(YoutubeDownloaderOptions):
|
|||
@property
|
||||
def video_id(self) -> str:
|
||||
"""
|
||||
Required. The ID of the video. Looks like the ``VMAPTo7RVDo`` in
|
||||
Required. The ID of the video. Looks like the ``VMAPTo7RVDo`` from
|
||||
``youtube.com/watch?v=VMAPTo7RVDo``.
|
||||
"""
|
||||
return self._video_id.value
|
||||
|
|
@ -92,6 +115,21 @@ class YoutubeVideoDownloader(YoutubeDownloader[YoutubeVideoDownloaderOptions, Yo
|
|||
|
||||
|
||||
class YoutubePlaylistDownloaderOptions(YoutubeDownloaderOptions):
|
||||
"""
|
||||
Downloads all videos from a youtube playlist.
|
||||
|
||||
Usage:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
youtube:
|
||||
# required
|
||||
download_strategy: "playlist"
|
||||
playlist_id: "UCsvn_Po0SmunchJYtttWpOxMg"
|
||||
"""
|
||||
|
||||
_required_keys = {"playlist_id"}
|
||||
|
||||
def __init__(self, name, value):
|
||||
|
|
@ -101,7 +139,8 @@ class YoutubePlaylistDownloaderOptions(YoutubeDownloaderOptions):
|
|||
@property
|
||||
def playlist_id(self) -> str:
|
||||
"""
|
||||
Required. The playlist's ID.
|
||||
Required. The playlist's ID. Looks like "UCsvn_Po0SmunchJYtttWpOxMg"
|
||||
from ``https://www.youtube.com/playlist?list=UCsvn_Po0SmunchJYtttWpOxMg``.
|
||||
"""
|
||||
return self._playlist_id.value
|
||||
|
||||
|
|
@ -141,6 +180,26 @@ class YoutubePlaylistDownloader(
|
|||
|
||||
|
||||
class YoutubeChannelDownloaderOptions(YoutubeDownloaderOptions, DateRangeValidator):
|
||||
"""
|
||||
Downloads all videos from a youtube channel.
|
||||
|
||||
Usage:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
youtube:
|
||||
# required
|
||||
download_strategy: "channel"
|
||||
channel_id: "UCsvn_Po0SmunchJYtttWpOxMg"
|
||||
# optional
|
||||
channel_avatar_path: "poster.jpg"
|
||||
channel_banner_path: "fanart.jpg"
|
||||
before: "now"
|
||||
after: "today-2weeks"
|
||||
"""
|
||||
|
||||
_required_keys = {"channel_id"}
|
||||
_optional_keys = {"before", "after", "channel_avatar_path", "channel_banner_path"}
|
||||
|
||||
|
|
@ -159,8 +218,10 @@ class YoutubeChannelDownloaderOptions(YoutubeDownloaderOptions, DateRangeValidat
|
|||
def channel_id(self) -> str:
|
||||
"""
|
||||
Required. The channel's ID. Not to be confused with the username. It should look something
|
||||
like `UCsvn_Po0SmunchJYOWpOxMg`. You can get this by opening a video and clicking on the
|
||||
channel's avatar image to take you to their channel, then check the url.
|
||||
like ``UCsvn_Po0SmunchJYOWpOxMg`` from
|
||||
``https://www.youtube.com/channel/UCsvn_Po0SmunchJYOWpOxMg``. You can get this by opening a
|
||||
video and clicking on the channel's avatar image to take you to their channel, then check
|
||||
the url.
|
||||
"""
|
||||
return self._channel_id.value
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,13 @@ from ytdl_sub.entries.base_entry import BaseEntry
|
|||
# pylint: disable=no-member
|
||||
|
||||
|
||||
class BaseEntryVariables:
|
||||
class SourceVariables:
|
||||
"""
|
||||
Abstract entry object to represent usable variables for formatting presets and subscriptions
|
||||
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
|
||||
|
|
@ -45,7 +49,7 @@ class BaseEntryVariables:
|
|||
return {property_name: getattr(self, property_name) for property_name in property_names}
|
||||
|
||||
|
||||
class EntryVariables(BaseEntryVariables):
|
||||
class EntryVariables(SourceVariables):
|
||||
@property
|
||||
def title(self: BaseEntry) -> str:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -8,18 +8,47 @@ from ytdl_sub.validators.validators import StringValidator
|
|||
|
||||
|
||||
class MusicTagsOptions(PluginOptions):
|
||||
"""
|
||||
Adds tags to every download audio file using
|
||||
`MediaFile <https://mediafile.readthedocs.io/en/latest/>`_,
|
||||
the same audio file tagging package used by
|
||||
`beets <https://beets.readthedocs.io/en/stable/>`_.
|
||||
It supports basic tags like ``title``, ``album``, ``artist`` and ``albumartist``. You can find
|
||||
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>`_.
|
||||
|
||||
Usage:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
music_tags:
|
||||
tags:
|
||||
artist: "{artist}"
|
||||
album: "{album}"
|
||||
genre: "ytdl downloaded music"
|
||||
"""
|
||||
|
||||
_required_keys = {"tags"}
|
||||
_optional_keys = {"multi_value_separator"}
|
||||
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
|
||||
self.tags = self._validate_key(key="tags", validator=DictFormatterValidator)
|
||||
self._tags = self._validate_key(key="tags", validator=DictFormatterValidator)
|
||||
|
||||
self.multi_value_separator = self._validate_key_if_present(
|
||||
key="multi_value_separator", validator=StringValidator
|
||||
)
|
||||
|
||||
@property
|
||||
def tags(self) -> DictFormatterValidator:
|
||||
"""
|
||||
Key/values of tag names/tag values. Supports source and override variables.
|
||||
"""
|
||||
return self._tags
|
||||
|
||||
|
||||
class MusicTagsPlugin(Plugin[MusicTagsOptions]):
|
||||
plugin_options_type = MusicTagsOptions
|
||||
|
|
|
|||
|
|
@ -11,14 +11,69 @@ from ytdl_sub.validators.string_formatter_validators import StringFormatterValid
|
|||
|
||||
|
||||
class NfoTagsOptions(PluginOptions):
|
||||
"""
|
||||
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.
|
||||
|
||||
Usage:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
nfo:
|
||||
nfo_name: "{sanitized_title}.nfo"
|
||||
nfo_root: "episodedetails"
|
||||
tags:
|
||||
title: "{title}"
|
||||
season: "{upload_year}"
|
||||
episode: "{upload_month}{upload_day_padded}"
|
||||
"""
|
||||
|
||||
_required_keys = {"nfo_name", "nfo_root", "tags"}
|
||||
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
@property
|
||||
def nfo_name(self) -> StringFormatterValidator:
|
||||
"""
|
||||
The NFO file name.
|
||||
"""
|
||||
return self._nfo_name
|
||||
|
||||
@property
|
||||
def nfo_root(self) -> StringFormatterValidator:
|
||||
"""
|
||||
The root tag of the NFO's XML. In the usage above, it would look like
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<episodedetails>
|
||||
</episodedetails>
|
||||
"""
|
||||
return self._nfo_root
|
||||
|
||||
@property
|
||||
def tags(self) -> DictFormatterValidator:
|
||||
"""
|
||||
Tags within the nfo_root tag. In the usage above, it would look like
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<episodedetails>
|
||||
<title>Awesome Youtube Video</title>
|
||||
<season>2022</season>
|
||||
<episode>502</episode>
|
||||
</episodedetails>
|
||||
"""
|
||||
return self._tags
|
||||
|
||||
|
||||
class NfoTagsPlugin(Plugin[NfoTagsOptions]):
|
||||
|
|
|
|||
|
|
@ -11,7 +11,20 @@ from ytdl_sub.validators.string_formatter_validators import OverridesStringForma
|
|||
|
||||
class OutputDirectoryNfoTagsOptions(PluginOptions):
|
||||
"""
|
||||
Validates config values for the output directory nfo tags plugin.
|
||||
Adds a single NFO file in the output directory. An NFO file is simply an XML file with a
|
||||
``.nfo`` extension. You can add any values into the NFO.
|
||||
|
||||
Usage:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
output_directory_nfo_tags:
|
||||
nfo_name: "tvshow.nfo"
|
||||
nfo_root: "tvshow"
|
||||
tags:
|
||||
title: "Sweet youtube TV show"
|
||||
"""
|
||||
|
||||
_required_keys = {"nfo_name", "nfo_root", "tags"}
|
||||
|
|
@ -20,13 +33,47 @@ class OutputDirectoryNfoTagsOptions(PluginOptions):
|
|||
super().__init__(name, value)
|
||||
# Since this does not create NFOs for entries, we must use the overrides formatter classes
|
||||
# to ensure we are only using values defined as string literals or in overrides
|
||||
self.nfo_name = self._validate_key(
|
||||
self._nfo_name = self._validate_key(
|
||||
key="nfo_name", validator=OverridesStringFormatterValidator
|
||||
)
|
||||
self.nfo_root = self._validate_key(
|
||||
self._nfo_root = self._validate_key(
|
||||
key="nfo_root", validator=OverridesStringFormatterValidator
|
||||
)
|
||||
self.tags = self._validate_key(key="tags", validator=OverridesDictFormatterValidator)
|
||||
self._tags = self._validate_key(key="tags", validator=OverridesDictFormatterValidator)
|
||||
|
||||
@property
|
||||
def nfo_name(self) -> OverridesStringFormatterValidator:
|
||||
"""
|
||||
The NFO file name.
|
||||
"""
|
||||
return self._nfo_name
|
||||
|
||||
@property
|
||||
def nfo_root(self) -> OverridesStringFormatterValidator:
|
||||
"""
|
||||
The root tag of the NFO's XML. In the usage above, it would look like
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<tvshow>
|
||||
</tvshow>
|
||||
"""
|
||||
return self._nfo_root
|
||||
|
||||
@property
|
||||
def tags(self) -> OverridesDictFormatterValidator:
|
||||
"""
|
||||
Tags within the nfo_root tag. In the usage above, it would look like
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<tvshow>
|
||||
<title>Sweet youtube TV show</title>
|
||||
</tvshow>
|
||||
"""
|
||||
return self._tags
|
||||
|
||||
|
||||
class OutputDirectoryNfoTagsPlugin(Plugin[OutputDirectoryNfoTagsOptions]):
|
||||
|
|
|
|||
|
|
@ -202,10 +202,9 @@ class Subscription:
|
|||
# If output options maintains stale file deletion, perform the delete here prior to saving
|
||||
# the download archive
|
||||
if self.output_options.maintain_download_archive:
|
||||
if self.output_options.keep_files:
|
||||
self._enhanced_download_archive.remove_stale_files(
|
||||
date_range=self.output_options.keep_files.get_date_range()
|
||||
)
|
||||
date_range_to_keep = self.output_options.get_upload_date_range_to_keep()
|
||||
if date_range_to_keep:
|
||||
self._enhanced_download_archive.remove_stale_files(date_range=date_range_to_keep)
|
||||
|
||||
self._enhanced_download_archive.save_download_mappings()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,14 @@ from ytdl_sub.validators.validators import Validator
|
|||
|
||||
class StringDatetimeValidator(Validator):
|
||||
"""
|
||||
Validates a ytdl datetime string value
|
||||
String that contains a yt-dlp datetime. From their docs:
|
||||
|
||||
.. code-block:: Markdown
|
||||
|
||||
A string in the format YYYYMMDD or
|
||||
(now|today|yesterday|date)[+-][0-9](microsecond|second|minute|hour|day|week|month|year)(s)
|
||||
|
||||
Valid examples are ``now-2weeks`` or ``20200101``.
|
||||
"""
|
||||
|
||||
_expected_value_type = str
|
||||
|
|
|
|||
|
|
@ -13,7 +13,28 @@ from ytdl_sub.validators.validators import Validator
|
|||
|
||||
class StringFormatterValidator(Validator):
|
||||
"""
|
||||
Ensures user-created formatter strings are valid
|
||||
String that can use
|
||||
:class:`source variables <ytdl_sub.entries.variables.entry_variables.SourceVariables>`
|
||||
and
|
||||
:class:`overrides <ytdl_sub.config.preset_options.Overrides>`
|
||||
for populating things like file paths and metadata.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
"{tv_show_file_name}.s{upload_year}.e{upload_month}{upload_day_padded}.{ext}"
|
||||
|
||||
is valid when using
|
||||
:class:`youtube variables <ytdl_sub.entries.variables.youtube_variables.YoutubeVideoVariables>`
|
||||
with the following overrides:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
overrides:
|
||||
tv_show_file_name: "sweet_tv_show"
|
||||
|
||||
and would resolve to something like ``sweet_tv_show.s2022.e502.mp4``.
|
||||
"""
|
||||
|
||||
_expected_value_type = str
|
||||
|
|
@ -132,15 +153,26 @@ class StringFormatterValidator(Validator):
|
|||
return formatter.format_string
|
||||
|
||||
|
||||
# pylint: disable=line-too-long
|
||||
class OverridesStringFormatterValidator(StringFormatterValidator):
|
||||
"""
|
||||
A string formatter that should strictly use overrides that resolve without any entry variables.
|
||||
String that can `only` use :class:`overrides <ytdl_sub.config.preset_options.Overrides>`.
|
||||
|
||||
Used in fields that do not touch the downloaded files themselves, but instead, `single`
|
||||
things like
|
||||
:func:`output_directory <ytdl_sub.config.preset_options.OutputOptions.output_directory>`
|
||||
or the fields in
|
||||
:class:`nfo_output_directory <ytdl_sub.plugins.output_directory_nfo_tags.OutputDirectoryNfoTagsOptions>`
|
||||
"""
|
||||
|
||||
|
||||
# pylint: enable=line-too-long
|
||||
|
||||
|
||||
class DictFormatterValidator(LiteralDictValidator):
|
||||
"""
|
||||
Validates a dictionary made up of key: string_formatters
|
||||
A dict made up of
|
||||
:class:`~ytdl_sub.validators.string_formatter_validators.StringFormatterValidator`.
|
||||
"""
|
||||
|
||||
_key_validator = StringFormatterValidator
|
||||
|
|
@ -164,8 +196,8 @@ class DictFormatterValidator(LiteralDictValidator):
|
|||
|
||||
class OverridesDictFormatterValidator(DictFormatterValidator):
|
||||
"""
|
||||
Validates a dictionary made up of key: string_formatters, that must be resolved by overrides
|
||||
only.
|
||||
A dict made up of
|
||||
:class:`~ytdl_sub.validators.string_formatter_validators.OverridesStringFormatterValidator`.
|
||||
"""
|
||||
|
||||
_key_validator = OverridesStringFormatterValidator
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ def expected_recent_channel_download():
|
|||
@pytest.fixture
|
||||
def rolling_recent_channel_subscription_dict(recent_channel_subscription_dict):
|
||||
return mergedeep.merge(
|
||||
recent_channel_subscription_dict, {"output_options": {"keep_files": {"after": "20181101"}}}
|
||||
recent_channel_subscription_dict, {"output_options": {"keep_files_after": "20181101"}}
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue