Overhaul docs for readthedocs (#20)
This commit is contained in:
parent
b37a138f65
commit
ab2df88299
21 changed files with 520 additions and 157 deletions
|
|
@ -49,6 +49,8 @@ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
|||
#
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
html_theme_options = {"navigation_depth": 10}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
|
|
@ -56,6 +58,6 @@ html_theme = "sphinx_rtd_theme"
|
|||
|
||||
# Do not show full module path in api docs
|
||||
add_module_names = False
|
||||
python_use_unqualified_type_names = True
|
||||
python_use_unqualified_type_names = False
|
||||
napoleon_numpy_docstrings = True
|
||||
napoleon_use_rtype = False
|
||||
|
|
|
|||
198
docs/config.rst
Normal file
198
docs/config.rst
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
Config
|
||||
======
|
||||
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.
|
||||
|
||||
The ``config.yaml`` is made up of two sections:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
configuration:
|
||||
presets:
|
||||
|
||||
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
|
||||
"""""""
|
||||
|
||||
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
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
playlist
|
||||
''''''''
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
youtube:
|
||||
download_strategy: "playlist"
|
||||
|
||||
playlist_id
|
||||
___________
|
||||
.. autoproperty:: ytdl_sub.downloaders.youtube_downloader.YoutubePlaylistDownloaderOptions.playlist_id
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
video
|
||||
'''''
|
||||
.. code-block:: yaml
|
||||
|
||||
presets:
|
||||
my_example_preset:
|
||||
youtube:
|
||||
download_strategy: "video"
|
||||
|
||||
video_id
|
||||
________
|
||||
.. autoproperty:: ytdl_sub.downloaders.youtube_downloader.YoutubeVideoDownloaderOptions.video_id
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
soundcloud
|
||||
""""""""""
|
||||
|
||||
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
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
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
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
ytdl_options
|
||||
^^^^^^^^^^^^
|
||||
.. autoclass:: ytdl_sub.config.preset_options.YTDLOptions()
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
overrides
|
||||
^^^^^^^^^
|
||||
.. autoclass:: ytdl_sub.config.preset_options.Overrides()
|
||||
|
||||
Plugins
|
||||
^^^^^^^
|
||||
|
||||
music_tags
|
||||
""""""""""
|
||||
.. autoclass:: ytdl_sub.plugins.music_tags.MusicTagsOptions()
|
||||
|
||||
nfo
|
||||
"""
|
||||
.. autoclass:: ytdl_sub.plugins.nfo_tags.NfoTagsOptions()
|
||||
|
||||
nfo_output_directory
|
||||
""""""""""""""""""""
|
||||
.. autoclass:: ytdl_sub.plugins.output_directory_nfo_tags.OutputDirectoryNfoTagsOptions()
|
||||
|
||||
Source Variables
|
||||
----------------
|
||||
Source variables are ``{variables}`` that contain metadata from downloaded media.
|
||||
These variables can be used in StringFormatters, but not OverrideFormatters.
|
||||
|
||||
Youtube Variables
|
||||
^^^^^^^^^^^^^^^^^
|
||||
.. automodule:: ytdl_sub.entries.variables.youtube_variables
|
||||
:members:
|
||||
:inherited-members:
|
||||
:undoc-members:
|
||||
|
||||
Soundcloud Variables
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
.. automodule:: ytdl_sub.entries.variables.soundcloud_variables
|
||||
:members:
|
||||
: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.
|
||||
|
||||
String Formatter
|
||||
^^^^^^^^^^^^^^^^
|
||||
.. autoclass:: ytdl_sub.validators.string_formatter_validators.StringFormatterValidator()
|
||||
|
||||
Overrides Formatter
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
.. autoclass:: ytdl_sub.validators.string_formatter_validators.OverridesStringFormatterValidator()
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
Examples
|
||||
========
|
||||
|
||||
Hello, WIP
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:maxdepth: 2
|
||||
|
||||
kodi_tv_shows
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
Kodi/Jellyfin TV Shows
|
||||
======================
|
||||
|
||||
Config
|
||||
|
||||
.. include:: ../../../examples/kodi_tv_shows_config.yaml
|
||||
:literal:
|
||||
|
||||
|
||||
|
||||
.. include:: ../../../examples/kodi_tv_shows_subscriptions.yaml
|
||||
:literal:
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
Format Variables
|
||||
================
|
||||
|
||||
Hello, WIP
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:maxdepth: 2
|
||||
|
||||
soundcloud
|
||||
youtube
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
Soundcloud Track
|
||||
----------------
|
||||
|
||||
.. automodule:: ytdl_sub.entries.variables.soundcloud_variables
|
||||
:members:
|
||||
:inherited-members:
|
||||
:undoc-members:
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
Youtube Video
|
||||
-------------
|
||||
|
||||
.. automodule:: ytdl_sub.entries.variables.youtube_variables
|
||||
:members:
|
||||
:inherited-members:
|
||||
:undoc-members:
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
Configuration
|
||||
=============
|
||||
|
||||
Hello, WIP
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:maxdepth: 2
|
||||
|
||||
format_variables/index
|
||||
examples/index
|
||||
48
docs/examples.rst
Normal file
48
docs/examples.rst
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
Examples
|
||||
========
|
||||
|
||||
This page shows how you can use ytdl-sub for various use cases. These
|
||||
are the configs I personally use and have incorporated as part of the e2e tests.
|
||||
|
||||
Each example has a ``config.yaml`` and ``subscription.yaml``. The config defines
|
||||
`how` you format your media, whereas the subscription defines `what` you
|
||||
download plus some additional configuring if needed.
|
||||
|
||||
Also note that ``kodi`` examples are applicable for ``jellyfin``, ``emby``, and
|
||||
``plex`` with the
|
||||
`XBMC Movies <https://github.com/gboudreau/XBMCnfoMoviesImporter.bundle>`_
|
||||
or
|
||||
`XBMC TV Show <https://github.com/gboudreau/XBMCnfoTVImporter.bundle>`_
|
||||
Plex importer. We would like to improve Plex support, please chime in
|
||||
`here <https://github.com/jmbannon/ytdl-sub/issues/6>`_
|
||||
if you have experience with importing custom videos with metadata.
|
||||
|
||||
Kodi/Jellyfin TV Shows
|
||||
----------------------
|
||||
|
||||
config.yaml
|
||||
^^^^^^^^^^^
|
||||
|
||||
.. include:: ../examples/kodi_tv_shows_config.yaml
|
||||
:literal:
|
||||
|
||||
subscriptions.yaml
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. include:: ../examples/kodi_tv_shows_subscriptions.yaml
|
||||
:literal:
|
||||
|
||||
Kodi/Jellyfin Music Videos
|
||||
--------------------------
|
||||
|
||||
config.yaml
|
||||
^^^^^^^^^^^
|
||||
|
||||
.. include:: ../examples/kodi_music_videos_config.yaml
|
||||
:literal:
|
||||
|
||||
subscriptions.yaml
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. include:: ../examples/kodi_music_videos_subscriptions.yaml
|
||||
:literal:
|
||||
58
docs/getting_started.rst
Normal file
58
docs/getting_started.rst
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
Getting Started
|
||||
===============
|
||||
|
||||
The goal of this app is automate the downloading and metadata creation of audio and video files, then place the files
|
||||
in a directory that gets read by your media player/server. Everyone stores and watches their media differently, so
|
||||
we strive for comprehensive and simplistic customization to fit all self-hosting needs.
|
||||
|
||||
Audio metadata is just a matter of adding tags to the audio file. The backend plugin we use supports practically every
|
||||
common audio file type, so there should (hopefully) be no issues getting audio recognized by your media player.
|
||||
|
||||
Video metadata on the other hand, is currently geared toward generating Kodi/Jellyfin/Emby NFO files. Plex uses metadata
|
||||
written within MP4 containers - we do not support that currently, but could be added as a plugin in the future.
|
||||
|
||||
Usage Ideas
|
||||
-----------
|
||||
Below is a list ways you can use ytdl-sub to download and consume different kinds of media in the representation you
|
||||
prefer.
|
||||
|
||||
* Download a Youtube channel
|
||||
* Store the channel as its own TV Show
|
||||
* Use the channel's avatar as the TV Show poster, banner as the fanart
|
||||
* Format the season and episodes as ``Season YYYY/sYYYY.eMMDD - Video Title.mp4`` to easily navigate videos by upload date
|
||||
* Store the channel as a single season under a TV Show shared with other channels
|
||||
* Only download audio, store it as a Podcast
|
||||
* Only keep video/audio uploaded in the last `N` days
|
||||
* Great for news or podcast based channels
|
||||
|
||||
* Download a Youtube playlist
|
||||
* Download an artist's music videos playlist, store each video as a Kodi/Jellyfin/Emby Music Video
|
||||
* Only download the audio, store the playlist as an album
|
||||
|
||||
* Manually download a single Youtube video
|
||||
* Store it as a Movie
|
||||
* Download a one-hit wonder and store it as a Kodi/Jellyfin/Emby Music Video
|
||||
|
||||
* Download a soundcloud artist's discography
|
||||
* Add tags and album cover images so it shows up nicely in your music player
|
||||
|
||||
If you want to jump the gun to see how ytdl-sub can be configured to do these things, head over to the
|
||||
:doc:`examples <examples>`.
|
||||
|
||||
Install
|
||||
-------
|
||||
Once we are ready for our first release, we will add this package to pypi. Then,
|
||||
we plan to create a docker image that uses the
|
||||
`LinuxServer.io <https://www.linuxserver.io/>`_
|
||||
base image, and hopefully become a part of their fleet someday.
|
||||
|
||||
Until then, you will have to clone this repo and run it using python 3.10
|
||||
|
||||
.. code-block:: Bash
|
||||
|
||||
git clone https://github.com/jmbannon/ytdl-sub.git
|
||||
cd ytdl-sub
|
||||
|
||||
pip install -e .
|
||||
|
||||
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
Getting Started
|
||||
===============
|
||||
|
||||
The goal of this app is automate the downloading and metadata creation of audio and video files, then place the files
|
||||
in a directory that gets read by your media player/server. Everyone stores and watches their media differently, so
|
||||
we strive for comprehensive and simplistic customization to fit all self-hosting needs.
|
||||
|
||||
Audio metadata is just a matter of adding tags to the audio file. The backend plugin we use supports practically every
|
||||
common audio file type, so there should (hopefully) be no issues getting audio recognized by your media player.
|
||||
|
||||
Video metadata on the other hand, is currently geared toward generating Kodi/Jellyfin/Emby NFO files. Plex uses metadata
|
||||
written within MP4 containers - we do not support that currently, but could be added as a plugin in the future.
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:maxdepth: 2
|
||||
|
||||
usage_ideas
|
||||
install
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
Install
|
||||
=======
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
Usage Ideas
|
||||
===========
|
||||
Below is a list ways you can use ytdl-sub to download and consume different kinds of media in the representation you
|
||||
prefer.
|
||||
|
||||
* Download a Youtube channel
|
||||
* Store the channel as its own TV Show
|
||||
* Use the channel's avatar as the TV Show poster, banner as the fanart
|
||||
* Format the season and episodes as ``Season YYYY/sYYYY.eMMDD - Video Title.mp4`` to easily navigate videos by upload date
|
||||
* Store the channel as a single season under a TV Show shared with other channels
|
||||
* Only download audio, store it as a Podcast
|
||||
* Only keep video/audio uploaded in the last `N` days
|
||||
* Great for news or podcast based channels
|
||||
|
||||
* Download a Youtube playlist
|
||||
* Download an artist's music videos playlist, store each video as a Kodi/Jellyfin/Emby Music Video
|
||||
* Only download the audio, store the playlist as an album
|
||||
|
||||
* Manually download a single Youtube video
|
||||
* Store it as a Movie
|
||||
* Download a one-hit wonder and store it as a Kodi/Jellyfin/Emby Music Video
|
||||
|
||||
* Download a soundcloud artist's discography
|
||||
* Add tags and album cover images so it shows up nicely in your music player
|
||||
|
||||
If you want to jump the gun to see how ytdl-sub can be configured to do these things, head over to the
|
||||
:doc:`examples <../config/examples/index>`.
|
||||
|
||||
|
|
@ -6,16 +6,17 @@ python app that strives to automate 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 Getting Started page. This guide
|
||||
shows how you can install ytdl-sub, configure it to download various types
|
||||
of media, and start downloading.
|
||||
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
|
||||
various types of media, and start downloading.
|
||||
|
||||
Contents
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:maxdepth: 2
|
||||
:maxdepth: 10
|
||||
|
||||
getting_started/index
|
||||
config/index
|
||||
getting_started
|
||||
config
|
||||
examples
|
||||
|
|
|
|||
|
|
@ -13,12 +13,52 @@ from ytdl_sub.validators.validators import LiteralDictValidator
|
|||
|
||||
|
||||
class YTDLOptions(LiteralDictValidator):
|
||||
"""Ensures `ytdl_options` is a dict"""
|
||||
"""
|
||||
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):
|
||||
"""Ensures `overrides` is a dict"""
|
||||
"""
|
||||
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}.{sanitized_title}"
|
||||
|
||||
# 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 __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
for key in self._keys:
|
||||
|
|
@ -60,26 +100,98 @@ class OutputOptions(StrictDictValidator):
|
|||
|
||||
# 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(
|
||||
self._output_directory: OverridesStringFormatterValidator = self._validate_key(
|
||||
key="output_directory", validator=OverridesStringFormatterValidator
|
||||
)
|
||||
|
||||
# file name and thumbnails however can use entry variables
|
||||
self.file_name: StringFormatterValidator = self._validate_key(
|
||||
self._file_name: StringFormatterValidator = self._validate_key(
|
||||
key="file_name", validator=StringFormatterValidator
|
||||
)
|
||||
self.thumbnail_name = self._validate_key_if_present(
|
||||
self._thumbnail_name = self._validate_key_if_present(
|
||||
key="thumbnail_name", validator=StringFormatterValidator
|
||||
)
|
||||
|
||||
self.maintain_download_archive = self._validate_key_if_present(
|
||||
self._maintain_download_archive = self._validate_key_if_present(
|
||||
key="maintain_download_archive", validator=BoolValidator, default=False
|
||||
)
|
||||
self.delete_stale_files = self._validate_key_if_present(
|
||||
self._keep_files = self._validate_key_if_present(
|
||||
key="keep_files", validator=DateRangeValidator
|
||||
)
|
||||
|
||||
if self.delete_stale_files and not self.maintain_download_archive:
|
||||
if self._keep_files 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 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(self) -> DateRangeValidator:
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
return self._keep_files
|
||||
|
|
|
|||
|
|
@ -24,10 +24,17 @@ class SoundcloudDownloaderOptions(DownloaderValidator, ABC):
|
|||
|
||||
def __init__(self, name: str, value: dict):
|
||||
super().__init__(name=name, value=value)
|
||||
self.skip_premiere_tracks = self._validate_key(
|
||||
self._skip_premiere_tracks = self._validate_key(
|
||||
"skip_premiere_tracks", BoolValidator, default=True
|
||||
)
|
||||
|
||||
@property
|
||||
def skip_premiere_tracks(self) -> bool:
|
||||
"""
|
||||
Optional. True to skip tracks that require purchasing. False otherwise. Defaults to True.
|
||||
"""
|
||||
return self._skip_premiere_tracks.value
|
||||
|
||||
|
||||
SoundcloudDownloaderOptionsT = TypeVar(
|
||||
"SoundcloudDownloaderOptionsT", bound=SoundcloudDownloaderOptions
|
||||
|
|
@ -68,7 +75,14 @@ class SoundcloudAlbumsAndSinglesDownloadOptions(SoundcloudDownloaderOptions):
|
|||
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
self.username = self._validate_key(key="username", validator=StringValidator)
|
||||
self._username = self._validate_key(key="username", validator=StringValidator)
|
||||
|
||||
@property
|
||||
def username(self) -> str:
|
||||
"""
|
||||
Required. The Soundcloud username found in the url of their page.
|
||||
"""
|
||||
return self._username.value
|
||||
|
||||
|
||||
class SoundcloudAlbumsAndSinglesDownloader(
|
||||
|
|
@ -132,7 +146,7 @@ class SoundcloudAlbumsAndSinglesDownloader(
|
|||
"""
|
||||
Soundcloud subscription to download albums and tracks as singles.
|
||||
"""
|
||||
artist_url = self.artist_url(artist_name=self.download_options.username.value)
|
||||
artist_url = self.artist_url(artist_name=self.download_options.username)
|
||||
entry_dicts = self.extract_info_via_info_json(url=artist_url)
|
||||
|
||||
# Get all of the artist's albums
|
||||
|
|
@ -146,7 +160,7 @@ class SoundcloudAlbumsAndSinglesDownloader(
|
|||
tracks += album.album_tracks()
|
||||
|
||||
# Filter any premiere tracks if specified
|
||||
if self.download_options.skip_premiere_tracks.value:
|
||||
if self.download_options.skip_premiere_tracks:
|
||||
tracks = [track for track in tracks if not track.is_premiere()]
|
||||
|
||||
return tracks
|
||||
|
|
|
|||
|
|
@ -59,7 +59,15 @@ class YoutubeVideoDownloaderOptions(YoutubeDownloaderOptions):
|
|||
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
self.video_id = self._validate_key("video_id", StringValidator)
|
||||
self._video_id = self._validate_key("video_id", StringValidator)
|
||||
|
||||
@property
|
||||
def video_id(self) -> str:
|
||||
"""
|
||||
Required. The ID of the video. Looks like the ``VMAPTo7RVDo`` in
|
||||
``youtube.com/watch?v=VMAPTo7RVDo``.
|
||||
"""
|
||||
return self._video_id.value
|
||||
|
||||
|
||||
class YoutubeVideoDownloader(YoutubeDownloader[YoutubeVideoDownloaderOptions, YoutubeVideo]):
|
||||
|
|
@ -73,8 +81,7 @@ class YoutubeVideoDownloader(YoutubeDownloader[YoutubeVideoDownloaderOptions, Yo
|
|||
|
||||
def download(self) -> List[YoutubeVideo]:
|
||||
"""Download a single Youtube video"""
|
||||
video_id = self.download_options.video_id.value
|
||||
video_url = self.video_url(video_id=video_id)
|
||||
video_url = self.video_url(video_id=self.download_options.video_id)
|
||||
|
||||
entry_dict = self.extract_info(url=video_url)
|
||||
return [YoutubeVideo(entry_dict=entry_dict, working_directory=self.working_directory)]
|
||||
|
|
@ -89,7 +96,14 @@ class YoutubePlaylistDownloaderOptions(YoutubeDownloaderOptions):
|
|||
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
self.playlist_id = self._validate_key("playlist_id", StringValidator)
|
||||
self._playlist_id = self._validate_key("playlist_id", StringValidator)
|
||||
|
||||
@property
|
||||
def playlist_id(self) -> str:
|
||||
"""
|
||||
Required. The playlist's ID.
|
||||
"""
|
||||
return self._playlist_id.value
|
||||
|
||||
|
||||
class YoutubePlaylistDownloader(
|
||||
|
|
@ -107,8 +121,7 @@ class YoutubePlaylistDownloader(
|
|||
"""
|
||||
Downloads all videos in a Youtube playlist
|
||||
"""
|
||||
playlist_id = self.download_options.playlist_id.value
|
||||
playlist_url = self.playlist_url(playlist_id=playlist_id)
|
||||
playlist_url = self.playlist_url(playlist_id=self.download_options.playlist_id)
|
||||
playlist_videos: List[YoutubePlaylistVideo] = []
|
||||
|
||||
entry_dicts = self.extract_info_via_info_json(url=playlist_url)
|
||||
|
|
@ -134,14 +147,37 @@ class YoutubeChannelDownloaderOptions(YoutubeDownloaderOptions, DateRangeValidat
|
|||
def __init__(self, name, value):
|
||||
YoutubeDownloaderOptions.__init__(self, name, value)
|
||||
DateRangeValidator.__init__(self, name, value)
|
||||
self.channel_id = self._validate_key("channel_id", StringValidator)
|
||||
self.channel_avatar_path = self._validate_key_if_present(
|
||||
self._channel_id = self._validate_key("channel_id", StringValidator)
|
||||
self._channel_avatar_path = self._validate_key_if_present(
|
||||
"channel_avatar_path", OverridesStringFormatterValidator
|
||||
)
|
||||
self.channel_banner_path = self._validate_key_if_present(
|
||||
self._channel_banner_path = self._validate_key_if_present(
|
||||
"channel_banner_path", OverridesStringFormatterValidator
|
||||
)
|
||||
|
||||
@property
|
||||
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.
|
||||
"""
|
||||
return self._channel_id.value
|
||||
|
||||
@property
|
||||
def channel_avatar_path(self) -> Optional[OverridesStringFormatterValidator]:
|
||||
"""
|
||||
Optional. Path to store the channel's avatar thumbnail image to.
|
||||
"""
|
||||
return self._channel_avatar_path
|
||||
|
||||
@property
|
||||
def channel_banner_path(self) -> Optional[OverridesStringFormatterValidator]:
|
||||
"""
|
||||
Optional. Path to store the channel's banner image to.
|
||||
"""
|
||||
return self._channel_banner_path
|
||||
|
||||
|
||||
class YoutubeChannelDownloader(YoutubeDownloader[YoutubeChannelDownloaderOptions, YoutubeVideo]):
|
||||
downloader_options_type = YoutubeChannelDownloaderOptions
|
||||
|
|
@ -167,20 +203,11 @@ class YoutubeChannelDownloader(YoutubeDownloader[YoutubeChannelDownloaderOptions
|
|||
"""Returns full channel url"""
|
||||
return f"https://youtube.com/channel/{channel_id}"
|
||||
|
||||
@property
|
||||
def channel_id(self) -> str:
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
Channel ID
|
||||
"""
|
||||
return self.download_options.channel_id.value
|
||||
|
||||
def download(self) -> List[YoutubeVideo]:
|
||||
"""
|
||||
Downloads all videos from a channel
|
||||
"""
|
||||
channel_url = self.channel_url(channel_id=self.channel_id)
|
||||
channel_url = self.channel_url(channel_id=self.download_options.channel_id)
|
||||
channel_videos: List[YoutubeVideo] = []
|
||||
ytdl_options_overrides = {}
|
||||
|
||||
|
|
|
|||
|
|
@ -202,9 +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.delete_stale_files:
|
||||
if self.output_options.keep_files:
|
||||
self._enhanced_download_archive.remove_stale_files(
|
||||
date_range=self.output_options.delete_stale_files.get_date_range()
|
||||
date_range=self.output_options.keep_files.get_date_range()
|
||||
)
|
||||
|
||||
self._enhanced_download_archive.save_download_mappings()
|
||||
|
|
|
|||
|
|
@ -11,8 +11,18 @@ class DateRangeValidator(StrictDictValidator):
|
|||
|
||||
def __init__(self, name, value):
|
||||
super().__init__(name, value)
|
||||
self.before = self._validate_key_if_present("before", StringDatetimeValidator)
|
||||
self.after = self._validate_key_if_present("after", StringDatetimeValidator)
|
||||
self._before = self._validate_key_if_present("before", StringDatetimeValidator)
|
||||
self._after = self._validate_key_if_present("after", StringDatetimeValidator)
|
||||
|
||||
@property
|
||||
def before(self) -> Optional[StringDatetimeValidator]:
|
||||
"""Optional. Only download videos before this datetime."""
|
||||
return self._before
|
||||
|
||||
@property
|
||||
def after(self) -> Optional[StringDatetimeValidator]:
|
||||
"""Optional. Only download videos after this datetime."""
|
||||
return self._after
|
||||
|
||||
def get_date_range(self) -> Optional[DateRange]:
|
||||
"""
|
||||
|
|
@ -20,9 +30,9 @@ class DateRangeValidator(StrictDictValidator):
|
|||
-------
|
||||
Date range if the 'before' or 'after' is defined. None otherwise.
|
||||
"""
|
||||
if self.before or self.after:
|
||||
if self._before or self._after:
|
||||
return DateRange(
|
||||
start=self.after.datetime_str if self.after else None,
|
||||
end=self.before.datetime_str if self.before else None,
|
||||
start=self._after.datetime_str if self._after else None,
|
||||
end=self._before.datetime_str if self._before else None,
|
||||
)
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@ class EnhancedDownloadArchive:
|
|||
-------
|
||||
The download mapping's file name (no path)
|
||||
"""
|
||||
return f".ytdl-subscribe-{self.subscription_name}-download-mapping.json"
|
||||
return f".ytdl-sub-{self.subscription_name}-download-archive.json"
|
||||
|
||||
@property
|
||||
def _mapping_output_file_path(self):
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ def expected_full_channel_download():
|
|||
return ExpectedDownload(
|
||||
expected_md5_file_hashes={
|
||||
# Download mapping
|
||||
Path("pz/.ytdl-subscribe-pz-download-mapping.json"): "add71021318bf87a3facb965fd38bd7f",
|
||||
Path("pz/.ytdl-sub-pz-download-archive.json"): "add71021318bf87a3facb965fd38bd7f",
|
||||
|
||||
# Output directory files
|
||||
Path("pz/fanart.jpg"): "e6e323373c8902568e96e374817179cf",
|
||||
|
|
@ -158,7 +158,7 @@ def expected_recent_channel_download():
|
|||
return ExpectedDownload(
|
||||
expected_md5_file_hashes={
|
||||
# Download mapping
|
||||
Path("pz/.ytdl-subscribe-pz-download-mapping.json"): "a133d9ea8a63e239cd41b799b9031fd5",
|
||||
Path("pz/.ytdl-sub-pz-download-archive.json"): "a133d9ea8a63e239cd41b799b9031fd5",
|
||||
|
||||
# Output directory files
|
||||
Path("pz/fanart.jpg"): "e6e323373c8902568e96e374817179cf",
|
||||
|
|
@ -205,7 +205,7 @@ def expected_rolling_recent_channel_download():
|
|||
return ExpectedDownload(
|
||||
expected_md5_file_hashes={
|
||||
# Download mapping
|
||||
Path("pz/.ytdl-subscribe-pz-download-mapping.json"): "8013b4d2ba6921c9347c014ac915e3f6",
|
||||
Path("pz/.ytdl-sub-pz-download-archive.json"): "8013b4d2ba6921c9347c014ac915e3f6",
|
||||
|
||||
# Output directory files
|
||||
Path("pz/fanart.jpg"): "e6e323373c8902568e96e374817179cf",
|
||||
|
|
|
|||
Loading…
Reference in a new issue