Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 8 additions & 2 deletions src/dakara_feeder/feeder/songs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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")
]
Expand All @@ -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],
)
Expand Down
31 changes: 31 additions & 0 deletions src/dakara_feeder/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down
8 changes: 8 additions & 0 deletions src/dakara_feeder/resources/feeder.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 4 additions & 8 deletions src/dakara_feeder/song.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down Expand Up @@ -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.
Expand All @@ -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."""
Expand Down
40 changes: 23 additions & 17 deletions tests/unit/test_feeder_songs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
(
_,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"):
Expand Down
29 changes: 29 additions & 0 deletions tests/unit/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
MediainfoNotInstalledError,
MediaParseError,
NullMetadataParser,
UnknownMetadataParser,
get_parser_class,
)


Expand Down Expand Up @@ -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")
4 changes: 2 additions & 2 deletions tests/unit/test_song.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
Loading