diff --git a/src/ytdl_sub/plugins/music_tags.py b/src/ytdl_sub/plugins/music_tags.py index f66b20a6..90b7889a 100644 --- a/src/ytdl_sub/plugins/music_tags.py +++ b/src/ytdl_sub/plugins/music_tags.py @@ -11,6 +11,7 @@ from ytdl_sub.plugins.plugin import PluginOptions from ytdl_sub.utils.file_handler import FileMetadata from ytdl_sub.utils.logger import Logger from ytdl_sub.utils.thumbnail import convert_download_thumbnail +from ytdl_sub.validators.audo_codec_validator import AUDIO_CODEC_EXTS from ytdl_sub.validators.strict_dict_validator import StrictDictValidator from ytdl_sub.validators.string_formatter_validators import ListFormatterValidator from ytdl_sub.validators.string_formatter_validators import StringFormatterValidator @@ -116,6 +117,13 @@ class MusicTagsPlugin(Plugin[MusicTagsOptions]): """ Tags the entry's audio file using values defined in the metadata options """ + if entry.ext not in AUDIO_CODEC_EXTS: + raise self.plugin_options.validation_exception( + f"music_tags plugin received a video with the extension '{entry.ext}'. Only audio " + f"files are supported for setting music tags. Ensure you are converting the video " + f"to audio using the audio_extract plugin." + ) + # Resolve the tags into this dict tags_to_write: Dict[str, List[str]] = defaultdict(list) for tag_name, tag_formatters in self.plugin_options.tags.as_lists.items(): diff --git a/src/ytdl_sub/plugins/plugin.py b/src/ytdl_sub/plugins/plugin.py index 2b046dfd..0f32cafb 100644 --- a/src/ytdl_sub/plugins/plugin.py +++ b/src/ytdl_sub/plugins/plugin.py @@ -10,6 +10,7 @@ from typing import TypeVar from ytdl_sub.config.preset_options import AddsVariablesMixin from ytdl_sub.config.preset_options import Overrides from ytdl_sub.entries.entry import Entry +from ytdl_sub.utils.exceptions import ValidationException from ytdl_sub.utils.file_handler import FileMetadata from ytdl_sub.utils.logger import Logger from ytdl_sub.validators.strict_dict_validator import StrictDictValidator @@ -44,6 +45,23 @@ class PluginOptions(StrictDictValidator, AddsVariablesMixin, ABC): Class that defines the parameters to a plugin """ + def validation_exception( + self, + error_message: str | Exception, + ) -> ValidationException: + """ + Parameters + ---------- + error_message + Error message to include in the validation exception + + Returns + ------- + Validation exception that points to the location in the config. To be used for plugins + to throw good validation exceptions at runtime. + """ + return self._validation_exception(error_message=error_message) + PluginOptionsT = TypeVar("PluginOptionsT", bound=PluginOptions) diff --git a/tests/e2e/plugins/test_music_tags.py b/tests/e2e/plugins/test_music_tags.py new file mode 100644 index 00000000..2cec1b9b --- /dev/null +++ b/tests/e2e/plugins/test_music_tags.py @@ -0,0 +1,51 @@ +import re + +import pytest +from expected_download import assert_expected_downloads +from expected_transaction_log import assert_transaction_log_matches + +from ytdl_sub.subscriptions.subscription import Subscription +from ytdl_sub.utils.exceptions import ValidationException + + +@pytest.fixture +def single_song_video_dict(output_directory): + return { + "download": { + "download_strategy": "url", + "url": "https://www.youtube.com/watch?v=2lAe1cqCOXo", + }, + "output_options": {"output_directory": output_directory, "file_name": "will_error.mp4"}, + # test multi-tags + "music_tags": {"embed_thumbnail": True, "tags": {"genres": ["multi_tag_1", "multi_tag_2"]}}, + # download the worst format so it is fast + "ytdl_options": { + "format": "worst[ext=mp4]", + "postprocessor_args": {"ffmpeg": ["-bitexact"]}, # Must add this for reproducibility + }, + } + + +class TestMusicTags: + def test_music_tags_errors_on_video( + self, + youtube_audio_config, + single_song_video_dict, + output_directory, + ): + subscription = Subscription.from_dict( + config=youtube_audio_config, + preset_name="single_song_test", + preset_dict=single_song_video_dict, + ) + + with pytest.raises( + ValidationException, + match=re.escape( + "Validation error in single_song_test.music_tags: music_tags plugin received a " + "video with the extension 'mp4'. Only audio files are supported for setting music " + "tags. Ensure you are converting the video to audio using the audio_extract " + "plugin." + ), + ): + subscription.download(dry_run=True)