mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
Add custom_tags_only mode for mbpseudo plugin
This commit is contained in:
parent
cb758988ed
commit
040b2dd940
3 changed files with 163 additions and 10 deletions
|
|
@ -20,6 +20,7 @@ import traceback
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import TYPE_CHECKING, Any, Iterable, Sequence
|
from typing import TYPE_CHECKING, Any, Iterable, Sequence
|
||||||
|
|
||||||
|
import mediafile
|
||||||
import musicbrainzngs
|
import musicbrainzngs
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
|
|
@ -49,10 +50,49 @@ class MusicBrainzPseudoReleasePlugin(MusicBrainzPlugin):
|
||||||
|
|
||||||
self._release_getter = musicbrainzngs.get_release_by_id
|
self._release_getter = musicbrainzngs.get_release_by_id
|
||||||
|
|
||||||
self.config.add({"scripts": []})
|
self.config.add(
|
||||||
|
{
|
||||||
|
"scripts": [],
|
||||||
|
"custom_tags_only": False,
|
||||||
|
"album_custom_tags": {
|
||||||
|
"album_transl": "album",
|
||||||
|
"album_artist_transl": "artist",
|
||||||
|
},
|
||||||
|
"track_custom_tags": {
|
||||||
|
"title_transl": "title",
|
||||||
|
"artist_transl": "artist",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self._scripts = self.config["scripts"].as_str_seq()
|
self._scripts = self.config["scripts"].as_str_seq()
|
||||||
self._log.debug("Desired scripts: {0}", self._scripts)
|
self._log.debug("Desired scripts: {0}", self._scripts)
|
||||||
|
|
||||||
|
album_custom_tags = self.config["album_custom_tags"].get().keys()
|
||||||
|
track_custom_tags = self.config["track_custom_tags"].get().keys()
|
||||||
|
self._log.debug(
|
||||||
|
"Custom tags for albums and tracks: {0} + {1}",
|
||||||
|
album_custom_tags,
|
||||||
|
track_custom_tags,
|
||||||
|
)
|
||||||
|
for custom_tag in album_custom_tags | track_custom_tags:
|
||||||
|
if not isinstance(custom_tag, str):
|
||||||
|
continue
|
||||||
|
|
||||||
|
media_field = mediafile.MediaField(
|
||||||
|
mediafile.MP3DescStorageStyle(custom_tag),
|
||||||
|
mediafile.MP4StorageStyle(
|
||||||
|
f"----:com.apple.iTunes:{custom_tag}"
|
||||||
|
),
|
||||||
|
mediafile.StorageStyle(custom_tag),
|
||||||
|
mediafile.ASFStorageStyle(custom_tag),
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
self.add_media_field(custom_tag, media_field)
|
||||||
|
except ValueError:
|
||||||
|
# ignore errors due to duplicates
|
||||||
|
pass
|
||||||
|
|
||||||
self.register_listener("pluginload", self._on_plugins_loaded)
|
self.register_listener("pluginload", self._on_plugins_loaded)
|
||||||
self.register_listener("album_matched", self._adjust_final_album_match)
|
self.register_listener("album_matched", self._adjust_final_album_match)
|
||||||
|
|
||||||
|
|
@ -107,12 +147,17 @@ class MusicBrainzPseudoReleasePlugin(MusicBrainzPlugin):
|
||||||
pseudo_release = super().album_info(
|
pseudo_release = super().album_info(
|
||||||
raw_pseudo_release["release"]
|
raw_pseudo_release["release"]
|
||||||
)
|
)
|
||||||
return PseudoAlbumInfo(
|
|
||||||
pseudo_release=_merge_pseudo_and_actual_album(
|
if self.config["custom_tags_only"].get(bool):
|
||||||
pseudo_release, official_release
|
self._add_custom_tags(official_release, pseudo_release)
|
||||||
),
|
return official_release
|
||||||
official_release=official_release,
|
else:
|
||||||
)
|
return PseudoAlbumInfo(
|
||||||
|
pseudo_release=_merge_pseudo_and_actual_album(
|
||||||
|
pseudo_release, official_release
|
||||||
|
),
|
||||||
|
official_release=official_release,
|
||||||
|
)
|
||||||
except musicbrainzngs.MusicBrainzError as exc:
|
except musicbrainzngs.MusicBrainzError as exc:
|
||||||
raise MusicBrainzAPIError(
|
raise MusicBrainzAPIError(
|
||||||
exc,
|
exc,
|
||||||
|
|
@ -167,6 +212,23 @@ class MusicBrainzPseudoReleasePlugin(MusicBrainzPlugin):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _add_custom_tags(
|
||||||
|
self,
|
||||||
|
official_release: AlbumInfo,
|
||||||
|
pseudo_release: AlbumInfo,
|
||||||
|
):
|
||||||
|
for tag_key, pseudo_key in (
|
||||||
|
self.config["album_custom_tags"].get().items()
|
||||||
|
):
|
||||||
|
official_release[tag_key] = pseudo_release[pseudo_key]
|
||||||
|
|
||||||
|
track_custom_tags = self.config["track_custom_tags"].get().items()
|
||||||
|
for track, pseudo_track in zip(
|
||||||
|
official_release.tracks, pseudo_release.tracks
|
||||||
|
):
|
||||||
|
for tag_key, pseudo_key in track_custom_tags:
|
||||||
|
track[tag_key] = pseudo_track[pseudo_key]
|
||||||
|
|
||||||
def _adjust_final_album_match(self, match: AlbumMatch):
|
def _adjust_final_album_match(self, match: AlbumMatch):
|
||||||
album_info = match.info
|
album_info = match.info
|
||||||
if isinstance(album_info, PseudoAlbumInfo):
|
if isinstance(album_info, PseudoAlbumInfo):
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,18 @@ Since this plugin first searches for official releases from MusicBrainz, all
|
||||||
options from the `musicbrainz` plugin's :ref:`musicbrainz-config` are supported,
|
options from the `musicbrainz` plugin's :ref:`musicbrainz-config` are supported,
|
||||||
but they must be specified under `mbpseudo` in the configuration file.
|
but they must be specified under `mbpseudo` in the configuration file.
|
||||||
Additionally, the configuration expects an array of scripts that are desired for
|
Additionally, the configuration expects an array of scripts that are desired for
|
||||||
the pseudo-releases. Therefore, the minimum configuration for this plugin looks
|
the pseudo-releases. For ``artist`` in particular, keep in mind that even
|
||||||
like this:
|
pseudo-releases might specify it with the original script, so you should also
|
||||||
|
configure import :ref:`languages` to give artist aliases more priority.
|
||||||
|
Therefore, the minimum configuration for this plugin looks like this:
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
plugins: mbpseudo # remove musicbrainz
|
plugins: mbpseudo # remove musicbrainz
|
||||||
|
|
||||||
|
import:
|
||||||
|
languages: en
|
||||||
|
|
||||||
mbpseudo:
|
mbpseudo:
|
||||||
scripts:
|
scripts:
|
||||||
- Latn
|
- Latn
|
||||||
|
|
@ -37,7 +42,7 @@ like this:
|
||||||
Note that the `search_limit` configuration applies to the initial search for
|
Note that the `search_limit` configuration applies to the initial search for
|
||||||
official releases, and that the `data_source` in the database will be
|
official releases, and that the `data_source` in the database will be
|
||||||
"MusicBrainz". Nevertheless, `data_source_mismatch_penalty` must also be
|
"MusicBrainz". Nevertheless, `data_source_mismatch_penalty` must also be
|
||||||
specified under `mbpseudo` (see also
|
specified under `mbpseudo` if desired (see also
|
||||||
:ref:`metadata-source-plugin-configuration`). An example with multiple data
|
:ref:`metadata-source-plugin-configuration`). An example with multiple data
|
||||||
sources may look like this:
|
sources may look like this:
|
||||||
|
|
||||||
|
|
@ -45,6 +50,9 @@ sources may look like this:
|
||||||
|
|
||||||
plugins: mbpseudo deezer
|
plugins: mbpseudo deezer
|
||||||
|
|
||||||
|
import:
|
||||||
|
languages: en
|
||||||
|
|
||||||
mbpseudo:
|
mbpseudo:
|
||||||
data_source_mismatch_penalty: 0
|
data_source_mismatch_penalty: 0
|
||||||
scripts:
|
scripts:
|
||||||
|
|
@ -52,3 +60,44 @@ sources may look like this:
|
||||||
|
|
||||||
deezer:
|
deezer:
|
||||||
data_source_mismatch_penalty: 0.2
|
data_source_mismatch_penalty: 0.2
|
||||||
|
|
||||||
|
By default, the data from the pseudo-release will be used to create a proposal
|
||||||
|
that is independent from the official release and sets all properties in its
|
||||||
|
metadata. It's possible to change the configuration so that some information
|
||||||
|
from the pseudo-release is instead added as custom tags, keeping the metadata
|
||||||
|
from the official release:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
mbpseudo:
|
||||||
|
# other config not shown
|
||||||
|
custom_tags_only: yes
|
||||||
|
|
||||||
|
The default custom tags with this configuration are specified as mappings where
|
||||||
|
the keys define the tag names and the values define the pseudo-release property
|
||||||
|
that will be used to set the tag's value:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
mbpseudo:
|
||||||
|
album_custom_tags:
|
||||||
|
album_transl: album
|
||||||
|
album_artist_transl: artist
|
||||||
|
track_custom_tags:
|
||||||
|
title_transl: title
|
||||||
|
artist_transl: artist
|
||||||
|
|
||||||
|
Note that the information for each set of custom tags corresponds to different
|
||||||
|
metadata levels (album or track level), which is why ``artist`` appears twice
|
||||||
|
even though it effectively references album artist and track artist
|
||||||
|
respectively.
|
||||||
|
|
||||||
|
If you want to modify any mapping under ``album_custom_tags`` or
|
||||||
|
``track_custom_tags``, you must specify *everything* for that set of tags in
|
||||||
|
your configuration file because any customization replaces the whole dictionary
|
||||||
|
of mappings for that level.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
These custom tags are also added to the music files, not only to the
|
||||||
|
database.
|
||||||
|
|
|
||||||
|
|
@ -223,3 +223,45 @@ class TestMBPseudoPlugin(PluginMixin):
|
||||||
assert match.info.data_source == "MusicBrainz"
|
assert match.info.data_source == "MusicBrainz"
|
||||||
assert match.info.album_id == "pseudo"
|
assert match.info.album_id == "pseudo"
|
||||||
assert match.info.album == "In Bloom"
|
assert match.info.album == "In Bloom"
|
||||||
|
|
||||||
|
|
||||||
|
class TestMBPseudoPluginCustomTagsOnly(PluginMixin):
|
||||||
|
plugin = "mbpseudo"
|
||||||
|
|
||||||
|
@pytest.fixture(scope="class")
|
||||||
|
def mbpseudo_plugin(self) -> MusicBrainzPseudoReleasePlugin:
|
||||||
|
self.config["import"]["languages"] = ["en", "jp"]
|
||||||
|
self.config[self.plugin]["scripts"] = ["Latn"]
|
||||||
|
self.config[self.plugin]["custom_tags_only"] = True
|
||||||
|
return MusicBrainzPseudoReleasePlugin()
|
||||||
|
|
||||||
|
@pytest.fixture(scope="class")
|
||||||
|
def official_release(self, rsrc_dir: pathlib.Path) -> JSONDict:
|
||||||
|
info_json = (rsrc_dir / "official_release.json").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
return json.loads(info_json)
|
||||||
|
|
||||||
|
@pytest.fixture(scope="class")
|
||||||
|
def pseudo_release(self, rsrc_dir: pathlib.Path) -> JSONDict:
|
||||||
|
info_json = (rsrc_dir / "pseudo_release.json").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
return json.loads(info_json)
|
||||||
|
|
||||||
|
def test_custom_tags(
|
||||||
|
self,
|
||||||
|
mbpseudo_plugin: MusicBrainzPseudoReleasePlugin,
|
||||||
|
official_release: JSONDict,
|
||||||
|
pseudo_release: JSONDict,
|
||||||
|
):
|
||||||
|
mbpseudo_plugin._release_getter = (
|
||||||
|
lambda album_id, includes: pseudo_release
|
||||||
|
)
|
||||||
|
album_info = mbpseudo_plugin.album_info(official_release["release"])
|
||||||
|
assert not isinstance(album_info, PseudoAlbumInfo)
|
||||||
|
assert album_info.data_source == "MusicBrainzPseudoRelease"
|
||||||
|
assert album_info["album_transl"] == "In Bloom"
|
||||||
|
assert album_info["album_artist_transl"] == "Lilas Ikuta"
|
||||||
|
assert album_info.tracks[0]["title_transl"] == "In Bloom"
|
||||||
|
assert album_info.tracks[0]["artist_transl"] == "Lilas Ikuta"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue