diff --git a/src/ytdl_sub/downloaders/youtube/playlist.py b/src/ytdl_sub/downloaders/youtube/playlist.py index 3b89545b..b6edf69c 100644 --- a/src/ytdl_sub/downloaders/youtube/playlist.py +++ b/src/ytdl_sub/downloaders/youtube/playlist.py @@ -92,6 +92,7 @@ class YoutubePlaylistDownloader( # Re-download the contents even if it's a dry-run as a single video. At this time, # playlists do not download subtitles or subtitle metadata as_single_video_dict = self.extract_info_with_retry( + is_downloaded_fn=None if self.is_dry_run else video.is_downloaded, ytdl_options_overrides={"writeinfojson": False, "skip_download": self.is_dry_run}, url=video.kwargs("webpage_url"), ) diff --git a/src/ytdl_sub/entries/entry.py b/src/ytdl_sub/entries/entry.py index 5970bc26..47236464 100644 --- a/src/ytdl_sub/entries/entry.py +++ b/src/ytdl_sub/entries/entry.py @@ -6,6 +6,7 @@ from typing import final from ytdl_sub.entries.base_entry import BaseEntry from ytdl_sub.entries.variables.entry_variables import EntryVariables +from ytdl_sub.validators.audo_codec_validator import AUDIO_CODEC_EXTS class Entry(EntryVariables, BaseEntry): @@ -65,13 +66,17 @@ class Entry(EntryVariables, BaseEntry): ------- True if the file and thumbnail exist locally. False otherwise. """ - thumbnail_exists = ( - os.path.isfile(self.get_download_thumbnail_path()) - or self.get_ytdlp_download_thumbnail_path() is not None - ) file_exists = os.path.isfile(self.get_download_file_path()) - return file_exists and thumbnail_exists + # HACK: yt-dlp does not record extracted audio extensions anywhere. If the file is not + # found, try it using audio extensions + if not file_exists: + for audio_ext in AUDIO_CODEC_EXTS: + if os.path.isfile(self.get_download_file_path().removesuffix(self.ext) + audio_ext): + file_exists = True + break + + return file_exists @final def to_dict(self) -> Dict[str, str]: diff --git a/src/ytdl_sub/plugins/audio_extract.py b/src/ytdl_sub/plugins/audio_extract.py index c7e6c4fd..9073f318 100644 --- a/src/ytdl_sub/plugins/audio_extract.py +++ b/src/ytdl_sub/plugins/audio_extract.py @@ -7,24 +7,10 @@ from ytdl_sub.entries.entry import Entry from ytdl_sub.plugins.plugin import Plugin from ytdl_sub.plugins.plugin import PluginOptions from ytdl_sub.utils.exceptions import FileNotDownloadedException -from ytdl_sub.validators.string_select_validator import StringSelectValidator +from ytdl_sub.validators.audo_codec_validator import AUDIO_CODEC_TYPES_EXTENSION_MAPPING +from ytdl_sub.validators.audo_codec_validator import CodecTypeValidator from ytdl_sub.validators.validators import FloatValidator -CODEC_TYPES_EXTENSION_MAPPING: Dict[str, str] = { - "aac": "aac", - "flac": "flac", - "mp3": "mp3", - "m4a": "m4a", - "opus": "opus", - "vorbis": "ogg", - "wav": "wav", -} - - -class CodecTypeValidator(StringSelectValidator): - _expected_value_type_name = "codec" - _select_values = set(CODEC_TYPES_EXTENSION_MAPPING.keys()) - class AudioExtractOptions(PluginOptions): """ @@ -102,8 +88,13 @@ class AudioExtractPlugin(Plugin[AudioExtractOptions]): Returns ------- Entry with updated 'ext' source variable + + Raises + ------ + FileNotDownloadedException + If the audio file is not found """ - new_ext = CODEC_TYPES_EXTENSION_MAPPING[self.plugin_options.codec] + new_ext = AUDIO_CODEC_TYPES_EXTENSION_MAPPING[self.plugin_options.codec] extracted_audio_file = entry.get_download_file_path().removesuffix(entry.ext) + new_ext if not self.is_dry_run: if not os.path.isfile(extracted_audio_file): diff --git a/src/ytdl_sub/validators/audo_codec_validator.py b/src/ytdl_sub/validators/audo_codec_validator.py new file mode 100644 index 00000000..b986c02f --- /dev/null +++ b/src/ytdl_sub/validators/audo_codec_validator.py @@ -0,0 +1,22 @@ +from typing import Dict +from typing import Set + +from ytdl_sub.validators.string_select_validator import StringSelectValidator + +AUDIO_CODEC_TYPES_EXTENSION_MAPPING: Dict[str, str] = { + "aac": "aac", + "flac": "flac", + "mp3": "mp3", + "m4a": "m4a", + "opus": "opus", + "vorbis": "ogg", + "wav": "wav", +} + +AUDIO_CODEC_TYPES: Set[str] = set(AUDIO_CODEC_TYPES_EXTENSION_MAPPING.keys()) +AUDIO_CODEC_EXTS: Set[str] = set(AUDIO_CODEC_TYPES_EXTENSION_MAPPING.values()) + + +class CodecTypeValidator(StringSelectValidator): + _expected_value_type_name = "codec" + _select_values = AUDIO_CODEC_TYPES diff --git a/tests/e2e/plugins/test_audio_extract.py b/tests/e2e/plugins/test_audio_extract.py index 28f8891a..198a3df4 100644 --- a/tests/e2e/plugins/test_audio_extract.py +++ b/tests/e2e/plugins/test_audio_extract.py @@ -18,16 +18,14 @@ def single_song_preset_dict(output_directory): }, } + @pytest.fixture def multiple_songs_preset_dict(output_directory): return { "preset": "yt_song_playlist", "youtube": {"playlist_url": "https://youtube.com/playlist?list=PL5BC0FC26BECA5A35"}, "output_options": {"output_directory": output_directory}, - "audio_extract": { - "codec": "vorbis", - "quality": 140 - }, + "audio_extract": {"codec": "vorbis", "quality": 140}, # download the worst format so it is fast "ytdl_options": { "format": "worst[ext=mp4]", @@ -90,4 +88,4 @@ class TestAudioExtract: dry_run=dry_run, expected_download_summary_file_name="plugins/test_audio_extract_playlist.json", regenerate_expected_download_summary=True, - ) \ No newline at end of file + ) diff --git a/tests/e2e/resources/expected_downloads_summaries/plugins/test_audio_extract_playlist.json b/tests/e2e/resources/expected_downloads_summaries/plugins/test_audio_extract_playlist.json index cef2bc8e..35c71317 100644 --- a/tests/e2e/resources/expected_downloads_summaries/plugins/test_audio_extract_playlist.json +++ b/tests/e2e/resources/expected_downloads_summaries/plugins/test_audio_extract_playlist.json @@ -1,5 +1,5 @@ { - "Jesse's Minecraft Server [Trailer - Feb.1].ogg": "3f701a4b2437704bc443cf07c9832dae", - "Jesse's Minecraft Server [Trailer - Feb.27].ogg": "430d1c8ec20573a73e2149c10c9c741f", - "Jesse's Minecraft Server [Trailer - Mar.21].ogg": "11742c96e9ea3a520b2411d8b3516472" + "Jesse's Minecraft Server [Trailer - Feb.1].ogg": "67b19b495756fb6dc332f49bfa2957d3", + "Jesse's Minecraft Server [Trailer - Feb.27].ogg": "273c96652e198171ee60345051addc7a", + "Jesse's Minecraft Server [Trailer - Mar.21].ogg": "e91db58c153f0724aaed5ce53de2a736" } \ No newline at end of file diff --git a/tests/e2e/resources/expected_downloads_summaries/plugins/test_audio_extract_single.json b/tests/e2e/resources/expected_downloads_summaries/plugins/test_audio_extract_single.json index 0efc1e08..1a2dbb73 100644 --- a/tests/e2e/resources/expected_downloads_summaries/plugins/test_audio_extract_single.json +++ b/tests/e2e/resources/expected_downloads_summaries/plugins/test_audio_extract_single.json @@ -1,3 +1,3 @@ { - "YouTube Rewind 2019: For the Record | #YouTubeRewind.mp3": "2b44b454ecc58f3600731a44e0c61f1b" + "YouTube Rewind 2019: For the Record | #YouTubeRewind.mp3": "c4ce74e2ddc4e6a0ab823b5b622e2275" } \ No newline at end of file