diff --git a/src/ytdl_sub/plugins/nfo_tags.py b/src/ytdl_sub/plugins/nfo_tags.py index 7e13509d..6e08124a 100644 --- a/src/ytdl_sub/plugins/nfo_tags.py +++ b/src/ytdl_sub/plugins/nfo_tags.py @@ -102,12 +102,18 @@ class SharedNfoTagsPlugin(Plugin[SharedNfoTagsOptions], ABC): return nfo_tags - def _create_nfo(self, entry: Entry, save_to_entry: bool = True) -> None: + def _create_nfo( + self, + entry: Entry, + nfo_tags: Optional[Dict[str, List[XmlElement]]] = None, + save_to_entry: bool = True, + ) -> None: # Write the nfo tags to XML with the nfo_root nfo_root = self.overrides.apply_formatter( formatter=self.plugin_options.nfo_root, entry=entry ) - nfo_tags = self._get_xml_element_dict(entry=entry) + if nfo_tags is None: + nfo_tags = self._get_xml_element_dict(entry=entry) if self.plugin_options.kodi_safe: nfo_root = to_max_3_byte_utf8_string(nfo_root) diff --git a/src/ytdl_sub/plugins/output_directory_nfo_tags.py b/src/ytdl_sub/plugins/output_directory_nfo_tags.py index a63f9684..1dcc37cf 100644 --- a/src/ytdl_sub/plugins/output_directory_nfo_tags.py +++ b/src/ytdl_sub/plugins/output_directory_nfo_tags.py @@ -1,3 +1,5 @@ +from typing import Dict +from typing import List from typing import Optional from ytdl_sub.config.preset_options import Overrides @@ -5,6 +7,7 @@ from ytdl_sub.entries.entry import Entry from ytdl_sub.plugins.nfo_tags import NfoTagsValidator from ytdl_sub.plugins.nfo_tags import SharedNfoTagsOptions from ytdl_sub.plugins.nfo_tags import SharedNfoTagsPlugin +from ytdl_sub.utils.xml import XmlElement from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive @@ -12,8 +15,8 @@ from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadAr class OutputDirectoryNfoTagsOptions(SharedNfoTagsOptions): """ Adds a single NFO file in the output directory. An NFO file is simply an XML file with a - ``.nfo`` extension. It uses the last entry's source variables which can change per download - invocation. Be cautious of which variables you use. + ``.nfo`` extension. It builds the tags using each entry's source variables, and merges all the + unique values into the final set of tags. Be cautious of which variables you use. Usage: @@ -47,38 +50,29 @@ class OutputDirectoryNfoTagsOptions(SharedNfoTagsOptions): @property def tags(self) -> NfoTagsValidator: """ - Tags within the nfo_root tag. In the usage above, it would look like - - .. code-block:: xml - - - - Sweet youtube TV show - - - Also supports xml attributes and duplicate keys: + Tags within the nfo_root tag. Also supports xml attributes and duplicate keys: .. code-block:: yaml tags: - named_season: - - tag: "{source_title}" + title: "Sweet youtube TV show" + namedseason: + - tag: "{season_name}" attributes: - number: "{collection_index}" - behavior: "merge" + number: "{season_index}" genre: - - tag: "Comedy" - behavior: "overwrite" - "Comedy" - "Drama" - Which translates to + With merged tags, it could translate to .. code-block:: xml - Sweet youtube TV show</season> + <title>Sweet youtube TV show Comedy Drama + Some Playlist + Another Playlist """ return self._tags @@ -93,17 +87,31 @@ class OutputDirectoryNfoTagsPlugin(SharedNfoTagsPlugin): enhanced_download_archive: EnhancedDownloadArchive, ): super().__init__(plugin_options, overrides, enhanced_download_archive) + self._nfo_tags: Dict[str, List[XmlElement]] = {} self._last_entry: Optional[Entry] = None def post_process_entry(self, entry: Entry) -> None: """ - Tracks the last entry processed + Merges the nfo_tags dict of all entries. Tracks the last entry processed """ + nfo_tags = self._get_xml_element_dict(entry=entry) + for key, xml_elements in nfo_tags.items(): + # Key not in nfo_tags, add and continue + if key not in self._nfo_tags: + self._nfo_tags[key] = xml_elements + continue + + # Is in nfo_tags, only add new ones + for xml_element in xml_elements: + if xml_element not in self._nfo_tags[key]: + self._nfo_tags[key].append(xml_element) + self._last_entry = entry def post_process_subscription(self): """ - Creates an NFO file in the root of the output directory using the last entry + Creates an NFO file in the root of the output directory using merged nfo tags and last entry + for the NFO root """ if self._last_entry: - self._create_nfo(entry=self._last_entry, save_to_entry=False) + self._create_nfo(entry=self._last_entry, nfo_tags=self._nfo_tags, save_to_entry=False) diff --git a/tests/e2e/plugins/test_nfo_tags.py b/tests/e2e/plugins/test_nfo_tags.py index 4c95122c..16f9dbbd 100644 --- a/tests/e2e/plugins/test_nfo_tags.py +++ b/tests/e2e/plugins/test_nfo_tags.py @@ -2,7 +2,6 @@ import pytest from e2e.expected_transaction_log import assert_transaction_log_matches from ytdl_sub.subscriptions.subscription import Subscription -from ytdl_sub.utils.exceptions import ValidationException @pytest.fixture @@ -61,6 +60,33 @@ def subscription_dict(output_directory): } +@pytest.fixture +def merged_output_nfo_preset_dict(output_directory): + return { + "preset": "yt_music_video_playlist", + "youtube": {"playlist_url": "https://youtube.com/playlist?list=PL5BC0FC26BECA5A35"}, + # override the output directory with our fixture-generated dir + "output_options": {"output_directory": output_directory}, + # download the worst format so it is fast + "ytdl_options": { + "format": "worst[ext=mp4]", + }, + "output_directory_nfo_tags": { + "nfo_name": "tvshow.nfo", + "nfo_root": "tvshow", + "tags": { + "title": "Test Title", + "namedseason": [{"tag": "{title}", "attributes": {"number": "{playlist_index}"}}], + "genre": [ + "Comedy", + "Drama", + ], + }, + }, + "overrides": {"artist": "JMC"}, + } + + class TestNfoTagsPlugins: @pytest.mark.parametrize("kodi_safe", [True, False]) def test_nfo_tags(self, subscription_dict, music_video_config, output_directory, kodi_safe): @@ -83,3 +109,21 @@ class TestNfoTagsPlugins: transaction_log=transaction_log, transaction_log_summary_file_name=f"plugins/nfo_tags/{transaction_log_file_name}", ) + + def test_merged_output_nfo_tags( + self, merged_output_nfo_preset_dict, music_video_config, output_directory + ): + subscription = Subscription.from_dict( + config=music_video_config, + preset_name="merged_output_nfo_tags", + preset_dict=merged_output_nfo_preset_dict, + ) + + # Only dry run is needed to see if NFO values are kodi safe + transaction_log = subscription.download(dry_run=True) + assert_transaction_log_matches( + output_directory=output_directory, + transaction_log=transaction_log, + transaction_log_summary_file_name=f"plugins/nfo_tags/merged_output_nfo_tags.txt", + regenerate_transaction_log=True, + ) diff --git a/tests/e2e/resources/transaction_log_summaries/plugins/nfo_tags/merged_output_nfo_tags.txt b/tests/e2e/resources/transaction_log_summaries/plugins/nfo_tags/merged_output_nfo_tags.txt new file mode 100644 index 00000000..813ef613 --- /dev/null +++ b/tests/e2e/resources/transaction_log_summaries/plugins/nfo_tags/merged_output_nfo_tags.txt @@ -0,0 +1,53 @@ +Files created in '{output_directory}' +---------------------------------------- +.ytdl-sub-merged_output_nfo_tags-download-archive.json +JMC - Jesse's Minecraft Server [Trailer - Feb.1]-thumb.jpg +JMC - Jesse's Minecraft Server [Trailer - Feb.1].info.json +JMC - Jesse's Minecraft Server [Trailer - Feb.1].mp4 +JMC - Jesse's Minecraft Server [Trailer - Feb.1].nfo + NFO tags: + musicvideo: + album: Music Videos + artist: JMC + title: Jesse's Minecraft Server [Trailer - Feb.1] + year: 2011 +JMC - Jesse's Minecraft Server [Trailer - Feb.27]-thumb.jpg +JMC - Jesse's Minecraft Server [Trailer - Feb.27].info.json +JMC - Jesse's Minecraft Server [Trailer - Feb.27].mp4 +JMC - Jesse's Minecraft Server [Trailer - Feb.27].nfo + NFO tags: + musicvideo: + album: Music Videos + artist: JMC + title: Jesse's Minecraft Server [Trailer - Feb.27] + year: 2011 +JMC - Jesse's Minecraft Server [Trailer - Mar.21]-thumb.jpg +JMC - Jesse's Minecraft Server [Trailer - Mar.21].info.json +JMC - Jesse's Minecraft Server [Trailer - Mar.21].mp4 +JMC - Jesse's Minecraft Server [Trailer - Mar.21].nfo + NFO tags: + musicvideo: + album: Music Videos + artist: JMC + title: Jesse's Minecraft Server [Trailer - Mar.21] + year: 2011 +tvshow.nfo + NFO tags: + tvshow: + genre: + - Comedy + - Drama + namedseason: + - + attributes: + number: 3 + tag: Jesse's Minecraft Server [Trailer - Feb.1] + - + attributes: + number: 2 + tag: Jesse's Minecraft Server [Trailer - Feb.27] + - + attributes: + number: 1 + tag: Jesse's Minecraft Server [Trailer - Mar.21] + title: Test Title \ No newline at end of file