diff --git a/docs/source/config_reference/scripting/scripting_functions.rst b/docs/source/config_reference/scripting/scripting_functions.rst index ebf238b33..a12b0ebd2 100644 --- a/docs/source/config_reference/scripting/scripting_functions.rst +++ b/docs/source/config_reference/scripting/scripting_functions.rst @@ -710,6 +710,24 @@ titlecase :description: Capitalize each word in the string. +unescape +~~~~~~~~ +:spec: ``unescape(string: String) -> String`` + +:description: + Unescape symbols like newlines or tabs to their true form. + +:usage: + +.. code-block:: python + + { + %unescape( "Hello\nWorld" ) + } + + # Hello + # World + upper ~~~~~ :spec: ``upper(string: String) -> String`` diff --git a/docs/source/prebuilt_presets/music.rst b/docs/source/prebuilt_presets/music.rst index 23323c6bc..cab784e67 100644 --- a/docs/source/prebuilt_presets/music.rst +++ b/docs/source/prebuilt_presets/music.rst @@ -6,6 +6,12 @@ Music downloadable by yt-dlp comes in many flavors. ``ytdl-sub`` offers a suite of various presets for handling some of the most popular forms of uploaded music content. +.. hint:: + + The subscription *value* (denoted by =) will set the genre tag for all music scraped under its key + for all music presets. + + YouTube Releases ---------------- Many artists, especially those auto-uploaded as ``Topics`` in YouTube have a section on @@ -18,11 +24,18 @@ Playlists are recognized as the album, and videos within it are tracks. YouTube Releases: = Jazz: # Sets genre tag to "Jazz" - "Thelonious Monk": "https://www.youtube.com/@theloniousmonk3870/releases" + "Thelonious Monk": "https://www.youtube.com/@officialtheloniousmonk/releases" -.. hint:: +If you are only interested in a subset of albums, you can provide their playlists as separate values in the form +of an array, like so: + +.. code-block:: yaml - The subscription *value* (denoted by =) will set the genre tag for all music scraped under its key. + YouTube Releases: + = Jazz: + "Thelonious Monk": + - "https://www.youtube.com/playlist?list=OLAK5uy_lcqINwfzkw73TPnAt6MlpB6V0gM9VzQu8" # Monk on Monk + - "https://www.youtube.com/playlist?list=OLAK5uy_nhuvjuZOO3yLIWCbQzbiWfyzkGapSIuYw" # Late Night Thelonious Monk YouTube Full Albums ------------------- @@ -38,6 +51,17 @@ Videos are recognized as the album, and chapters within it are tracks. = Lofi: "Game Chops": "https://www.youtube.com/playlist?list=PLBsm_SagFMmdWnCnrNtLjA9kzfrRkto4i" +If you are only interested in a subset of albums, you can provide their video as separate values in the form +of an array, like so: + +.. code-block:: yaml + + YouTube Full Albums: + = Lofi: + "Game Chops": + - "https://www.youtube.com/watch?v=m7vBrD7LMLI" # Zelda & Sleep Ensemble Collection + - "https://www.youtube.com/watch?v=w0XebCwSpKI" # Study Buddy ~ video game lofi mix + Soundcloud Discography ---------------------- SoundCloud tracks can be uploaded as either a single, part of an album, or a collaboration diff --git a/src/ytdl_sub/downloaders/url/downloader.py b/src/ytdl_sub/downloaders/url/downloader.py index f47cedee0..f0595945d 100644 --- a/src/ytdl_sub/downloaders/url/downloader.py +++ b/src/ytdl_sub/downloaders/url/downloader.py @@ -462,10 +462,13 @@ def _download_metadata(self, url: str, validator: UrlValidator) -> Iterable[Entr download_reversed = ScriptUtils.bool_formatter_output( self.overrides.apply_formatter(validator.download_reverse) ) + include_sibling_metadata = ScriptUtils.bool_formatter_output( + self.overrides.apply_formatter(validator.include_sibling_metadata) + ) parents, orphan_entries = self._download_url_metadata( url=url, - include_sibling_metadata=validator.include_sibling_metadata, + include_sibling_metadata=include_sibling_metadata, ytdl_options_overrides=metadata_ytdl_options, ) diff --git a/src/ytdl_sub/downloaders/url/validators.py b/src/ytdl_sub/downloaders/url/validators.py index 1f22faa17..1bd9a79f5 100644 --- a/src/ytdl_sub/downloaders/url/validators.py +++ b/src/ytdl_sub/downloaders/url/validators.py @@ -12,7 +12,6 @@ from ytdl_sub.validators.string_formatter_validators import OverridesBooleanFormatterValidator from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator -from ytdl_sub.validators.validators import BoolValidator from ytdl_sub.validators.validators import ListValidator @@ -86,7 +85,9 @@ def __init__(self, name, value): key="ytdl_options", validator=YTDLOptions, default={} ) self._include_sibling_metadata = self._validate_key( - key="include_sibling_metadata", validator=BoolValidator, default=False + key="include_sibling_metadata", + validator=OverridesBooleanFormatterValidator, + default="False", ) @property @@ -170,14 +171,14 @@ def ytdl_options(self) -> YTDLOptions: return self._ytdl_options @property - def include_sibling_metadata(self) -> bool: + def include_sibling_metadata(self) -> OverridesBooleanFormatterValidator: """ Optional. Whether to include sibling metadata as an entry variable, which comprises basic metadata from all other entries (including itself) that belong to the same playlist. For channels or large playlists, this becomes memory-intensive since you are storing ``n^2`` metadata. Defaults to False. """ - return self._include_sibling_metadata.value + return self._include_sibling_metadata class UrlStringOrDictValidator(UrlValidator): diff --git a/src/ytdl_sub/prebuilt_presets/helpers/url.yaml b/src/ytdl_sub/prebuilt_presets/helpers/url.yaml index 7fc1d9adf..2f7d8f37f 100644 --- a/src/ytdl_sub/prebuilt_presets/helpers/url.yaml +++ b/src/ytdl_sub/prebuilt_presets/helpers/url.yaml @@ -25,109 +25,209 @@ presets: uid: "avatar_uncropped" - name: "{banner_uncropped_thumbnail_file_name}" uid: "banner_uncropped" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url2}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url3}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url4}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url5}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url6}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url7}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url8}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url9}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url10}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url11}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url12}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url13}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url14}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url15}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url16}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url17}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url18}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url19}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url20}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url21}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url22}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url23}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url24}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url25}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url26}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url27}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url28}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url29}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url30}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url31}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url32}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url33}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url34}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url35}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url36}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url37}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url38}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url39}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url40}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url41}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url42}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url43}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url44}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url45}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url46}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url47}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url48}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url49}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url50}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url51}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url52}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url53}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url54}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url55}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url56}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url57}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url58}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url59}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url60}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url61}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url62}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url63}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url64}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url65}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url66}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url67}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url68}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url69}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url70}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url71}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url72}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url73}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url74}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url75}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url76}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url77}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url78}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url79}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url80}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url81}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url82}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url83}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url84}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url85}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url86}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url87}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url88}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url89}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url90}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url91}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url92}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url93}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url94}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url95}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url96}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url97}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url98}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url99}" + include_sibling_metadata: "{include_sibling_metadata}" - url: "{url100}" - + include_sibling_metadata: "{include_sibling_metadata}" overrides: avatar_uncropped_thumbnail_file_name: "" banner_uncropped_thumbnail_file_name: "" + include_sibling_metadata: False subscription_array: "{ [] }" subscription_value: "" diff --git a/src/ytdl_sub/prebuilt_presets/music/albums_from_chapters.yaml b/src/ytdl_sub/prebuilt_presets/music/albums_from_chapters.yaml index 0c6295164..6c9b82ee0 100644 --- a/src/ytdl_sub/prebuilt_presets/music/albums_from_chapters.yaml +++ b/src/ytdl_sub/prebuilt_presets/music/albums_from_chapters.yaml @@ -2,7 +2,9 @@ presets: # assumes that each chapter in an entry is a song _albums_from_chapters: - preset: "Single" + preset: + - "_music_base" + - "_multi_url" chapters: embed_chapters: True @@ -10,6 +12,8 @@ presets: when_no_chapters: "pass" overrides: + include_sibling_metadata: False + track_title: "{chapter_title}" # Chapter title is the track title track_album: "{title}" # Video's title is the album title track_number: "{chapter_index}" diff --git a/src/ytdl_sub/prebuilt_presets/music/albums_from_playlists.yaml b/src/ytdl_sub/prebuilt_presets/music/albums_from_playlists.yaml index d5f2cdadf..228392797 100644 --- a/src/ytdl_sub/prebuilt_presets/music/albums_from_playlists.yaml +++ b/src/ytdl_sub/prebuilt_presets/music/albums_from_playlists.yaml @@ -4,12 +4,11 @@ presets: _albums_from_playlists: preset: - "_music_base" - - download: - - url: "{url}" - include_sibling_metadata: True + - "_multi_url" overrides: + include_sibling_metadata: True + track_album: "{playlist_title}" track_number: "{playlist_index}" track_number_padded: "{playlist_index_padded}" diff --git a/src/ytdl_sub/script/functions/string_functions.py b/src/ytdl_sub/script/functions/string_functions.py index 0819a6f53..c2977db0f 100644 --- a/src/ytdl_sub/script/functions/string_functions.py +++ b/src/ytdl_sub/script/functions/string_functions.py @@ -143,3 +143,22 @@ def pad_zero(numeric: Numeric, length: Integer) -> String: length=length, char=String("0"), ) + + @staticmethod + def unescape(string: String) -> String: + """ + :description: + Unescape symbols like newlines or tabs to their true form. + + :usage: + + .. code-block:: python + + { + %unescape( "Hello\\nWorld" ) + } + + # Hello + # World + """ + return String(string.value.encode("utf-8").decode("unicode_escape")) diff --git a/tests/integration/prebuilt_presets/test_music.py b/tests/integration/prebuilt_presets/test_music.py index 9fbf4c0ca..6ef0ec0e9 100644 --- a/tests/integration/prebuilt_presets/test_music.py +++ b/tests/integration/prebuilt_presets/test_music.py @@ -1,11 +1,13 @@ import pytest from expected_download import assert_expected_downloads from expected_transaction_log import assert_transaction_log_matches +from integration.plugins.conftest import mock_chapters_class from ytdl_sub.prebuilt_presets.music import MusicPresets from ytdl_sub.subscriptions.subscription import Subscription +@pytest.mark.usefixtures(mock_chapters_class.__name__) @pytest.mark.parametrize("music_preset", MusicPresets.preset_names) class TestPrebuiltMusicPresets: @@ -29,16 +31,19 @@ def test_presets_run( }, } + num_urls = 1 + if music_preset in {"YouTube Releases", "YouTube Full Albums"}: + preset_dict["overrides"]["url2"] = "https://your.name.here.2" + num_urls = 2 + elif music_preset == "SoundCloud Discography": + num_urls = 2 # simulate albums + tracks + subscription = Subscription.from_dict( config=config, preset_name=subscription_name, preset_dict=preset_dict, ) - num_urls = 1 - if music_preset == "SoundCloud Discography": - num_urls = 2 # simulate /albums and /tracks - with mock_download_collection_entries( is_youtube_channel=False, num_urls=num_urls, is_extracted_audio=True ): diff --git a/tests/resources/expected_downloads_summaries/unit/music/YouTube Full Albums.json b/tests/resources/expected_downloads_summaries/unit/music/YouTube Full Albums.json index fe7acd682..fabc51b6b 100644 --- a/tests/resources/expected_downloads_summaries/unit/music/YouTube Full Albums.json +++ b/tests/resources/expected_downloads_summaries/unit/music/YouTube Full Albums.json @@ -1,11 +1,43 @@ { - ".ytdl-sub-subscription_test-download-archive.json": "9f6f4458d42da4561db236473896fe71", - "subscription_test/[2020] Mock Entry 20-1/01 - Mock Entry 20-1.mp3": "c967fcb589fca43fb3ae9038290e3a49", + ".ytdl-sub-subscription_test-download-archive.json": "a428f2d540b605d0804b99600372d5d1", + "subscription_test/[2020] Mock Entry 20-1/01 - " Intro.mp3": "c09c8590553141752697c6752667666a", + "subscription_test/[2020] Mock Entry 20-1/02 - " Part 1.mp3": "b25c7e1d9e614974ea255485414acc62", + "subscription_test/[2020] Mock Entry 20-1/03 - " Part 2.mp3": "3440ca7a156d8d8290e20854586f723a", + "subscription_test/[2020] Mock Entry 20-1/04 - " Part 3.mp3": "d093d2674a88b7e8f52edbf3629868ff", "subscription_test/[2020] Mock Entry 20-1/folder.jpg": "e80c508c4818454300133fe1dc1a9cd7", - "subscription_test/[2020] Mock Entry 20-2/01 - Mock Entry 20-2.mp3": "5f22ce0cdaa93518786f2d68ff088a83", + "subscription_test/[2020] Mock Entry 20-2/01 - " Intro.mp3": "d2b61d59894df0e99169a32d8cec0b2b", + "subscription_test/[2020] Mock Entry 20-2/02 - " Part 1.mp3": "30971eafe14adf282784462e1cedf822", + "subscription_test/[2020] Mock Entry 20-2/03 - " Part 2.mp3": "9e165ed71adc64a88192e686b97a0df3", + "subscription_test/[2020] Mock Entry 20-2/04 - " Part 3.mp3": "2e6851853e3634eaf52cc9258d8e375a", "subscription_test/[2020] Mock Entry 20-2/folder.jpg": "e80c508c4818454300133fe1dc1a9cd7", - "subscription_test/[2020] Mock Entry 20-3/01 - Mock Entry 20-3.mp3": "645dedee58a794da96e0e29129e90744", + "subscription_test/[2020] Mock Entry 20-3/01 - " Intro.mp3": "915b052131b2c50bc3b91fee23a58da5", + "subscription_test/[2020] Mock Entry 20-3/02 - " Part 1.mp3": "030e82f87f761f75ad19adfc4dbf1063", + "subscription_test/[2020] Mock Entry 20-3/03 - " Part 2.mp3": "810c579dac3632e1155911a6cb4ef506", + "subscription_test/[2020] Mock Entry 20-3/04 - " Part 3.mp3": "42a1ce49b8217dbe614cbb5eee5f552c", "subscription_test/[2020] Mock Entry 20-3/folder.jpg": "e80c508c4818454300133fe1dc1a9cd7", - "subscription_test/[2021] Mock Entry 21-1/01 - Mock Entry 21-1.mp3": "ad3e8f379cae386dd6d76d5af4552d59", + "subscription_test/[2020] Mock Entry 20-4/01 - " Intro.mp3": "bf0d2b91e2c7cc136dc0a56be94e0d7e", + "subscription_test/[2020] Mock Entry 20-4/02 - " Part 1.mp3": "eb8c0e34fef88b940f328956c03a2ae0", + "subscription_test/[2020] Mock Entry 20-4/03 - " Part 2.mp3": "23bcef6856e58d942cb83372434f8525", + "subscription_test/[2020] Mock Entry 20-4/04 - " Part 3.mp3": "08f50f77e9654362724a6e737f44306d", + "subscription_test/[2020] Mock Entry 20-4/folder.jpg": "e80c508c4818454300133fe1dc1a9cd7", + "subscription_test/[2020] Mock Entry 20-5/01 - " Intro.mp3": "01f501bade24e3cab1fb6b58a6d2a8ac", + "subscription_test/[2020] Mock Entry 20-5/02 - " Part 1.mp3": "c5a0b461e1e1a67b028c05a9a3a774d2", + "subscription_test/[2020] Mock Entry 20-5/03 - " Part 2.mp3": "edda09bc84916663bc946696f6cc5ce0", + "subscription_test/[2020] Mock Entry 20-5/04 - " Part 3.mp3": "083a8482ee343b8e71fd4ff0710649fe", + "subscription_test/[2020] Mock Entry 20-5/folder.jpg": "e80c508c4818454300133fe1dc1a9cd7", + "subscription_test/[2020] Mock Entry 20-6/01 - " Intro.mp3": "674f23188f8808e3b44c116e41594e76", + "subscription_test/[2020] Mock Entry 20-6/02 - " Part 1.mp3": "d8fc85cfade392b78fcf66670905a2d1", + "subscription_test/[2020] Mock Entry 20-6/03 - " Part 2.mp3": "23c7ac598d181ea16b945662beeae11e", + "subscription_test/[2020] Mock Entry 20-6/04 - " Part 3.mp3": "1dd37e65243aa545a55de73cd480580a", + "subscription_test/[2020] Mock Entry 20-6/folder.jpg": "e80c508c4818454300133fe1dc1a9cd7", + "subscription_test/[2020] Mock Entry 20-7/01 - " Intro.mp3": "c42c845f3028d910137af63af04c46a0", + "subscription_test/[2020] Mock Entry 20-7/02 - " Part 1.mp3": "514db0c82a210b9ea886e6ee2ab9327f", + "subscription_test/[2020] Mock Entry 20-7/03 - " Part 2.mp3": "112b6018f05e1aa62518001b61de0123", + "subscription_test/[2020] Mock Entry 20-7/04 - " Part 3.mp3": "5e1fb5d017a54b0156fa96ad71241f00", + "subscription_test/[2020] Mock Entry 20-7/folder.jpg": "e80c508c4818454300133fe1dc1a9cd7", + "subscription_test/[2021] Mock Entry 21-1/01 - " Intro.mp3": "c1718b35ec1ab0b83c519acd30f32927", + "subscription_test/[2021] Mock Entry 21-1/02 - " Part 1.mp3": "4fed9885e3ce0f2d1ae0134c2b22ff08", + "subscription_test/[2021] Mock Entry 21-1/03 - " Part 2.mp3": "240774666c7636f9490ab98a77ef75bb", + "subscription_test/[2021] Mock Entry 21-1/04 - " Part 3.mp3": "0087935c3158fb4151dac0920dd84002", "subscription_test/[2021] Mock Entry 21-1/folder.jpg": "e80c508c4818454300133fe1dc1a9cd7" } \ No newline at end of file diff --git a/tests/resources/expected_downloads_summaries/unit/music/YouTube Releases.json b/tests/resources/expected_downloads_summaries/unit/music/YouTube Releases.json index a43434b34..bfd9211e9 100644 --- a/tests/resources/expected_downloads_summaries/unit/music/YouTube Releases.json +++ b/tests/resources/expected_downloads_summaries/unit/music/YouTube Releases.json @@ -1,9 +1,14 @@ { - ".ytdl-sub-subscription_test-download-archive.json": "36b3c7143ac4257489791309d802af82", + ".ytdl-sub-subscription_test-download-archive.json": "82b90b449dfd345ea734cc85990246e5", "subscription_test/[2020] Download First/02 - Mock Entry 20-1.mp3": "f7169b5892ebb4285c33ee153abdbfdc", "subscription_test/[2020] Download First/03 - Mock Entry 20-2.mp3": "7ffd6a9e71c7f327e8f9765ad4645a0e", "subscription_test/[2020] Download First/04 - Mock Entry 20-3.mp3": "d3718a85ccd6cd03634cba94927496a6", "subscription_test/[2020] Download First/folder.jpg": "e80c508c4818454300133fe1dc1a9cd7", + "subscription_test/[2020] Download Second/02 - Mock Entry 20-4.mp3": "7d860070bcef126eb4677303c69e09d7", + "subscription_test/[2020] Download Second/03 - Mock Entry 20-5.mp3": "dc47b62dff859d147f9d96d5efa130c1", + "subscription_test/[2020] Download Second/04 - Mock Entry 20-6.mp3": "3fab293854b6abd1f0e14e7ca7ea34dc", + "subscription_test/[2020] Download Second/05 - Mock Entry 20-7.mp3": "acebc597556ead820434b5d796d6dc50", + "subscription_test/[2020] Download Second/folder.jpg": "e80c508c4818454300133fe1dc1a9cd7", "subscription_test/[2021] Download First/01 - Mock Entry 21-1.mp3": "6b9c7da5acce22b180fd42a463806a68", "subscription_test/[2021] Download First/folder.jpg": "e80c508c4818454300133fe1dc1a9cd7" } \ No newline at end of file diff --git a/tests/resources/transaction_log_summaries/unit/music/YouTube Full Albums.txt b/tests/resources/transaction_log_summaries/unit/music/YouTube Full Albums.txt index 5137c6b28..d9d428e4f 100644 --- a/tests/resources/transaction_log_summaries/unit/music/YouTube Full Albums.txt +++ b/tests/resources/transaction_log_summaries/unit/music/YouTube Full Albums.txt @@ -3,7 +3,10 @@ Files created: {output_directory} .ytdl-sub-subscription_test-download-archive.json {output_directory}/subscription_test/[2020] Mock Entry 20-1 - 01 - Mock Entry 20-1.mp3 + 01 - " Intro.mp3 + From Chapter Split: + Source Title: Mock Entry 20-1 + Segment: 0:00 - 0:07 Music Tags: album: Mock Entry 20-1 albumartist: subscription_test @@ -13,13 +16,67 @@ Files created: date: 2020-08-08 genres: Unset original_date: 2020-08-08 - title: Mock Entry 20-1 + title: " Intro track: 1 - tracktotal: 1 + tracktotal: 4 + year: 2020 + 02 - " Part 1.mp3 + From Chapter Split: + Source Title: Mock Entry 20-1 + Segment: 0:07 - 0:13 + Music Tags: + album: Mock Entry 20-1 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-08-08 + genres: Unset + original_date: 2020-08-08 + title: " Part 1 + track: 2 + tracktotal: 4 + year: 2020 + 03 - " Part 2.mp3 + From Chapter Split: + Source Title: Mock Entry 20-1 + Segment: 0:13 - 0:19 + Music Tags: + album: Mock Entry 20-1 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-08-08 + genres: Unset + original_date: 2020-08-08 + title: " Part 2 + track: 3 + tracktotal: 4 + year: 2020 + 04 - " Part 3.mp3 + From Chapter Split: + Source Title: Mock Entry 20-1 + Segment: 0:19 - 0:42 + Music Tags: + album: Mock Entry 20-1 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-08-08 + genres: Unset + original_date: 2020-08-08 + title: " Part 3 + track: 4 + tracktotal: 4 year: 2020 folder.jpg {output_directory}/subscription_test/[2020] Mock Entry 20-2 - 01 - Mock Entry 20-2.mp3 + 01 - " Intro.mp3 + From Chapter Split: + Source Title: Mock Entry 20-2 + Segment: 0:00 - 0:07 Music Tags: album: Mock Entry 20-2 albumartist: subscription_test @@ -29,13 +86,67 @@ Files created: date: 2020-08-08 genres: Unset original_date: 2020-08-08 - title: Mock Entry 20-2 + title: " Intro track: 1 - tracktotal: 1 + tracktotal: 4 + year: 2020 + 02 - " Part 1.mp3 + From Chapter Split: + Source Title: Mock Entry 20-2 + Segment: 0:07 - 0:13 + Music Tags: + album: Mock Entry 20-2 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-08-08 + genres: Unset + original_date: 2020-08-08 + title: " Part 1 + track: 2 + tracktotal: 4 + year: 2020 + 03 - " Part 2.mp3 + From Chapter Split: + Source Title: Mock Entry 20-2 + Segment: 0:13 - 0:19 + Music Tags: + album: Mock Entry 20-2 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-08-08 + genres: Unset + original_date: 2020-08-08 + title: " Part 2 + track: 3 + tracktotal: 4 + year: 2020 + 04 - " Part 3.mp3 + From Chapter Split: + Source Title: Mock Entry 20-2 + Segment: 0:19 - 0:42 + Music Tags: + album: Mock Entry 20-2 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-08-08 + genres: Unset + original_date: 2020-08-08 + title: " Part 3 + track: 4 + tracktotal: 4 year: 2020 folder.jpg {output_directory}/subscription_test/[2020] Mock Entry 20-3 - 01 - Mock Entry 20-3.mp3 + 01 - " Intro.mp3 + From Chapter Split: + Source Title: Mock Entry 20-3 + Segment: 0:00 - 0:07 Music Tags: album: Mock Entry 20-3 albumartist: subscription_test @@ -45,13 +156,347 @@ Files created: date: 2020-08-07 genres: Unset original_date: 2020-08-07 - title: Mock Entry 20-3 + title: " Intro + track: 1 + tracktotal: 4 + year: 2020 + 02 - " Part 1.mp3 + From Chapter Split: + Source Title: Mock Entry 20-3 + Segment: 0:07 - 0:13 + Music Tags: + album: Mock Entry 20-3 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-08-07 + genres: Unset + original_date: 2020-08-07 + title: " Part 1 + track: 2 + tracktotal: 4 + year: 2020 + 03 - " Part 2.mp3 + From Chapter Split: + Source Title: Mock Entry 20-3 + Segment: 0:13 - 0:19 + Music Tags: + album: Mock Entry 20-3 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-08-07 + genres: Unset + original_date: 2020-08-07 + title: " Part 2 + track: 3 + tracktotal: 4 + year: 2020 + 04 - " Part 3.mp3 + From Chapter Split: + Source Title: Mock Entry 20-3 + Segment: 0:19 - 0:42 + Music Tags: + album: Mock Entry 20-3 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-08-07 + genres: Unset + original_date: 2020-08-07 + title: " Part 3 + track: 4 + tracktotal: 4 + year: 2020 + folder.jpg +{output_directory}/subscription_test/[2020] Mock Entry 20-4 + 01 - " Intro.mp3 + From Chapter Split: + Source Title: Mock Entry 20-4 + Segment: 0:00 - 0:07 + Music Tags: + album: Mock Entry 20-4 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-08-06 + genres: Unset + original_date: 2020-08-06 + title: " Intro + track: 1 + tracktotal: 4 + year: 2020 + 02 - " Part 1.mp3 + From Chapter Split: + Source Title: Mock Entry 20-4 + Segment: 0:07 - 0:13 + Music Tags: + album: Mock Entry 20-4 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-08-06 + genres: Unset + original_date: 2020-08-06 + title: " Part 1 + track: 2 + tracktotal: 4 + year: 2020 + 03 - " Part 2.mp3 + From Chapter Split: + Source Title: Mock Entry 20-4 + Segment: 0:13 - 0:19 + Music Tags: + album: Mock Entry 20-4 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-08-06 + genres: Unset + original_date: 2020-08-06 + title: " Part 2 + track: 3 + tracktotal: 4 + year: 2020 + 04 - " Part 3.mp3 + From Chapter Split: + Source Title: Mock Entry 20-4 + Segment: 0:19 - 0:42 + Music Tags: + album: Mock Entry 20-4 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-08-06 + genres: Unset + original_date: 2020-08-06 + title: " Part 3 + track: 4 + tracktotal: 4 + year: 2020 + folder.jpg +{output_directory}/subscription_test/[2020] Mock Entry 20-5 + 01 - " Intro.mp3 + From Chapter Split: + Source Title: Mock Entry 20-5 + Segment: 0:00 - 0:07 + Music Tags: + album: Mock Entry 20-5 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-07-06 + genres: Unset + original_date: 2020-07-06 + title: " Intro + track: 1 + tracktotal: 4 + year: 2020 + 02 - " Part 1.mp3 + From Chapter Split: + Source Title: Mock Entry 20-5 + Segment: 0:07 - 0:13 + Music Tags: + album: Mock Entry 20-5 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-07-06 + genres: Unset + original_date: 2020-07-06 + title: " Part 1 + track: 2 + tracktotal: 4 + year: 2020 + 03 - " Part 2.mp3 + From Chapter Split: + Source Title: Mock Entry 20-5 + Segment: 0:13 - 0:19 + Music Tags: + album: Mock Entry 20-5 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-07-06 + genres: Unset + original_date: 2020-07-06 + title: " Part 2 + track: 3 + tracktotal: 4 + year: 2020 + 04 - " Part 3.mp3 + From Chapter Split: + Source Title: Mock Entry 20-5 + Segment: 0:19 - 0:42 + Music Tags: + album: Mock Entry 20-5 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-07-06 + genres: Unset + original_date: 2020-07-06 + title: " Part 3 + track: 4 + tracktotal: 4 + year: 2020 + folder.jpg +{output_directory}/subscription_test/[2020] Mock Entry 20-6 + 01 - " Intro.mp3 + From Chapter Split: + Source Title: Mock Entry 20-6 + Segment: 0:00 - 0:07 + Music Tags: + album: Mock Entry 20-6 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-07-06 + genres: Unset + original_date: 2020-07-06 + title: " Intro + track: 1 + tracktotal: 4 + year: 2020 + 02 - " Part 1.mp3 + From Chapter Split: + Source Title: Mock Entry 20-6 + Segment: 0:07 - 0:13 + Music Tags: + album: Mock Entry 20-6 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-07-06 + genres: Unset + original_date: 2020-07-06 + title: " Part 1 + track: 2 + tracktotal: 4 + year: 2020 + 03 - " Part 2.mp3 + From Chapter Split: + Source Title: Mock Entry 20-6 + Segment: 0:13 - 0:19 + Music Tags: + album: Mock Entry 20-6 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-07-06 + genres: Unset + original_date: 2020-07-06 + title: " Part 2 + track: 3 + tracktotal: 4 + year: 2020 + 04 - " Part 3.mp3 + From Chapter Split: + Source Title: Mock Entry 20-6 + Segment: 0:19 - 0:42 + Music Tags: + album: Mock Entry 20-6 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-07-06 + genres: Unset + original_date: 2020-07-06 + title: " Part 3 + track: 4 + tracktotal: 4 + year: 2020 + folder.jpg +{output_directory}/subscription_test/[2020] Mock Entry 20-7 + 01 - " Intro.mp3 + From Chapter Split: + Source Title: Mock Entry 20-7 + Segment: 0:00 - 0:07 + Music Tags: + album: Mock Entry 20-7 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-06-06 + genres: Unset + original_date: 2020-06-06 + title: " Intro track: 1 - tracktotal: 1 + tracktotal: 4 + year: 2020 + 02 - " Part 1.mp3 + From Chapter Split: + Source Title: Mock Entry 20-7 + Segment: 0:07 - 0:13 + Music Tags: + album: Mock Entry 20-7 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-06-06 + genres: Unset + original_date: 2020-06-06 + title: " Part 1 + track: 2 + tracktotal: 4 + year: 2020 + 03 - " Part 2.mp3 + From Chapter Split: + Source Title: Mock Entry 20-7 + Segment: 0:13 - 0:19 + Music Tags: + album: Mock Entry 20-7 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-06-06 + genres: Unset + original_date: 2020-06-06 + title: " Part 2 + track: 3 + tracktotal: 4 + year: 2020 + 04 - " Part 3.mp3 + From Chapter Split: + Source Title: Mock Entry 20-7 + Segment: 0:19 - 0:42 + Music Tags: + album: Mock Entry 20-7 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-06-06 + genres: Unset + original_date: 2020-06-06 + title: " Part 3 + track: 4 + tracktotal: 4 year: 2020 folder.jpg {output_directory}/subscription_test/[2021] Mock Entry 21-1 - 01 - Mock Entry 21-1.mp3 + 01 - " Intro.mp3 + From Chapter Split: + Source Title: Mock Entry 21-1 + Segment: 0:00 - 0:07 Music Tags: album: Mock Entry 21-1 albumartist: subscription_test @@ -61,8 +506,59 @@ Files created: date: 2021-08-08 genres: Unset original_date: 2021-08-08 - title: Mock Entry 21-1 + title: " Intro track: 1 - tracktotal: 1 + tracktotal: 4 + year: 2021 + 02 - " Part 1.mp3 + From Chapter Split: + Source Title: Mock Entry 21-1 + Segment: 0:07 - 0:13 + Music Tags: + album: Mock Entry 21-1 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2021-08-08 + genres: Unset + original_date: 2021-08-08 + title: " Part 1 + track: 2 + tracktotal: 4 + year: 2021 + 03 - " Part 2.mp3 + From Chapter Split: + Source Title: Mock Entry 21-1 + Segment: 0:13 - 0:19 + Music Tags: + album: Mock Entry 21-1 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2021-08-08 + genres: Unset + original_date: 2021-08-08 + title: " Part 2 + track: 3 + tracktotal: 4 + year: 2021 + 04 - " Part 3.mp3 + From Chapter Split: + Source Title: Mock Entry 21-1 + Segment: 0:19 - 0:42 + Music Tags: + album: Mock Entry 21-1 + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2021-08-08 + genres: Unset + original_date: 2021-08-08 + title: " Part 3 + track: 4 + tracktotal: 4 year: 2021 folder.jpg \ No newline at end of file diff --git a/tests/resources/transaction_log_summaries/unit/music/YouTube Releases.txt b/tests/resources/transaction_log_summaries/unit/music/YouTube Releases.txt index aad6ba116..579ff6567 100644 --- a/tests/resources/transaction_log_summaries/unit/music/YouTube Releases.txt +++ b/tests/resources/transaction_log_summaries/unit/music/YouTube Releases.txt @@ -46,6 +46,64 @@ Files created: tracktotal: 4 year: 2020 folder.jpg +{output_directory}/subscription_test/[2020] Download Second + 02 - Mock Entry 20-4.mp3 + Music Tags: + album: Download Second + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-08-06 + genres: Unset + original_date: 2020-08-06 + title: Mock Entry 20-4 + track: 2 + tracktotal: 5 + year: 2020 + 03 - Mock Entry 20-5.mp3 + Music Tags: + album: Download Second + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-07-06 + genres: Unset + original_date: 2020-07-06 + title: Mock Entry 20-5 + track: 3 + tracktotal: 5 + year: 2020 + 04 - Mock Entry 20-6.mp3 + Music Tags: + album: Download Second + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-07-06 + genres: Unset + original_date: 2020-07-06 + title: Mock Entry 20-6 + track: 4 + tracktotal: 5 + year: 2020 + 05 - Mock Entry 20-7.mp3 + Music Tags: + album: Download Second + albumartist: subscription_test + albumartists: subscription_test + artist: subscription_test + artists: subscription_test + date: 2020-06-06 + genres: Unset + original_date: 2020-06-06 + title: Mock Entry 20-7 + track: 5 + tracktotal: 5 + year: 2020 + folder.jpg {output_directory}/subscription_test/[2021] Download First 01 - Mock Entry 21-1.mp3 Music Tags: diff --git a/tests/unit/script/functions/test_string_functions.py b/tests/unit/script/functions/test_string_functions.py index e5d898f8c..a5a2961d9 100644 --- a/tests/unit/script/functions/test_string_functions.py +++ b/tests/unit/script/functions/test_string_functions.py @@ -129,17 +129,18 @@ def test_contains_any(self, value, expected_output): @pytest.mark.parametrize( "input_string, split, max_split, expected_output", [ - ("no splits", " | ", None, ["no splits"]), - ("one | split", " | ", None, ["one", "split"]), - ("max | split | one", " | ", 1, ["max", "split | one"]), + ("no splits", "' | '", None, ["no splits"]), + ("one | split", "' | '", None, ["one", "split"]), + ("max | split | one", "' | '", 1, ["max", "split | one"]), + ("multiline\ndescription", "%unescape('\\n')", None, ["multiline", "description"]), ], ) def test_split( self, input_string: str, split: str, max_split: Optional[int], expected_output: List[str] ): if max_split: - output = single_variable_output(f"{{%split('{input_string}', '{split}', {max_split})}}") + output = single_variable_output(f"{{%split('{input_string}', {split}, {max_split})}}") else: - output = single_variable_output(f"{{%split('{input_string}', '{split}')}}") + output = single_variable_output(f"{{%split('{input_string}', {split})}}") assert output == expected_output diff --git a/tests/unit/script/types/test_string.py b/tests/unit/script/types/test_string.py index c3749845b..11ad9065b 100644 --- a/tests/unit/script/types/test_string.py +++ b/tests/unit/script/types/test_string.py @@ -1,6 +1,7 @@ import re import pytest +from unit.script.conftest import single_variable_output from ytdl_sub.script.parser import STRINGS_NOT_CLOSED from ytdl_sub.script.parser import STRINGS_ONLY_ARGS @@ -45,10 +46,21 @@ def test_string_not_arg(self, string: str): ("{%string('backslash \\\\')}", "backslash \\\\"), ("{%string('''triple quote with \" ' \\''')}", "triple quote with \" ' \\"), ('{%string("""triple quote with " \' \\""")}', "triple quote with \" ' \\"), + ("{%string('supports \t tabs')}", "supports \t tabs"), ], ) def test_string(self, string: str, expected_string: str): - assert Script({"out": string}).resolve() == ScriptOutput({"out": String(expected_string)}) + assert single_variable_output(string) == expected_string + + @pytest.mark.parametrize( + "string, expected_string", + [ + ("{%unescape('literal \\n newlines')}", "literal \n newlines"), + ("{%unescape('literal \\t tabs')}", "literal \t tabs"), + ], + ) + def test_unescape(self, string: str, expected_string: str): + assert single_variable_output(string) == expected_string def test_null_is_empty_string(self): assert Script({"out": "{%string(null)}"}).resolve() == ScriptOutput({"out": String("")})