From a74333be88950fcfeafaa2584c19ed42d0e696c4 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Mon, 29 Aug 2022 16:33:01 -0700 Subject: [PATCH] [FEATURE] Add output option for info json file --- examples/kodi_music_videos_config.yaml | 1 + examples/kodi_tv_shows_config.yaml | 1 + examples/soundcloud_discography_config.yaml | 7 +++-- examples/youtube_extract_and_tag_audio.yaml | 1 + src/ytdl_sub/config/preset_options.py | 13 +++++++++ src/ytdl_sub/entries/entry.py | 29 +++++++++++++++++++ .../entries/variables/entry_variables.py | 10 +++++++ src/ytdl_sub/subscriptions/subscription.py | 15 ++++++++++ tests/unit/entries/conftest.py | 1 + 9 files changed, 76 insertions(+), 2 deletions(-) diff --git a/examples/kodi_music_videos_config.yaml b/examples/kodi_music_videos_config.yaml index 0ce8d1d8..0beaf1b8 100644 --- a/examples/kodi_music_videos_config.yaml +++ b/examples/kodi_music_videos_config.yaml @@ -38,6 +38,7 @@ presets: output_directory: "{music_video_directory}" file_name: "{music_video_name}.{ext}" thumbnail_name: "{music_video_name}-thumb.jpg" + info_json_name: "{music_video_name}.{info_json_ext}" # For each video downloaded, add a music video NFO file for it. Populate it # with tags that Kodi will read and use to display it in the music or music diff --git a/examples/kodi_tv_shows_config.yaml b/examples/kodi_tv_shows_config.yaml index 2d4536cf..04e8bc9c 100644 --- a/examples/kodi_tv_shows_config.yaml +++ b/examples/kodi_tv_shows_config.yaml @@ -53,6 +53,7 @@ presets: output_directory: "{youtube_tv_shows_directory}/{tv_show_name_sanitized}" file_name: "{episode_name}.{ext}" thumbnail_name: "{episode_name}-thumb.jpg" + info_json_name: "{episode_name}.{info_json_ext}" maintain_download_archive: True # For each video downloaded, add an episode NFO file for it. We give it diff --git a/examples/soundcloud_discography_config.yaml b/examples/soundcloud_discography_config.yaml index 5b10b3c8..bfc03416 100644 --- a/examples/soundcloud_discography_config.yaml +++ b/examples/soundcloud_discography_config.yaml @@ -44,8 +44,9 @@ presets: # them on a successive invocation. output_options: output_directory: "{music_directory}" - file_name: "{artist_sanitized}/{album_directory_name}/{track_number_padded} - {title_sanitized}.{ext}" - thumbnail_name: "{artist_sanitized}/{album_directory_name}/folder.jpg" + file_name: "{album_path}/{track_file_name}.{ext}" + thumbnail_name: "{album_path}/folder.{thumbnail_ext}" + info_json_name: "{album_path}/{track_file_name}.{info_json_ext}" maintain_download_archive: True # For each song downloaded, populate the audio file with music tags. @@ -67,4 +68,6 @@ presets: # which gets reused above for the audio file name and album art path. overrides: album_directory_name: "[{album_year}] {album_sanitized}" + track_file_name: "{track_number_padded} - {title_sanitized}" + album_path: "{artist_sanitized}/{album_directory_name}" music_directory: "/path/to/music" \ No newline at end of file diff --git a/examples/youtube_extract_and_tag_audio.yaml b/examples/youtube_extract_and_tag_audio.yaml index 223fcfb6..b18aaf53 100644 --- a/examples/youtube_extract_and_tag_audio.yaml +++ b/examples/youtube_extract_and_tag_audio.yaml @@ -10,6 +10,7 @@ presets: output_options: output_directory: "{music_directory}" file_name: "{custom_track_name_sanitized}.{ext}" + info_json_name: "{custom_track_name_sanitized}.{info_json_ext}" audio_extract: codec: "mp3" diff --git a/src/ytdl_sub/config/preset_options.py b/src/ytdl_sub/config/preset_options.py index 7ae03041..bba08636 100644 --- a/src/ytdl_sub/config/preset_options.py +++ b/src/ytdl_sub/config/preset_options.py @@ -126,6 +126,7 @@ class OutputOptions(StrictDictValidator): _required_keys = {"output_directory", "file_name"} _optional_keys = { "thumbnail_name", + "info_json_name", "subtitles_name", "maintain_download_archive", "keep_files_before", @@ -148,6 +149,9 @@ class OutputOptions(StrictDictValidator): self._thumbnail_name = self._validate_key_if_present( key="thumbnail_name", validator=StringFormatterValidator ) + self._info_json_name = self._validate_key_if_present( + key="info_json_name", validator=StringFormatterValidator + ) self._maintain_download_archive = self._validate_key_if_present( key="maintain_download_archive", validator=BoolValidator, default=False @@ -191,6 +195,15 @@ class OutputOptions(StrictDictValidator): """ return self._thumbnail_name + @property + def info_json_name(self) -> Optional[StringFormatterValidator]: + """ + Optional. The file name for the media's info json file. This can include directories such + as ``"Season {upload_year}/{title}.{info_json_ext}"``, and will be placed in the output + directory. + """ + return self._info_json_name + @property def maintain_download_archive(self) -> bool: """ diff --git a/src/ytdl_sub/entries/entry.py b/src/ytdl_sub/entries/entry.py index 47236464..b38a544e 100644 --- a/src/ytdl_sub/entries/entry.py +++ b/src/ytdl_sub/entries/entry.py @@ -1,3 +1,5 @@ +import copy +import json import os from pathlib import Path from typing import Dict @@ -59,6 +61,33 @@ class Entry(EntryVariables, BaseEntry): return None + def get_download_info_json_name(self) -> str: + """ + Returns + ------- + The download info json's file name + """ + return f"{self.uid}.{self.info_json_ext}" + + def get_download_info_json_path(self) -> str: + """ + Returns + ------- + Entry's downloaded info json file path + """ + return str(Path(self.working_directory()) / self.get_download_info_json_name()) + + def write_info_json(self) -> None: + """ + Write the entry's _kwargs back into the info.json file as well as its source variables + """ + kwargs_dict = copy.deepcopy(self._kwargs) + kwargs_dict["ytdl-sub-entry-variables"] = self.to_dict() + kwargs_json = json.dumps(self._kwargs, ensure_ascii=False, sort_keys=True, indent=2) + + with open(self.get_download_info_json_path(), "w", encoding="utf-8") as file: + file.write(kwargs_json) + @final def is_downloaded(self) -> bool: """ diff --git a/src/ytdl_sub/entries/variables/entry_variables.py b/src/ytdl_sub/entries/variables/entry_variables.py index faa3b646..c4f42fe5 100644 --- a/src/ytdl_sub/entries/variables/entry_variables.py +++ b/src/ytdl_sub/entries/variables/entry_variables.py @@ -304,3 +304,13 @@ class EntryVariables(SourceVariables): The uploaded date formatted as YYYY-MM-DD """ return f"{self.upload_year}-{self.upload_month_padded}-{self.upload_day_padded}" + + @property + def info_json_ext(self) -> str: + """ + Returns + ------- + str + The "info.json" extension + """ + return "info.json" diff --git a/src/ytdl_sub/subscriptions/subscription.py b/src/ytdl_sub/subscriptions/subscription.py index 251d4caf..b98109b6 100644 --- a/src/ytdl_sub/subscriptions/subscription.py +++ b/src/ytdl_sub/subscriptions/subscription.py @@ -205,6 +205,21 @@ class Subscription: entry=entry, ) + if self.output_options.info_json_name: + output_info_json_name = self.overrides.apply_formatter( + formatter=self.output_options.info_json_name, entry=entry + ) + + # if not dry-run, write the info json + if not dry_run: + entry.write_info_json() + + self._enhanced_download_archive.save_file_to_output_directory( + file_name=entry.get_download_thumbnail_name(), + output_file_name=output_info_json_name, + entry=entry, + ) + @contextlib.contextmanager def _prepare_working_directory(self): """ diff --git a/tests/unit/entries/conftest.py b/tests/unit/entries/conftest.py index 1c0fe0bb..012ba56c 100644 --- a/tests/unit/entries/conftest.py +++ b/tests/unit/entries/conftest.py @@ -77,6 +77,7 @@ def mock_entry_to_dict( "upload_day_of_year_reversed": 354, "upload_day_of_year_reversed_padded": "354", "thumbnail_ext": thumbnail_ext, + "info_json_ext": "info.json", }