diff --git a/config.yaml b/config.yaml index b59930b8..934606a5 100644 --- a/config.yaml +++ b/config.yaml @@ -26,17 +26,37 @@ presets: year: "{album_year}" genre: "{genre}" - music_videos: + yt_music_video: + # A single YouTube video is our source/download strategy. However, this + # can be overwritten to download music videos from a "playlist", as we + # will see in a preset below youtube: + download_strategy: "video" + + # For advanced YTDL users only. + # It is wise to leave ignoreerrors=True to avoid errors for things + # like age-restricted videos in case you have not set your cookie. ytdl_options: ignoreerrors: True + + # For each video downloaded, set the file and thumbnail name here. + # We set both with {music_video_name}, which is a variable we define in + # the overrides section further below to represent consistent naming format. output_options: - file_name: "{sanitized_artist} - {sanitized_title}.{ext}" - thumbnail_name: "{sanitized_artist} - {sanitized_title}.jpg" + output_directory: "{music_video_directory}/{sanitized_artist}" + file_name: "{music_video_name}.{ext}" + thumbnail_name: "{music_video_name}.jpg" + + # Always convert the video thumbnail to jpg + # TODO: always convert all thumbnails to jpg in code convert_thumbnail: to: "jpg" + + # 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 + # videos section. nfo_tags: - nfo_name: "{sanitized_artist} - {sanitized_title}.nfo" + nfo_name: "{music_video_name}.nfo" nfo_root: "musicvideo" tags: artist: "{artist}" @@ -44,6 +64,33 @@ presets: album: "Music Videos" year: "{upload_year}" + # Overrides is a section where we can define our own variables, and use them in + # any other section. We define our music video directory and episode file name + # here, which gets reused above for the video, thumbnail, and NFO file. + overrides: + music_video_directory: "path/to/Music Videos" + music_video_name: "{sanitized_artist} - {sanitized_title}" + + # It is not always ideal to download all of an artist's music videos. + # Maybe you only like one song of theirs. We can reuse our preset above + # to download a single video instead. + yt_music_video_playlist: + preset: "yt_music_video" + youtube: + download_strategy: "playlist" + + # Setting maintain_download_archive=True is generally a good thing to enable + # with playlists and channels because it will store previously downloaded + # video ids to tell YTDL not to re-download them on a successive invocation. + output_options: + maintain_download_archive: True + + # Setting break_on_existing=True is also a good thing anytime you are using + # a download archive, because it will tell the downloader to stop trying + # to download videos that have already been downloaded. + ytdl_options: + break_on_existing: True + yt_channel: youtube: download_strategy: "channel" diff --git a/src/ytdl_sub/main.py b/src/ytdl_sub/main.py index 9b346432..0bcb023d 100644 --- a/src/ytdl_sub/main.py +++ b/src/ytdl_sub/main.py @@ -58,9 +58,9 @@ def _download_subscription_from_cli(config: ConfigFile, extra_args: List[str]) - subscription.download() -def main(): +def _main(): """ - Entrypoint for ytdl-subscribe + Entrypoint for ytdl-sub, without the error handling """ # If no args are provided, print help and exit if len(sys.argv) < 2: @@ -80,18 +80,26 @@ def main(): logger.info("Download complete!") -if __name__ == "__main__": +def main(): + """ + Entrypoint for ytdl-sub + """ try: - main() + _main() except ValidationException as validation_exception: logger.error(validation_exception) sys.exit(1) - except Exception as exc: # pylint: disable=broad-except - logger.exception( - "A fatal error occurred. Please copy and paste the stacktrace above and make a Github " - "issue at https://github.com/jmbannon/ytdl-subscribe/issues with your config and " - "command/subscription yaml file to reproduce. Thanks for trying ytdl-subscribe!" + except Exception: # pylint: disable=broad-except + logger.exception("An uncaught error occurred:") + logger.error( + "Please copy and paste the stacktrace above and make a Github " + "issue at https://github.com/jmbannon/ytdl-sub/issues with your config and " + "command/subscription yaml file to reproduce. Thanks for trying ytdl-sub!" ) sys.exit(1) sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/src/ytdl_sub/plugins/plugin.py b/src/ytdl_sub/plugins/plugin.py index 4d2ad76a..74362a9d 100644 --- a/src/ytdl_sub/plugins/plugin.py +++ b/src/ytdl_sub/plugins/plugin.py @@ -1,5 +1,6 @@ from abc import ABC from typing import Generic +from typing import Optional from typing import Type from typing import TypeVar from typing import final @@ -33,7 +34,7 @@ class Plugin(Generic[PluginOptionsT], ABC): plugin_options: PluginOptionsT, output_directory: str, overrides: Overrides, - enhanced_download_archive: EnhancedDownloadArchive, + enhanced_download_archive: Optional[EnhancedDownloadArchive], ): self.plugin_options = plugin_options self.output_directory = output_directory @@ -46,6 +47,7 @@ class Plugin(Generic[PluginOptionsT], ABC): def archive_entry_file_name(self, entry: Entry, relative_file_path: str) -> None: """ Adds an entry and a file name that belongs to it into the archive mapping. + If maintain_download_archive is False for the subscription, this method will do nothing. Parameters ---------- @@ -54,9 +56,10 @@ class Plugin(Generic[PluginOptionsT], ABC): relative_file_path: The name of the file path relative to the output directory """ - self.__enhanced_download_archive.mapping.add_entry( - entry=entry, entry_file_path=relative_file_path - ) + if self.__enhanced_download_archive: + self.__enhanced_download_archive.mapping.add_entry( + entry=entry, entry_file_path=relative_file_path + ) def post_process_entry(self, entry: Entry): """ diff --git a/src/ytdl_sub/subscriptions/subscription.py b/src/ytdl_sub/subscriptions/subscription.py index d95696f1..1c9f9a80 100644 --- a/src/ytdl_sub/subscriptions/subscription.py +++ b/src/ytdl_sub/subscriptions/subscription.py @@ -131,12 +131,17 @@ class Subscription: """ return self.overrides.apply_formatter(formatter=self.output_options.output_directory) - def _copy_file_to_output_directory(self, source_file_path: str, output_file_name: str): + def _copy_file_to_output_directory( + self, entry: Entry, source_file_path: str, output_file_name: str + ): """ Helper function to move a file from the working directory to the output directory. + Will add it to the download archive mapping if the archive is being maintained. Parameters ---------- + entry: + Entry that the file belongs to source_file_path: Path to the source file output_file_name: @@ -146,6 +151,9 @@ class Subscription: os.makedirs(os.path.dirname(destination_file_path), exist_ok=True) copyfile(source_file_path, destination_file_path) + if self.output_options.maintain_download_archive: + self._enhanced_download_archive.mapping.add_entry(entry, output_file_name) + def _copy_entry_files_to_output_directory(self, entry: Entry): """ Helper function to move the media file and optionally thumbnail file to the output directory @@ -160,23 +168,22 @@ class Subscription: output_file_name = self.overrides.apply_formatter( formatter=self.output_options.file_name, entry=entry ) - self._enhanced_download_archive.mapping.add_entry(entry, output_file_name) self._copy_file_to_output_directory( - source_file_path=entry.get_download_file_path(), output_file_name=output_file_name + entry=entry, + source_file_path=entry.get_download_file_path(), + output_file_name=output_file_name, ) if self.output_options.thumbnail_name: output_thumbnail_name = self.overrides.apply_formatter( formatter=self.output_options.thumbnail_name, entry=entry ) - self._enhanced_download_archive.mapping.add_entry(entry, output_thumbnail_name) self._copy_file_to_output_directory( + entry=entry, source_file_path=entry.get_download_thumbnail_path(), output_file_name=output_thumbnail_name, ) - self._enhanced_download_archive.mapping.add_entry(entry, output_file_name) - @contextlib.contextmanager def _prepare_working_directory(self): """ @@ -221,7 +228,9 @@ class Subscription: plugin_options=plugin_options, output_directory=self.output_directory, overrides=self.overrides, - enhanced_download_archive=self._enhanced_download_archive, + enhanced_download_archive=self._enhanced_download_archive + if self.output_options.maintain_download_archive + else None, ) plugins.append(plugin) @@ -238,7 +247,9 @@ class Subscription: working_directory=self.working_directory, download_options=self.downloader_options, ytdl_options=self.ytdl_options.dict, - download_archive_file_name=self._enhanced_download_archive.archive_file_name, + download_archive_file_name=self._enhanced_download_archive.archive_file_name + if self.output_options.maintain_download_archive + else None, ) entries = downloader.download()