diff --git a/README.md b/README.md index eae9d50f..2fe75402 100644 --- a/README.md +++ b/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 diff --git a/docs/config.rst b/docs/config.rst index 056b8abe..7fe369be 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -30,112 +30,60 @@ 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 ` +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 ` +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 ------------------------------------------------------------------------------- @@ -151,14 +99,18 @@ 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 """""""""""""""""""" diff --git a/src/ytdl_sub/config/preset_options.py b/src/ytdl_sub/config/preset_options.py index ca827c97..198c5b1e 100644 --- a/src/ytdl_sub/config/preset_options.py +++ b/src/ytdl_sub/config/preset_options.py @@ -86,7 +86,26 @@ 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 + after: today-2weeks + """ _required_keys = {"output_directory", "file_name"} _optional_keys = { @@ -168,19 +187,8 @@ class OutputOptions(StrictDictValidator): """ 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: + Only keeps files that are uploaded in the defined range. ``before`` and ``after`` are + date-times. A common usage of this option is to only fill in the after, such as: .. code-block:: yaml diff --git a/src/ytdl_sub/downloaders/soundcloud_downloader.py b/src/ytdl_sub/downloaders/soundcloud_downloader.py index 23f1a2e6..89bea5c2 100644 --- a/src/ytdl_sub/downloaders/soundcloud_downloader.py +++ b/src/ytdl_sub/downloaders/soundcloud_downloader.py @@ -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): diff --git a/src/ytdl_sub/downloaders/youtube_downloader.py b/src/ytdl_sub/downloaders/youtube_downloader.py index 8de267cb..694f20f5 100644 --- a/src/ytdl_sub/downloaders/youtube_downloader.py +++ b/src/ytdl_sub/downloaders/youtube_downloader.py @@ -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 diff --git a/src/ytdl_sub/plugins/music_tags.py b/src/ytdl_sub/plugins/music_tags.py index 9d61ca3e..c2b13b91 100644 --- a/src/ytdl_sub/plugins/music_tags.py +++ b/src/ytdl_sub/plugins/music_tags.py @@ -8,18 +8,47 @@ from ytdl_sub.validators.validators import StringValidator class MusicTagsOptions(PluginOptions): + """ + Adds tags to every download audio file using + `MediaFile `_, + the same audio file tagging package used by + `beets `_. + 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 `_. + + 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 diff --git a/src/ytdl_sub/plugins/nfo_tags.py b/src/ytdl_sub/plugins/nfo_tags.py index e006148a..abce398a 100644 --- a/src/ytdl_sub/plugins/nfo_tags.py +++ b/src/ytdl_sub/plugins/nfo_tags.py @@ -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 + + + + + """ + 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 + + + + Awesome Youtube Video + 2022 + 502 + + """ + return self._tags class NfoTagsPlugin(Plugin[NfoTagsOptions]):