diff --git a/CHANGELOG.md b/CHANGELOG.md index d36efb8..372038a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,10 @@ ## Unreleased +### Changed + +- The metadata parser can now be specified with the configuration key `metadata_parser`. Accepted values are `ffprobe` (default), `mediainfo`, or `null` (for testing only). + ### Removed - Dropped Python 3.9. diff --git a/src/dakara_feeder/feeder/songs.py b/src/dakara_feeder/feeder/songs.py index 5d3a93e..975dfce 100644 --- a/src/dakara_feeder/feeder/songs.py +++ b/src/dakara_feeder/feeder/songs.py @@ -9,6 +9,7 @@ from dakara_feeder.customization import get_custom_song from dakara_feeder.difference import generate_diff, match_similar from dakara_feeder.directory import list_directory +from dakara_feeder.metadata import get_parser_class from dakara_feeder.similarity import calculate_file_path_similarity from dakara_feeder.song import BaseSong from dakara_feeder.utils import divide_chunks @@ -54,6 +55,7 @@ def __init__(self, config, force_update=False, prune=True, progress=True): self.bar = progress_bar if progress else null_bar self.song_class_module_name = config.get("custom_song_class") self.song_class = BaseSong + self.metadata_parser_class = get_parser_class(config.get("metadata_parser")) def load(self): """Execute side-effect initialization tasks.""" @@ -120,7 +122,9 @@ def feed(self): if added_songs_path: added_songs = [ self.song_class( - self.kara_folder_path, new_songs_paths_map[song_path] + self.kara_folder_path, + new_songs_paths_map[song_path], + self.metadata_parser_class, ).get_representation() for song_path in self.bar(added_songs_path, text="Parsing songs to add") ] @@ -132,7 +136,9 @@ def feed(self): updated_songs = [ ( self.song_class( - self.kara_folder_path, new_songs_paths_map[new_song_path] + self.kara_folder_path, + new_songs_paths_map[new_song_path], + self.metadata_parser_class, ).get_representation(), old_songs_id_by_path[old_song_path], ) diff --git a/src/dakara_feeder/metadata.py b/src/dakara_feeder/metadata.py index 7be77a1..6665414 100644 --- a/src/dakara_feeder/metadata.py +++ b/src/dakara_feeder/metadata.py @@ -270,6 +270,37 @@ def get_subtitle_tracks_count(self): ) +parsers = { + "null": NullMetadataParser, + "mediainfo": MediainfoMetadataParser, + "ffprobe": FFProbeMetadataParser, +} + + +def get_parser_class(name=None): + """Retrieve a metadata parser class on name. + + Args: + name (str): Name of the metadata parser. It will be converted to lower + case. If `None`, returns `FFProbeMetadataParser`. + + Returns: + type: Metadata parser class. + """ + if name is None: + return FFProbeMetadataParser + + try: + return parsers[name.lower()] + + except KeyError as key: + raise UnknownMetadataParser(f"Unknown metadata parser {key}") from key + + +class UnknownMetadataParser(DakaraError): + """Error if unknown metadata parser is requested.""" + + class MediaParseError(DakaraError): """Error if the metadata cannot be parsed.""" diff --git a/src/dakara_feeder/resources/feeder.yaml b/src/dakara_feeder/resources/feeder.yaml index d4d4ffc..bb49e8a 100644 --- a/src/dakara_feeder/resources/feeder.yaml +++ b/src/dakara_feeder/resources/feeder.yaml @@ -50,6 +50,14 @@ kara_folder: /path/to/folder # Default is BaseSong, which is pretty basic. # custom_song_class: module_name.Song +# Metadata parser to use +# Tool to extract metadata from files. Either: +# ffprobe: Requires FFProbe installed (from FFMpeg). Fastest method. +# mediainfo: Requires mediainfo installed. Slower, alternative method. +# null: Dummy parser. Parse nothing, for testing purposes. +# Default is ffprobe +# metadata_parser: ffprobe + # Other parameters # Minimal level of messages to log diff --git a/src/dakara_feeder/song.py b/src/dakara_feeder/song.py index 5ee89cc..32cebd9 100644 --- a/src/dakara_feeder/song.py +++ b/src/dakara_feeder/song.py @@ -3,11 +3,7 @@ import logging from pathlib import Path -from dakara_feeder.metadata import ( - FFProbeMetadataParser, - MediaParseError, - NullMetadataParser, -) +from dakara_feeder.metadata import MediaParseError, NullMetadataParser from dakara_feeder.subtitle.parsing import Pysubs2SubtitleParser, SubtitleParseError logger = logging.getLogger(__name__) @@ -71,6 +67,7 @@ class BaseSong: Args: base_directory (pathlib.Path): Path to the scanned directory. paths (directory_lister.SongPaths): Paths of the song file. + metadata_parser_class (type): Class of the metadata parser. Attributes: metadata_class (type): Class of the metadata parser to use. @@ -88,15 +85,14 @@ class BaseSong: containing metadata of the video file. """ - metadata_class = FFProbeMetadataParser - - def __init__(self, base_directory, paths): + def __init__(self, base_directory, paths, metadata_parser_class): self.base_directory = base_directory self.video_path = paths.video self.audio_path = paths.audio self.subtitle_path = paths.subtitle self.others_path = paths.others self.metadata = NullMetadataParser.parse(self.video_path) + self.metadata_class = metadata_parser_class def parse_metadata(self): """Use the requested metadata parser to parse video file.""" diff --git a/tests/unit/test_feeder_songs.py b/tests/unit/test_feeder_songs.py index 3ea779e..df7bf83 100644 --- a/tests/unit/test_feeder_songs.py +++ b/tests/unit/test_feeder_songs.py @@ -16,7 +16,11 @@ class SongsFeederTestCase(TestCase): def setUp(self): # create base config - self.config = {"server": {}, "kara_folder": "basepath"} + self.config = { + "server": {}, + "kara_folder": "basepath", + "metadata_parser": "ffprobe", + } @patch.object(SongsFeeder, "check_kara_folder_path", autospec=True) @patch("dakara_feeder.feeder.songs.get_custom_song", autospec=True) @@ -32,6 +36,7 @@ def test_load_no_song_class( # pre assert self.assertIs(feeder.song_class, BaseSong) + self.assertIs(feeder.metadata_parser_class, FFProbeMetadataParser) # call the method feeder.load() @@ -61,14 +66,14 @@ class MySong(BaseSong): mocked_get_custom_song.return_value = MySong # create the config - config = { - "server": {}, - "kara_folder": "basepath", - "custom_song_class": "module.MySong", - } + self.config.update( + { + "custom_song_class": "module.MySong", + } + ) # create the object - feeder = SongsFeeder(config, progress=False) + feeder = SongsFeeder(self.config, progress=False) # pre assert self.assertIs(feeder.song_class, BaseSong) @@ -393,6 +398,8 @@ def test_feed_with_no_prune( ], ) + maxDiff = None + @patch.object(Pysubs2SubtitleParser, "parse", autospec=True) @patch.object(FFProbeMetadataParser, "parse", autospec=True) @patch("dakara_feeder.feeder.songs.list_directory", autospec=True) @@ -463,6 +470,8 @@ def test_create_two_songs( # check called once self.assertEqual(len(post_calls), 1) + mocked_metadata_parse.assert_called() + # check one positional argument ( _, @@ -512,14 +521,14 @@ def get_artists(self): return ["artist1", "artist2"] # create the config - config = { - "server": {}, - "custom_song_class": "custom_song_module", - "kara_folder": "basepath", - } + self.config.update( + { + "custom_song_class": "custom_song_module", + } + ) # create the object - feeder = SongsFeeder(config, progress=False) + feeder = SongsFeeder(self.config, progress=False) feeder.song_class = Song # call the method @@ -573,11 +582,8 @@ def test_feed_extra_audio_file( mocked_metadata_parse.return_value.get_audio_tracks_count.return_value = 1 mocked_subtitle_parse.return_value.get_lyrics.return_value = "lyri lyri" - # create the config - config = {"server": {}, "kara_folder": "basepath"} - # create the object - feeder = SongsFeeder(config, progress=False) + feeder = SongsFeeder(self.config, progress=False) # call the method with self.assertLogs("dakara_feeder.feeder.songs", "DEBUG"): diff --git a/tests/unit/test_metadata.py b/tests/unit/test_metadata.py index a9e0aac..3a899d0 100644 --- a/tests/unit/test_metadata.py +++ b/tests/unit/test_metadata.py @@ -12,6 +12,8 @@ MediainfoNotInstalledError, MediaParseError, NullMetadataParser, + UnknownMetadataParser, + get_parser_class, ) @@ -175,3 +177,30 @@ def test_get_subtitle_tracks_count(self): } ) self.assertEqual(parser.get_subtitle_tracks_count(), 1) + + +class GetParserClassTestCase(TestCase): + """Test the metadata parser class getter.""" + + def test_get_default(self): + """Test to get the default metadata parser.""" + self.assertIs(get_parser_class(), FFProbeMetadataParser) + + def test_get_ffprobe(self): + """Test to get the FFProbe metadata parser.""" + self.assertIs(get_parser_class("ffprobe"), FFProbeMetadataParser) + + def test_get_mediainfo(self): + """Test to get the Mediainfo metadata parser.""" + self.assertIs(get_parser_class("mediainfo"), MediainfoMetadataParser) + + def test_get_null(self): + """Test to get the null metadata parser.""" + self.assertIs(get_parser_class("null"), NullMetadataParser) + + def test_get_unknown(self): + """Test to get an unknown metadata parser.""" + with self.assertRaisesRegex( + UnknownMetadataParser, "Unknown metadata parser 'unknown'" + ): + get_parser_class("unknown") diff --git a/tests/unit/test_song.py b/tests/unit/test_song.py index 7de5005..46fbe77 100644 --- a/tests/unit/test_song.py +++ b/tests/unit/test_song.py @@ -27,7 +27,7 @@ def test_subtitle_parser_error(self, mocked_metadata_parse, mocked_subtitle_pars paths = SongPaths(Path("file.mp4"), subtitle=Path("file.ass")) # create BaseSong instance - song = BaseSong(Path("/base-dir"), paths) + song = BaseSong(Path("/base-dir"), paths, FFProbeMetadataParser) # get song representation with self.assertLogs("dakara_feeder.song") as logger: @@ -54,7 +54,7 @@ def test_metadata_error(self, mocked_metadata_parse, mocked_subtitle_parse): paths = SongPaths(Path("file.mp4"), subtitle=Path("file.ass")) # create BaseSong instance - song = BaseSong(Path("/base-dir"), paths) + song = BaseSong(Path("/base-dir"), paths, FFProbeMetadataParser) # get song representation with self.assertLogs("dakara_feeder.song") as logger: