Skip to content
Merged
35 changes: 19 additions & 16 deletions beetsplug/musicbrainz.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,22 +94,20 @@ def _preferred_alias(
for locale in languages:
# Find matching primary aliases for this locale that are not
# being ignored
matches = []
for alias in valid_aliases:
if (
alias["locale"] == locale
and alias.get("primary")
and (alias.get("type") or "").lower() not in ignored_alias_types
):
matches.append(alias)
return alias

# Skip to the next locale if we have no matches
if not matches:
continue
return None

return matches[0]

return None
def _key_with_preferred_alias(obj: JSONDict, key: str) -> str:
alias = _preferred_alias(obj.get("aliases", ()))
return alias["name"] if alias else obj[key]


def _multi_artist_credit(
Expand All @@ -126,10 +124,7 @@ def _multi_artist_credit(
alias = _preferred_alias(el["artist"].get("aliases", ()))

# An artist.
if alias:
cur_artist_name = alias["name"]
else:
cur_artist_name = el["artist"]["name"]
cur_artist_name = alias["name"] if alias else el["artist"]["name"]
artist_parts.append(cur_artist_name)

# Artist sort name.
Expand Down Expand Up @@ -346,8 +341,10 @@ def track_info(
``medium_index``, the track's index on its medium; ``medium_total``,
the number of tracks on the medium. Each number is a 1-based index.
"""
title = _key_with_preferred_alias(recording, key="title")

info = beets.autotag.hooks.TrackInfo(
title=recording["title"],
title=title,
track_id=recording["id"],
index=index,
medium=medium,
Expand Down Expand Up @@ -525,8 +522,11 @@ def album_info(self, release: JSONDict) -> beets.autotag.hooks.AlbumInfo:
ti.media = format
ti.track_alt = track["number"]

# Prefer track data, where present, over recording data.
if track.get("title"):
# Prefer track data, where present, over recording data except
# if a preferred recording alias is available.
if track.get("title") and not _preferred_alias(
track["recording"].get("aliases", ())
):
ti.title = track["title"]
if track.get("artist-credit"):
# Get the artist names.
Expand All @@ -552,8 +552,9 @@ def album_info(self, release: JSONDict) -> beets.autotag.hooks.AlbumInfo:
track_infos.append(ti)

album_artist_ids = _artist_ids(release["artist-credit"])
release_title = _key_with_preferred_alias(release, key="title")
info = beets.autotag.hooks.AlbumInfo(
album=release["title"],
album=release_title,
album_id=release["id"],
artist=artist_name,
artist_id=album_artist_ids[0],
Expand All @@ -577,7 +578,9 @@ def album_info(self, release: JSONDict) -> beets.autotag.hooks.AlbumInfo:
info.albumstatus = release.get("status")

if release["release-group"].get("title"):
info.release_group_title = release["release-group"].get("title")
info.release_group_title = _key_with_preferred_alias(
release["release-group"], key="title"
)

# Get the disambiguation strings at the release and release group level.
if release["release-group"].get("disambiguation"):
Expand Down
2 changes: 2 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ New features
``beet import``.
- :doc:`plugins/random`: Added ``--field`` option to specify which field to use
for equal-chance sampling (default: ``albumartist``).
- :doc:`plugins/musicbrainz`: Use title aliases for releases, release groups,
and recordings.

Bug fixes
~~~~~~~~~
Expand Down
3 changes: 3 additions & 0 deletions docs/reference/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,9 @@ MusicBrainz. You can use a space-separated list of language abbreviations, like
``en jp es``, to specify a preference order. Defaults to an empty list, meaning
that no language is preferred.

The alias is used for artist name, track title, release group title and album
title.

.. _ignored_alias_types:

ignored_alias_types
Expand Down
111 changes: 86 additions & 25 deletions test/plugins/test_musicbrainz.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import unittest
import uuid
from typing import ClassVar
from typing import Any, ClassVar
from unittest import mock

import pytest
Expand All @@ -28,6 +28,17 @@
from beetsplug import musicbrainz


def make_alias(suffix: str, locale: str, primary: bool = False):
alias: dict[str, Any] = {
"name": f"ALIAS{suffix}",
"locale": locale,
"sort-name": f"ALIASSORT{suffix}",
}
if primary:
alias["primary"] = True
return alias


class MusicBrainzTestCase(BeetsTestCase):
def setUp(self):
super().setUp()
Expand Down Expand Up @@ -56,6 +67,7 @@ def _make_release(
"first-release-date": date_str,
"id": "RELEASE GROUP ID",
"disambiguation": "RG_DISAMBIGUATION",
"title": "RELEASE GROUP TITLE",
},
"artist-credit": [
{
Expand Down Expand Up @@ -174,6 +186,7 @@ def _make_track(
disambiguation=None,
remixer=False,
multi_artist_credit=False,
aliases=None,
):
track = {
"title": title,
Expand Down Expand Up @@ -222,8 +235,26 @@ def _make_track(
track["video"] = True
if disambiguation:
track["disambiguation"] = disambiguation
if aliases is not None:
track["aliases"] = aliases
return track

def test_parse_release_title(self):
release = self._make_release(None)
release["aliases"] = [
make_alias(suffix="en", locale="en", primary=True),
]

# test no alias
config["import"]["languages"] = []
d = self.mb.album_info(release)
assert d.album == "ALBUM TITLE"

# test en primary
config["import"]["languages"] = ["en"]
d = self.mb.album_info(release)
assert d.album == "ALIASen"

def test_parse_release_with_year(self):
release = self._make_release("1984")
d = self.mb.album_info(release)
Expand All @@ -249,18 +280,45 @@ def test_parse_release_full_date(self):

def test_parse_tracks(self):
tracks = [
self._make_track("TITLE ONE", "ID ONE", 100.0 * 1000.0),
self._make_track("TITLE TWO", "ID TWO", 200.0 * 1000.0),
self._make_track(
"TITLE ONE",
"ID ONE",
100.0 * 1000.0,
aliases=[make_alias(suffix="ONEen", locale="en", primary=True)],
),
self._make_track(
"TITLE TWO",
"ID TWO",
200.0 * 1000.0,
aliases=[make_alias(suffix="TWOen", locale="en", primary=True)],
),
]
release = self._make_release(tracks=tracks)

# Preference over recording data
release["media"][0]["tracks"][1]["title"] = "TRACK TITLE TWO"

# test no alias
config["import"]["languages"] = []
d = self.mb.album_info(release)
t = d.tracks
assert len(t) == 2
assert t[0].title == "TITLE ONE"
assert t[0].track_id == "ID ONE"
assert t[0].length == 100.0
assert t[1].title == "TITLE TWO"
assert t[1].title == "TRACK TITLE TWO"
assert t[1].track_id == "ID TWO"
assert t[1].length == 200.0

# test en primary
config["import"]["languages"] = ["en"]
d = self.mb.album_info(release)
t = d.tracks
assert len(t) == 2
assert t[0].title == "ALIASONEen"
assert t[0].track_id == "ID ONE"
assert t[0].length == 100.0
assert t[1].title == "ALIASTWOen"
assert t[1].track_id == "ID TWO"
assert t[1].length == 200.0

Expand Down Expand Up @@ -370,6 +428,22 @@ def test_parse_releasegroupid(self):
d = self.mb.album_info(release)
assert d.releasegroup_id == "RELEASE GROUP ID"

def test_parse_release_group_title(self):
release = self._make_release(None)
release["release-group"]["aliases"] = [
make_alias(suffix="en", locale="en", primary=True),
]

# test no alias
config["import"]["languages"] = []
d = self.mb.album_info(release)
assert d.release_group_title == "RELEASE GROUP TITLE"

# test en primary
config["import"]["languages"] = ["en"]
d = self.mb.album_info(release)
assert d.release_group_title == "ALIASen"

def test_parse_asin(self):
release = self._make_release(None)
d = self.mb.album_info(release)
Expand Down Expand Up @@ -719,18 +793,6 @@ def _credit_dict(self, suffix=""):
"name": f"CREDIT{suffix}",
}

def _add_alias(self, credit_dict, suffix="", locale="", primary=False):
alias = {
"name": f"ALIAS{suffix}",
"locale": locale,
"sort-name": f"ALIASSORT{suffix}",
}
if primary:
alias["primary"] = "primary"
if "aliases" not in credit_dict["artist"]:
credit_dict["artist"]["aliases"] = []
credit_dict["artist"]["aliases"].append(alias)

def test_single_artist(self):
credit = [self._credit_dict()]
a, s, c = musicbrainz._flatten_artist_credit(credit)
Expand Down Expand Up @@ -764,16 +826,15 @@ def test_two_artists(self):

def test_alias(self):
credit_dict = self._credit_dict()
self._add_alias(credit_dict, suffix="en", locale="en", primary=True)
self._add_alias(
credit_dict, suffix="en_GB", locale="en_GB", primary=True
)
self._add_alias(credit_dict, suffix="fr", locale="fr")
self._add_alias(credit_dict, suffix="fr_P", locale="fr", primary=True)
self._add_alias(credit_dict, suffix="pt_BR", locale="pt_BR")

credit_dict["artist"]["aliases"] = [
make_alias(suffix="en", locale="en", primary=True),
make_alias(suffix="en_GB", locale="en_GB", primary=True),
make_alias(suffix="fr", locale="fr"),
make_alias(suffix="fr_P", locale="fr", primary=True),
make_alias(suffix="pt_BR", locale="pt_BR"),
]
# test no alias
config["import"]["languages"] = [""]
config["import"]["languages"] = []
flat = musicbrainz._flatten_artist_credit([credit_dict])
assert flat == ("NAME", "SORT", "CREDIT")

Expand Down
Loading