From d979465ff927c7b09c1361842e2e841d46717ca1 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Sun, 22 Oct 2023 22:59:29 -0700 Subject: [PATCH] [FEATURE] Music prebuilt presets --- src/ytdl_sub/prebuilt_presets/__init__.py | 6 +- .../prebuilt_presets/music/__init__.py | 9 ++ .../prebuilt_presets/music/music.yaml | 147 ++++++++++++++++++ .../prebuilt_presets/tv_show/__init__.py | 77 ++++----- .../prebuilt_presets/test_prebuilt_presets.py | 16 +- 5 files changed, 198 insertions(+), 57 deletions(-) create mode 100644 src/ytdl_sub/prebuilt_presets/music/__init__.py create mode 100644 src/ytdl_sub/prebuilt_presets/music/music.yaml diff --git a/src/ytdl_sub/prebuilt_presets/__init__.py b/src/ytdl_sub/prebuilt_presets/__init__.py index 43ae2433..036159a0 100644 --- a/src/ytdl_sub/prebuilt_presets/__init__.py +++ b/src/ytdl_sub/prebuilt_presets/__init__.py @@ -5,8 +5,6 @@ from typing import Set import mergedeep -from ytdl_sub.prebuilt_presets.tv_show import TvShowByDatePresets -from ytdl_sub.prebuilt_presets.tv_show import TvShowCollectionPresets from ytdl_sub.utils.yaml import load_yaml @@ -27,3 +25,7 @@ PREBUILT_PRESET_NAMES: Set[str] = set(PREBUILT_PRESETS.keys()) PUBLISHED_PRESET_NAMES: Set[str] = { name for name in PREBUILT_PRESET_NAMES if not name.startswith("_") } + + +class PrebuiltPresets: + preset_names: Set[str] diff --git a/src/ytdl_sub/prebuilt_presets/music/__init__.py b/src/ytdl_sub/prebuilt_presets/music/__init__.py new file mode 100644 index 00000000..15b39113 --- /dev/null +++ b/src/ytdl_sub/prebuilt_presets/music/__init__.py @@ -0,0 +1,9 @@ +from ytdl_sub.prebuilt_presets import _, PrebuiltPresets + + + +class MusicPresets(PrebuiltPresets): + + kodi_tv_show_by_date = _ + jellyfin_tv_show_by_date = _ + plex_tv_show_by_date = _ diff --git a/src/ytdl_sub/prebuilt_presets/music/music.yaml b/src/ytdl_sub/prebuilt_presets/music/music.yaml new file mode 100644 index 00000000..d614c4af --- /dev/null +++ b/src/ytdl_sub/prebuilt_presets/music/music.yaml @@ -0,0 +1,147 @@ +presets: + + _music_base: + output_options: + output_directory: "{music_directory}" + file_name: "{track_full_path}" + thumbnail_name: "{album_cover_path}" + download_archive_name: ".{subscription_name_sanitized}-download-archive.json" + maintain_download_archive: True + + ytdl_options: + break_on_existing: True + + format: "ba[ext=webm]/ba" + + audio_extract: + codec: "best" + + music_tags: + artist: "{track_artist}" + albumartist: "{track_album_artist}" + title: "{track_title}" + album: "{track_album}" + track: "{track_number}" + tracktotal: "{track_total}" + year: "{track_year}" + # multi-tags + artists: + - "{track_artist}" + albumartists: + - "{track_album_artist}" + genre: + - "{track_genre}" + + overrides: + # Subscription overrides + subscription_indent_1: "Unset" # genre + url: "{subscription_value}" + + # Track Overrides + track_title: "{title}" + track_album: "Singles" + track_artist: "{channel}" + track_album_artist: "{track_artist}" + track_number: "1" + track_number_padded: "01" + track_total: "1" + track_year: "{upload_year}" + track_genre: "{subscription_indent_1}" + + # Directory Overrides + music_directory: "/music/tagged_manual" + artist_dir: "{track_artist_sanitized}" + album_dir: "[{track_year}] {track_album_sanitized}" + track_file_name: "{track_number_padded} - {track_title_sanitized}.{ext}" + track_full_path: "{artist_dir}/{album_dir}/{track_file_name}" + album_cover_path: "{artist_dir}/{album_dir}/folder.{thumbnail_ext}" + + + "Single": + preset: + - "_music_base" + + download: + - "{url}" + + + _albums_from_playlists: + preset: + - "Single" + + overrides: + track_album: "{playlist_title}" + track_number: "{playlist_index}" + track_number_padded: "{playlist_index_padded}" + track_total: "{playlist_count}" + track_year: "{playlist_max_upload_year}" + + + _albums_from_chapters: + preset: "Single" + + chapters: + embed_chapters: True + split_by_chapters: + when_no_chapters: "pass" + + overrides: + track_title: "{chapter_title}" # Chapter title is the track title + track_album: "{title}" # Video's title is the album title + track_number: "{chapter_index}" + track_number_padded: "{chapter_index_padded}" + track_total: "{chapter_count}" + + "SoundCloud Discography": + preset: "_music_base" + + # Download using the multi_url strategy + download: + download_strategy: "multi_url" + urls: + # The first URL will be all the artist's tracks. + # Treat these as singles - an album with a single track + - url: "{url}/tracks" + variables: + sc_track_album: "{title}" + sc_track_number: "1" + sc_track_number_padded: "01" + sc_track_total: "1" + sc_track_year: "{upload_year}" + # Set the second URL to the artist's albums. If a track belongs to both + # to an album and tracks (in the URL above), it will resolve to this + # URL and include the album metadata we set below. + - url: "{url}/albums" + variables: + sc_track_album: "{playlist_title}" + sc_track_number: "{playlist_index}" + sc_track_number_padded: "{playlist_index_padded}" + sc_track_total: "{playlist_count}" + sc_track_year: "{playlist_max_upload_year}" + + # Override various track properties using playlist variables. + overrides: + track_album: "{sc_track_album}" + track_number: "{sc_track_number}" + track_number_padded: "{sc_track_number_padded}" + track_total: "{sc_track_total}" + track_year: "{sc_track_year}" + + "YouTube Releases": + preset: + - "_albums_from_playlists" + + "Bandcamp": + preset: + - "_albums_from_playlists" + regex: + from: + title: + match: + - ".*? - (.*)" # Captures 'Some - Song' from 'Emily Hopkins - Some - Song' + capture_group_names: + - "captured_track_title" + capture_group_defaults: + - "{title}" + overrides: + track_title: "{captured_track_title}" diff --git a/src/ytdl_sub/prebuilt_presets/tv_show/__init__.py b/src/ytdl_sub/prebuilt_presets/tv_show/__init__.py index e2210061..def736a1 100644 --- a/src/ytdl_sub/prebuilt_presets/tv_show/__init__.py +++ b/src/ytdl_sub/prebuilt_presets/tv_show/__init__.py @@ -1,26 +1,4 @@ -from typing import Set - - -class PrebuiltPreset: - """placeholder""" - - -_ = PrebuiltPreset() - - -class PrebuiltPresets: - @classmethod - def get_preset_names(cls) -> Set[str]: - """ - Returns - ------- - Preset names in the set - """ - return set( - preset_name - for preset_name in dir(cls) - if isinstance(getattr(cls, preset_name), PrebuiltPreset) - ) +from ytdl_sub.prebuilt_presets import PrebuiltPresets class TvShowByDatePresets(PrebuiltPresets): @@ -28,39 +6,44 @@ class TvShowByDatePresets(PrebuiltPresets): TV Show by Date presets create a TV show from a single URL using upload dates as season/episode numbers. """ - - kodi_tv_show_by_date = _ - jellyfin_tv_show_by_date = _ - plex_tv_show_by_date = _ + preset_names = { + "kodi_tv_show_by_date", + "jellyfin_tv_show_by_date", + "plex_tv_show_by_date", + } class TvShowByDateEpisodeFormattingPresets(PrebuiltPresets): - season_by_year__episode_by_month_day = _ - season_by_year__episode_by_month_day_reversed = _ - season_by_year_month__episode_by_day = _ - season_by_year__episode_by_download_index = _ + preset_names = { + "season_by_year__episode_by_month_day", + "season_by_year__episode_by_month_day_reversed", + "season_by_year_month__episode_by_day", + "season_by_year__episode_by_download_index", + } class TvShowCollectionPresets(PrebuiltPresets): - """ - Docstring for all TV SHOW URL presets - """ - - kodi_tv_show_collection = _ - jellyfin_tv_show_collection = _ - plex_tv_show_collection = _ + preset_names = { + "kodi_tv_show_collection", + "jellyfin_tv_show_collection", + "plex_tv_show_collection", + } class TvShowCollectionEpisodeFormattingPresets(PrebuiltPresets): - season_by_collection__episode_by_year_month_day = _ - season_by_collection__episode_by_year_month_day_reversed = _ - season_by_collection__episode_by_playlist_index = _ - season_by_collection__episode_by_playlist_index_reversed = _ + preset_names = { + "season_by_collection__episode_by_year_month_day", + "season_by_collection__episode_by_year_month_day_reversed", + "season_by_collection__episode_by_playlist_index", + "season_by_collection__episode_by_playlist_index_reversed", + } class TvShowCollectionSeasonPresets(PrebuiltPresets): - collection_season_1 = _ - collection_season_2 = _ - collection_season_3 = _ - collection_season_4 = _ - collection_season_5 = _ + preset_names = { + "collection_season_1", + "collection_season_2", + "collection_season_3", + "collection_season_4", + "collection_season_5", + } diff --git a/tests/unit/prebuilt_presets/test_prebuilt_presets.py b/tests/unit/prebuilt_presets/test_prebuilt_presets.py index d663f05a..6e5bfa71 100644 --- a/tests/unit/prebuilt_presets/test_prebuilt_presets.py +++ b/tests/unit/prebuilt_presets/test_prebuilt_presets.py @@ -6,8 +6,8 @@ import pytest from expected_download import assert_expected_downloads from expected_transaction_log import assert_transaction_log_matches -from ytdl_sub.prebuilt_presets import TvShowByDatePresets -from ytdl_sub.prebuilt_presets import TvShowCollectionPresets +from ytdl_sub.prebuilt_presets.tv_show import TvShowByDatePresets +from ytdl_sub.prebuilt_presets.tv_show import TvShowCollectionPresets from ytdl_sub.prebuilt_presets.tv_show import TvShowByDateEpisodeFormattingPresets from ytdl_sub.prebuilt_presets.tv_show import TvShowCollectionEpisodeFormattingPresets from ytdl_sub.prebuilt_presets.tv_show import TvShowCollectionSeasonPresets @@ -15,9 +15,9 @@ from ytdl_sub.subscriptions.subscription import Subscription from ytdl_sub.utils.exceptions import ValidationException -@pytest.mark.parametrize("media_player_preset", TvShowByDatePresets.get_preset_names()) +@pytest.mark.parametrize("media_player_preset", TvShowByDatePresets.preset_names) @pytest.mark.parametrize( - "tv_show_structure_preset", TvShowByDateEpisodeFormattingPresets.get_preset_names() + "tv_show_structure_preset", TvShowByDateEpisodeFormattingPresets.preset_names ) class TestPrebuiltTVShowPresets: def test_compilation( @@ -179,12 +179,12 @@ class TestPrebuiltTVShowPresets: ) -@pytest.mark.parametrize("media_player_preset", TvShowCollectionPresets.get_preset_names()) +@pytest.mark.parametrize("media_player_preset", TvShowCollectionPresets.preset_names) @pytest.mark.parametrize( - "tv_show_structure_preset", TvShowCollectionEpisodeFormattingPresets.get_preset_names() + "tv_show_structure_preset", TvShowCollectionEpisodeFormattingPresets.preset_names ) class TestPrebuiltTvShowCollectionPresets: - @pytest.mark.parametrize("season_preset", TvShowCollectionSeasonPresets.get_preset_names()) + @pytest.mark.parametrize("season_preset", TvShowCollectionSeasonPresets.preset_names) def test_compilation( self, config, @@ -208,7 +208,7 @@ class TestPrebuiltTvShowCollectionPresets: }, ) - @pytest.mark.parametrize("season_preset", TvShowCollectionSeasonPresets.get_preset_names()) + @pytest.mark.parametrize("season_preset", TvShowCollectionSeasonPresets.preset_names) def test_compilation_errors_missing_one( self, config,