mirror of
https://github.com/beetbox/beets.git
synced 2026-02-22 15:22:42 +01:00
musicbrainz: lookup release directly
This commit is contained in:
parent
7fdb458524
commit
2a63e13617
8 changed files with 2856 additions and 1539 deletions
|
|
@ -6,7 +6,7 @@ import requests
|
|||
from beets import __version__
|
||||
|
||||
|
||||
class NotFoundError(requests.exceptions.HTTPError):
|
||||
class HTTPNotFoundError(requests.exceptions.HTTPError):
|
||||
pass
|
||||
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ class TimeoutSession(requests.Session):
|
|||
kwargs.setdefault("timeout", 10)
|
||||
r = super().request(*args, **kwargs)
|
||||
if r.status_code == HTTPStatus.NOT_FOUND:
|
||||
raise NotFoundError("HTTP Error: Not Found", response=r)
|
||||
raise HTTPNotFoundError("HTTP Error: Not Found", response=r)
|
||||
if 300 <= r.status_code < 400:
|
||||
raise CaptchaError("Captcha is required", response=r)
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ from beets import plugins, ui
|
|||
from beets.autotag.distance import string_dist
|
||||
from beets.util.config import sanitize_choices
|
||||
|
||||
from ._utils.requests import TimeoutSession
|
||||
from ._utils.requests import CaptchaError, HTTPNotFoundError, TimeoutSession
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable, Iterator
|
||||
|
|
@ -327,7 +327,7 @@ class LRCLib(Backend):
|
|||
|
||||
yield self.fetch_json(self.SEARCH_URL, params=base_params)
|
||||
|
||||
with suppress(NotFoundError):
|
||||
with suppress(HTTPNotFoundError):
|
||||
yield [self.fetch_json(self.GET_URL, params=get_params)]
|
||||
|
||||
@classmethod
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ from copy import deepcopy
|
|||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import mediafile
|
||||
import musicbrainzngs
|
||||
from typing_extensions import override
|
||||
|
||||
from beets import config
|
||||
|
|
@ -32,7 +31,6 @@ from beets.autotag.match import assign_items
|
|||
from beets.plugins import find_plugins
|
||||
from beets.util.id_extractors import extract_release_id
|
||||
from beetsplug.musicbrainz import (
|
||||
RELEASE_INCLUDES,
|
||||
MusicBrainzAPIError,
|
||||
MusicBrainzPlugin,
|
||||
_merge_pseudo_and_actual_album,
|
||||
|
|
@ -53,8 +51,6 @@ class MusicBrainzPseudoReleasePlugin(MusicBrainzPlugin):
|
|||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._release_getter = musicbrainzngs.get_release_by_id
|
||||
|
||||
self.config.add(
|
||||
{
|
||||
"scripts": [],
|
||||
|
|
@ -143,12 +139,12 @@ class MusicBrainzPseudoReleasePlugin(MusicBrainzPlugin):
|
|||
|
||||
if release.get("status") == _STATUS_PSEUDO:
|
||||
return official_release
|
||||
elif pseudo_release_ids := self._intercept_mb_release(release):
|
||||
album_id = self._extract_id(pseudo_release_ids[0])
|
||||
|
||||
if (ids := self._intercept_mb_release(release)) and (
|
||||
album_id := self._extract_id(ids[0])
|
||||
):
|
||||
try:
|
||||
raw_pseudo_release = self._release_getter(
|
||||
album_id, RELEASE_INCLUDES
|
||||
)["release"]
|
||||
raw_pseudo_release = self.api.get_release(album_id)
|
||||
pseudo_release = super().album_info(raw_pseudo_release)
|
||||
|
||||
if self.config["custom_tags_only"].get(bool):
|
||||
|
|
@ -181,7 +177,7 @@ class MusicBrainzPseudoReleasePlugin(MusicBrainzPlugin):
|
|||
|
||||
return [
|
||||
pr_id
|
||||
for rel in data.get("release-relation-list", [])
|
||||
for rel in data.get("release-relations", [])
|
||||
if (pr_id := self._wanted_pseudo_release_id(album_id, rel))
|
||||
is not None
|
||||
]
|
||||
|
|
@ -234,7 +230,7 @@ class MusicBrainzPseudoReleasePlugin(MusicBrainzPlugin):
|
|||
"artist-credit", []
|
||||
)
|
||||
aliases = [
|
||||
artist_credit.get("artist", {}).get("alias-list", [])
|
||||
artist_credit.get("artist", {}).get("aliases", [])
|
||||
for artist_credit in artist_credits
|
||||
]
|
||||
|
||||
|
|
@ -247,7 +243,7 @@ class MusicBrainzPseudoReleasePlugin(MusicBrainzPlugin):
|
|||
aliases_flattened,
|
||||
)
|
||||
if alias_dict := _preferred_alias(aliases_flattened, [locale]):
|
||||
if alias := alias_dict.get("alias"):
|
||||
if alias := alias_dict.get("name"):
|
||||
self._log.debug("Got alias '{0}'", alias)
|
||||
pseudo_release.artist = alias
|
||||
for track in pseudo_release.tracks:
|
||||
|
|
|
|||
|
|
@ -16,11 +16,12 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import operator
|
||||
import traceback
|
||||
from collections import Counter
|
||||
from contextlib import suppress
|
||||
from functools import cached_property
|
||||
from itertools import product
|
||||
from functools import cached_property, singledispatchmethod
|
||||
from itertools import groupby, product
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from urllib.parse import urljoin
|
||||
|
||||
|
|
@ -35,7 +36,7 @@ from beets.metadata_plugins import MetadataSourcePlugin
|
|||
from beets.util.deprecation import deprecate_for_user
|
||||
from beets.util.id_extractors import extract_release_id
|
||||
|
||||
from ._utils.requests import TimeoutSession
|
||||
from ._utils.requests import HTTPNotFoundError, TimeoutSession
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterable, Sequence
|
||||
|
|
@ -83,27 +84,24 @@ class MusicBrainzAPIError(util.HumanReadableError):
|
|||
return f"{self._reasonstr()} in {self.verb} with query {self.query!r}"
|
||||
|
||||
|
||||
RELEASE_INCLUDES = list(
|
||||
{
|
||||
"artists",
|
||||
"media",
|
||||
"recordings",
|
||||
"release-groups",
|
||||
"labels",
|
||||
"artist-credits",
|
||||
"aliases",
|
||||
"recording-level-rels",
|
||||
"work-rels",
|
||||
"work-level-rels",
|
||||
"artist-rels",
|
||||
"isrcs",
|
||||
"url-rels",
|
||||
"release-rels",
|
||||
"genres",
|
||||
"tags",
|
||||
}
|
||||
& set(musicbrainzngs.VALID_INCLUDES["release"])
|
||||
)
|
||||
RELEASE_INCLUDES = [
|
||||
"artists",
|
||||
"media",
|
||||
"recordings",
|
||||
"release-groups",
|
||||
"labels",
|
||||
"artist-credits",
|
||||
"aliases",
|
||||
"recording-level-rels",
|
||||
"work-rels",
|
||||
"work-level-rels",
|
||||
"artist-rels",
|
||||
"isrcs",
|
||||
"url-rels",
|
||||
"release-rels",
|
||||
"genres",
|
||||
"tags",
|
||||
]
|
||||
|
||||
TRACK_INCLUDES = list(
|
||||
{
|
||||
|
|
@ -130,7 +128,7 @@ BROWSE_MAXTRACKS = 500
|
|||
|
||||
|
||||
class MusicBrainzAPI:
|
||||
api_url = "https://musicbrainz.org/ws/2/"
|
||||
api_url = "https://musicbrainz.org/ws/2"
|
||||
|
||||
@cached_property
|
||||
def session(self) -> LimiterTimeoutSession:
|
||||
|
|
@ -141,6 +139,54 @@ class MusicBrainzAPI:
|
|||
f"{self.api_url}/{entity}", params={**kwargs, "fmt": "json"}
|
||||
).json()
|
||||
|
||||
def get_release(self, id_: str) -> JSONDict:
|
||||
return self._group_relations(
|
||||
self._get(f"release/{id_}", inc=" ".join(RELEASE_INCLUDES))
|
||||
)
|
||||
|
||||
@singledispatchmethod
|
||||
@classmethod
|
||||
def _group_relations(cls, data: Any) -> Any:
|
||||
"""Normalize MusicBrainz 'relations' into type-keyed fields recursively.
|
||||
|
||||
This helper rewrites payloads that use a generic 'relations' list into
|
||||
a structure that is easier to consume downstream. When a mapping
|
||||
contains 'relations', those entries are regrouped by their 'target-type'
|
||||
and stored under keys like '<target-type>-relations'. The original
|
||||
'relations' key is removed to avoid ambiguous access patterns.
|
||||
|
||||
The transformation is applied recursively so that nested objects and
|
||||
sequences are normalized consistently, while non-container values are
|
||||
left unchanged.
|
||||
"""
|
||||
return data
|
||||
|
||||
@_group_relations.register(list)
|
||||
@classmethod
|
||||
def _(cls, data: list[Any]) -> list[Any]:
|
||||
return [cls._group_relations(i) for i in data]
|
||||
|
||||
@_group_relations.register(dict)
|
||||
@classmethod
|
||||
def _(cls, data: JSONDict) -> JSONDict:
|
||||
for k, v in list(data.items()):
|
||||
if k == "relations":
|
||||
get_target_type = operator.methodcaller("get", "target-type")
|
||||
for target_type, group in groupby(
|
||||
sorted(v, key=get_target_type), get_target_type
|
||||
):
|
||||
relations = [
|
||||
{k: v for k, v in item.items() if k != "target-type"}
|
||||
for item in group
|
||||
]
|
||||
data[f"{target_type}-relations"] = cls._group_relations(
|
||||
relations
|
||||
)
|
||||
data.pop("relations")
|
||||
else:
|
||||
data[k] = cls._group_relations(v)
|
||||
return data
|
||||
|
||||
|
||||
def _preferred_alias(
|
||||
aliases: list[JSONDict], languages: list[str] | None = None
|
||||
|
|
@ -169,7 +215,7 @@ def _preferred_alias(
|
|||
for alias in valid_aliases:
|
||||
if (
|
||||
alias["locale"] == locale
|
||||
and "primary" in alias
|
||||
and alias.get("primary")
|
||||
and alias.get("type", "").lower() not in ignored_alias_types
|
||||
):
|
||||
matches.append(alias)
|
||||
|
|
@ -194,36 +240,33 @@ def _multi_artist_credit(
|
|||
artist_sort_parts = []
|
||||
artist_credit_parts = []
|
||||
for el in credit:
|
||||
if isinstance(el, str):
|
||||
# Join phrase.
|
||||
if include_join_phrase:
|
||||
artist_parts.append(el)
|
||||
artist_credit_parts.append(el)
|
||||
artist_sort_parts.append(el)
|
||||
alias = _preferred_alias(el["artist"].get("aliases", ()))
|
||||
|
||||
# An artist.
|
||||
if alias:
|
||||
cur_artist_name = alias["name"]
|
||||
else:
|
||||
alias = _preferred_alias(el["artist"].get("alias-list", ()))
|
||||
cur_artist_name = el["artist"]["name"]
|
||||
artist_parts.append(cur_artist_name)
|
||||
|
||||
# An artist.
|
||||
if alias:
|
||||
cur_artist_name = alias["alias"]
|
||||
else:
|
||||
cur_artist_name = el["artist"]["name"]
|
||||
artist_parts.append(cur_artist_name)
|
||||
# Artist sort name.
|
||||
if alias:
|
||||
artist_sort_parts.append(alias["sort-name"])
|
||||
elif "sort-name" in el["artist"]:
|
||||
artist_sort_parts.append(el["artist"]["sort-name"])
|
||||
else:
|
||||
artist_sort_parts.append(cur_artist_name)
|
||||
|
||||
# Artist sort name.
|
||||
if alias:
|
||||
artist_sort_parts.append(alias["sort-name"])
|
||||
elif "sort-name" in el["artist"]:
|
||||
artist_sort_parts.append(el["artist"]["sort-name"])
|
||||
else:
|
||||
artist_sort_parts.append(cur_artist_name)
|
||||
# Artist credit.
|
||||
if "name" in el:
|
||||
artist_credit_parts.append(el["name"])
|
||||
else:
|
||||
artist_credit_parts.append(cur_artist_name)
|
||||
|
||||
# Artist credit.
|
||||
if "name" in el:
|
||||
artist_credit_parts.append(el["name"])
|
||||
else:
|
||||
artist_credit_parts.append(cur_artist_name)
|
||||
if include_join_phrase and (joinphrase := el.get("joinphrase")):
|
||||
artist_parts.append(joinphrase)
|
||||
artist_sort_parts.append(joinphrase)
|
||||
artist_credit_parts.append(joinphrase)
|
||||
|
||||
return (
|
||||
artist_parts,
|
||||
|
|
@ -293,9 +336,9 @@ def _preferred_release_event(
|
|||
].as_str_seq()
|
||||
|
||||
for country in preferred_countries:
|
||||
for event in release.get("release-event-list", {}):
|
||||
for event in release.get("release-events", {}):
|
||||
try:
|
||||
if country in event["area"]["iso-3166-1-code-list"]:
|
||||
if country in event["area"]["iso-3166-1-codes"]:
|
||||
return country, event["date"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
|
@ -370,7 +413,11 @@ def _merge_pseudo_and_actual_album(
|
|||
class MusicBrainzPlugin(MetadataSourcePlugin):
|
||||
@cached_property
|
||||
def genres_field(self) -> str:
|
||||
return f"{self.config['genres_tag'].as_choice(['genre', 'tag'])}-list"
|
||||
return f"{self.config['genres_tag'].as_choice(['genre', 'tag'])}s"
|
||||
|
||||
@cached_property
|
||||
def api(self) -> MusicBrainzAPI:
|
||||
return MusicBrainzAPI()
|
||||
|
||||
def __init__(self):
|
||||
"""Set up the python-musicbrainz-ngs module according to settings
|
||||
|
|
@ -461,9 +508,9 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
|||
info.artists_ids = _artist_ids(recording["artist-credit"])
|
||||
info.artist_id = info.artists_ids[0]
|
||||
|
||||
if recording.get("artist-relation-list"):
|
||||
if recording.get("artist-relations"):
|
||||
info.remixer = _get_related_artist_names(
|
||||
recording["artist-relation-list"], relation_type="remixer"
|
||||
recording["artist-relations"], relation_type="remixer"
|
||||
)
|
||||
|
||||
if recording.get("length"):
|
||||
|
|
@ -477,7 +524,7 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
|||
lyricist = []
|
||||
composer = []
|
||||
composer_sort = []
|
||||
for work_relation in recording.get("work-relation-list", ()):
|
||||
for work_relation in recording.get("work-relations", ()):
|
||||
if work_relation["type"] != "performance":
|
||||
continue
|
||||
info.work = work_relation["work"]["title"]
|
||||
|
|
@ -486,7 +533,7 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
|||
info.work_disambig = work_relation["work"]["disambiguation"]
|
||||
|
||||
for artist_relation in work_relation["work"].get(
|
||||
"artist-relation-list", ()
|
||||
"artist-relations", ()
|
||||
):
|
||||
if "type" in artist_relation:
|
||||
type = artist_relation["type"]
|
||||
|
|
@ -504,7 +551,7 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
|||
info.composer_sort = ", ".join(composer_sort)
|
||||
|
||||
arranger = []
|
||||
for artist_relation in recording.get("artist-relation-list", ()):
|
||||
for artist_relation in recording.get("artist-relations", ()):
|
||||
if "type" in artist_relation:
|
||||
type = artist_relation["type"]
|
||||
if type == "arranger":
|
||||
|
|
@ -536,9 +583,9 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
|||
release["artist-credit"], include_join_phrase=False
|
||||
)
|
||||
|
||||
ntracks = sum(len(m["track-list"]) for m in release["medium-list"])
|
||||
ntracks = sum(len(m["tracks"]) for m in release["media"])
|
||||
|
||||
# The MusicBrainz API omits 'artist-relation-list' and 'work-relation-list'
|
||||
# The MusicBrainz API omits 'relations'
|
||||
# when the release has more than 500 tracks. So we use browse_recordings
|
||||
# on chunks of tracks to recover the same information in this case.
|
||||
if ntracks > BROWSE_MAXTRACKS:
|
||||
|
|
@ -555,27 +602,27 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
|||
)["recording-list"]
|
||||
)
|
||||
track_map = {r["id"]: r for r in recording_list}
|
||||
for medium in release["medium-list"]:
|
||||
for recording in medium["track-list"]:
|
||||
for medium in release["media"]:
|
||||
for recording in medium["tracks"]:
|
||||
recording_info = track_map[recording["recording"]["id"]]
|
||||
recording["recording"] = recording_info
|
||||
|
||||
# Basic info.
|
||||
track_infos = []
|
||||
index = 0
|
||||
for medium in release["medium-list"]:
|
||||
for medium in release["media"]:
|
||||
disctitle = medium.get("title")
|
||||
format = medium.get("format")
|
||||
|
||||
if format in config["match"]["ignored_media"].as_str_seq():
|
||||
continue
|
||||
|
||||
all_tracks = medium["track-list"]
|
||||
all_tracks = medium["tracks"]
|
||||
if (
|
||||
"data-track-list" in medium
|
||||
"data-tracks" in medium
|
||||
and not config["match"]["ignore_data_tracks"]
|
||||
):
|
||||
all_tracks += medium["data-track-list"]
|
||||
all_tracks += medium["data-tracks"]
|
||||
track_count = len(all_tracks)
|
||||
|
||||
if "pregap" in medium:
|
||||
|
|
@ -590,7 +637,7 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
|||
|
||||
if (
|
||||
"video" in track["recording"]
|
||||
and track["recording"]["video"] == "true"
|
||||
and track["recording"]["video"]
|
||||
and config["match"]["ignore_video_tracks"]
|
||||
):
|
||||
continue
|
||||
|
|
@ -644,7 +691,7 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
|||
artists=artists_names,
|
||||
artists_ids=album_artist_ids,
|
||||
tracks=track_infos,
|
||||
mediums=len(release["medium-list"]),
|
||||
mediums=len(release["media"]),
|
||||
artist_sort=artist_sort_name,
|
||||
artists_sort=artists_sort_names,
|
||||
artist_credit=artist_credit_name,
|
||||
|
|
@ -684,9 +731,9 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
|||
rel_primarytype = release["release-group"]["primary-type"]
|
||||
if rel_primarytype:
|
||||
albumtypes.append(rel_primarytype.lower())
|
||||
if "secondary-type-list" in release["release-group"]:
|
||||
if release["release-group"]["secondary-type-list"]:
|
||||
for sec_type in release["release-group"]["secondary-type-list"]:
|
||||
if "secondary-types" in release["release-group"]:
|
||||
if release["release-group"]["secondary-types"]:
|
||||
for sec_type in release["release-group"]["secondary-types"]:
|
||||
albumtypes.append(sec_type.lower())
|
||||
info.albumtypes = albumtypes
|
||||
|
||||
|
|
@ -702,8 +749,8 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
|||
_set_date_str(info, release_group_date, True)
|
||||
|
||||
# Label name.
|
||||
if release.get("label-info-list"):
|
||||
label_info = release["label-info-list"][0]
|
||||
if release.get("label-info"):
|
||||
label_info = release["label-info"][0]
|
||||
if label_info.get("label"):
|
||||
label = label_info["label"]["name"]
|
||||
if label != "[no label]":
|
||||
|
|
@ -717,10 +764,10 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
|||
info.language = rep.get("language")
|
||||
|
||||
# Media (format).
|
||||
if release["medium-list"]:
|
||||
if release["media"]:
|
||||
# If all media are the same, use that medium name
|
||||
if len({m.get("format") for m in release["medium-list"]}) == 1:
|
||||
info.media = release["medium-list"][0].get("format")
|
||||
if len({m.get("format") for m in release["media"]}) == 1:
|
||||
info.media = release["media"][0].get("format")
|
||||
# Otherwise, let's just call it "Media"
|
||||
else:
|
||||
info.media = "Media"
|
||||
|
|
@ -744,11 +791,11 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
|||
wanted_sources = {
|
||||
site for site, wanted in external_ids.items() if wanted
|
||||
}
|
||||
if wanted_sources and (url_rels := release.get("url-relation-list")):
|
||||
if wanted_sources and (url_rels := release.get("url-relations")):
|
||||
urls = {}
|
||||
|
||||
for source, url in product(wanted_sources, url_rels):
|
||||
if f"{source}.com" in (target := url["target"]):
|
||||
if f"{source}.com" in (target := url["url"]["resource"]):
|
||||
urls[source] = target
|
||||
self._log.debug(
|
||||
"Found link to {} release via MusicBrainz",
|
||||
|
|
@ -838,7 +885,10 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
|||
criteria = self.get_album_criteria(items, artist, album, va_likely)
|
||||
release_ids = (r["id"] for r in self._search_api("release", criteria))
|
||||
|
||||
yield from filter(None, map(self.album_for_id, release_ids))
|
||||
for id_ in release_ids:
|
||||
with suppress(HTTPNotFoundError):
|
||||
if album_info := self.album_for_id(id_):
|
||||
yield album_info
|
||||
|
||||
def item_candidates(
|
||||
self, item: Item, artist: str, title: str
|
||||
|
|
@ -862,22 +912,20 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
|||
return None
|
||||
|
||||
try:
|
||||
res = musicbrainzngs.get_release_by_id(albumid, RELEASE_INCLUDES)
|
||||
res = self.api.get_release(albumid)
|
||||
|
||||
# resolve linked release relations
|
||||
actual_res = None
|
||||
|
||||
if res.get("status") == "Pseudo-Release" and (
|
||||
relations := res["release"].get("release-relation-list")
|
||||
relations := res.get("release-relations")
|
||||
):
|
||||
for rel in relations:
|
||||
if (
|
||||
rel["type"] == "transl-tracklisting"
|
||||
and rel["direction"] == "backward"
|
||||
):
|
||||
actual_res = musicbrainzngs.get_release_by_id(
|
||||
rel["target"], RELEASE_INCLUDES
|
||||
)
|
||||
actual_res = self.api.get_release(rel["target"])
|
||||
|
||||
except musicbrainzngs.ResponseError:
|
||||
self._log.debug("Album ID match failed.")
|
||||
|
|
@ -888,11 +936,11 @@ class MusicBrainzPlugin(MetadataSourcePlugin):
|
|||
)
|
||||
|
||||
# release is potentially a pseudo release
|
||||
release = self.album_info(res["release"])
|
||||
release = self.album_info(res)
|
||||
|
||||
# should be None unless we're dealing with a pseudo release
|
||||
if actual_res is not None:
|
||||
actual_release = self.album_info(actual_res["release"])
|
||||
actual_release = self.album_info(actual_res)
|
||||
return _merge_pseudo_and_actual_album(release, actual_release)
|
||||
else:
|
||||
return release
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import json
|
||||
import pathlib
|
||||
from copy import deepcopy
|
||||
|
||||
import pytest
|
||||
|
||||
from beets import config
|
||||
from beets.autotag import AlbumMatch
|
||||
from beets.autotag.distance import Distance
|
||||
from beets.autotag.hooks import AlbumInfo, TrackInfo
|
||||
from beets.library import Item
|
||||
from beets.test.helper import PluginMixin
|
||||
from beets.test.helper import ConfigMixin, PluginMixin
|
||||
from beetsplug._typing import JSONDict
|
||||
from beetsplug.mbpseudo import (
|
||||
_STATUS_PSEUDO,
|
||||
|
|
@ -18,6 +18,23 @@ from beetsplug.mbpseudo import (
|
|||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def rsrc_dir(pytestconfig: pytest.Config):
|
||||
return pytestconfig.rootpath / "test" / "rsrc" / "mbpseudo"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def official_release(rsrc_dir: pathlib.Path) -> JSONDict:
|
||||
info_json = (rsrc_dir / "official_release.json").read_text(encoding="utf-8")
|
||||
return json.loads(info_json)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pseudo_release(rsrc_dir: pathlib.Path) -> JSONDict:
|
||||
info_json = (rsrc_dir / "pseudo_release.json").read_text(encoding="utf-8")
|
||||
return json.loads(info_json)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def official_release_info() -> AlbumInfo:
|
||||
return AlbumInfo(
|
||||
tracks=[TrackInfo(title="百花繚乱")],
|
||||
|
|
@ -26,7 +43,7 @@ def official_release_info() -> AlbumInfo:
|
|||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
@pytest.fixture
|
||||
def pseudo_release_info() -> AlbumInfo:
|
||||
return AlbumInfo(
|
||||
tracks=[TrackInfo(title="In Bloom")],
|
||||
|
|
@ -35,6 +52,14 @@ def pseudo_release_info() -> AlbumInfo:
|
|||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def config():
|
||||
config = ConfigMixin().config
|
||||
with pytest.MonkeyPatch.context() as m:
|
||||
m.setattr("beetsplug.mbpseudo.config", config)
|
||||
yield config
|
||||
|
||||
|
||||
class TestPseudoAlbumInfo:
|
||||
def test_album_id_always_from_pseudo(
|
||||
self, official_release_info: AlbumInfo, pseudo_release_info: AlbumInfo
|
||||
|
|
@ -62,8 +87,7 @@ class TestPseudoAlbumInfo:
|
|||
info = PseudoAlbumInfo(
|
||||
pseudo_release_info, official_release_info, data_source="test"
|
||||
)
|
||||
item = Item()
|
||||
item["title"] = "百花繚乱"
|
||||
item = Item(title="百花繚乱")
|
||||
|
||||
assert info.determine_best_ref([item]) == "official"
|
||||
|
||||
|
|
@ -71,37 +95,29 @@ class TestPseudoAlbumInfo:
|
|||
assert info.data_source == "test"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def rsrc_dir(pytestconfig: pytest.Config):
|
||||
return pytestconfig.rootpath / "test" / "rsrc" / "mbpseudo"
|
||||
|
||||
|
||||
class TestMBPseudoPlugin(PluginMixin):
|
||||
class TestMBPseudoMixin(PluginMixin):
|
||||
plugin = "mbpseudo"
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patch_get_release(self, monkeypatch, pseudo_release: JSONDict):
|
||||
monkeypatch.setattr(
|
||||
"beetsplug.musicbrainz.MusicBrainzAPI.get_release",
|
||||
lambda _, album_id: deepcopy(
|
||||
{pseudo_release["id"]: pseudo_release}[album_id]
|
||||
),
|
||||
)
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def plugin_config(self):
|
||||
return {"scripts": ["Latn", "Dummy"]}
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
@pytest.fixture
|
||||
def mbpseudo_plugin(self, plugin_config) -> MusicBrainzPseudoReleasePlugin:
|
||||
self.config[self.plugin].set(plugin_config)
|
||||
return MusicBrainzPseudoReleasePlugin()
|
||||
|
||||
@pytest.fixture
|
||||
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
|
||||
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)
|
||||
|
||||
class TestMBPseudoPlugin(TestMBPseudoMixin):
|
||||
def test_scripts_init(
|
||||
self, mbpseudo_plugin: MusicBrainzPseudoReleasePlugin
|
||||
):
|
||||
|
|
@ -129,7 +145,7 @@ class TestMBPseudoPlugin(PluginMixin):
|
|||
mbpseudo_plugin: MusicBrainzPseudoReleasePlugin,
|
||||
pseudo_release: JSONDict,
|
||||
):
|
||||
album_info = mbpseudo_plugin.album_info(pseudo_release["release"])
|
||||
album_info = mbpseudo_plugin.album_info(pseudo_release)
|
||||
assert not isinstance(album_info, PseudoAlbumInfo)
|
||||
assert album_info.data_source == "MusicBrainzPseudoRelease"
|
||||
assert album_info.albumstatus == _STATUS_PSEUDO
|
||||
|
|
@ -148,9 +164,9 @@ class TestMBPseudoPlugin(PluginMixin):
|
|||
official_release: JSONDict,
|
||||
json_key: str,
|
||||
):
|
||||
del official_release["release"]["release-relation-list"][0][json_key]
|
||||
del official_release["release-relations"][0][json_key]
|
||||
|
||||
album_info = mbpseudo_plugin.album_info(official_release["release"])
|
||||
album_info = mbpseudo_plugin.album_info(official_release)
|
||||
assert not isinstance(album_info, PseudoAlbumInfo)
|
||||
assert album_info.data_source == "MusicBrainzPseudoRelease"
|
||||
|
||||
|
|
@ -159,11 +175,11 @@ class TestMBPseudoPlugin(PluginMixin):
|
|||
mbpseudo_plugin: MusicBrainzPseudoReleasePlugin,
|
||||
official_release: JSONDict,
|
||||
):
|
||||
official_release["release"]["release-relation-list"][0]["release"][
|
||||
official_release["release-relations"][0]["release"][
|
||||
"text-representation"
|
||||
]["script"] = "Null"
|
||||
|
||||
album_info = mbpseudo_plugin.album_info(official_release["release"])
|
||||
album_info = mbpseudo_plugin.album_info(official_release)
|
||||
assert not isinstance(album_info, PseudoAlbumInfo)
|
||||
assert album_info.data_source == "MusicBrainzPseudoRelease"
|
||||
|
||||
|
|
@ -171,12 +187,8 @@ class TestMBPseudoPlugin(PluginMixin):
|
|||
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"])
|
||||
album_info = mbpseudo_plugin.album_info(official_release)
|
||||
assert isinstance(album_info, PseudoAlbumInfo)
|
||||
assert album_info.data_source == "MusicBrainzPseudoRelease"
|
||||
|
||||
|
|
@ -226,40 +238,19 @@ class TestMBPseudoPlugin(PluginMixin):
|
|||
assert match.info.album == "In Bloom"
|
||||
|
||||
|
||||
class TestMBPseudoPluginCustomTagsOnly(PluginMixin):
|
||||
plugin = "mbpseudo"
|
||||
|
||||
class TestMBPseudoPluginCustomTagsOnly(TestMBPseudoMixin):
|
||||
@pytest.fixture(scope="class")
|
||||
def mbpseudo_plugin(self) -> MusicBrainzPseudoReleasePlugin:
|
||||
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 plugin_config(self):
|
||||
return {"scripts": ["Latn", "Dummy"], "custom_tags_only": True}
|
||||
|
||||
def test_custom_tags(
|
||||
self,
|
||||
config,
|
||||
mbpseudo_plugin: MusicBrainzPseudoReleasePlugin,
|
||||
official_release: JSONDict,
|
||||
pseudo_release: JSONDict,
|
||||
):
|
||||
config["import"]["languages"] = []
|
||||
mbpseudo_plugin._release_getter = (
|
||||
lambda album_id, includes: pseudo_release
|
||||
)
|
||||
album_info = mbpseudo_plugin.album_info(official_release["release"])
|
||||
config["import"]["languages"] = ["en", "jp"]
|
||||
album_info = mbpseudo_plugin.album_info(official_release)
|
||||
assert not isinstance(album_info, PseudoAlbumInfo)
|
||||
assert album_info.data_source == "MusicBrainzPseudoRelease"
|
||||
assert album_info["album_transl"] == "In Bloom"
|
||||
|
|
@ -269,15 +260,12 @@ class TestMBPseudoPluginCustomTagsOnly(PluginMixin):
|
|||
|
||||
def test_custom_tags_with_import_languages(
|
||||
self,
|
||||
config,
|
||||
mbpseudo_plugin: MusicBrainzPseudoReleasePlugin,
|
||||
official_release: JSONDict,
|
||||
pseudo_release: JSONDict,
|
||||
):
|
||||
config["import"]["languages"] = ["en", "jp"]
|
||||
mbpseudo_plugin._release_getter = (
|
||||
lambda album_id, includes: pseudo_release
|
||||
)
|
||||
album_info = mbpseudo_plugin.album_info(official_release["release"])
|
||||
config["import"]["languages"] = []
|
||||
album_info = mbpseudo_plugin.album_info(official_release)
|
||||
assert not isinstance(album_info, PseudoAlbumInfo)
|
||||
assert album_info.data_source == "MusicBrainzPseudoRelease"
|
||||
assert album_info["album_transl"] == "In Bloom"
|
||||
|
|
|
|||
|
|
@ -64,10 +64,10 @@ class MBAlbumInfoTest(MusicBrainzTestCase):
|
|||
}
|
||||
],
|
||||
"date": "3001",
|
||||
"medium-list": [],
|
||||
"genre-list": [{"count": 1, "name": "GENRE"}],
|
||||
"tag-list": [{"count": 1, "name": "TAG"}],
|
||||
"label-info-list": [
|
||||
"media": [],
|
||||
"genres": [{"count": 1, "name": "GENRE"}],
|
||||
"tags": [{"count": 1, "name": "TAG"}],
|
||||
"label-info": [
|
||||
{
|
||||
"catalog-number": "CATALOG NUMBER",
|
||||
"label": {"name": "LABEL NAME"},
|
||||
|
|
@ -83,7 +83,7 @@ class MBAlbumInfoTest(MusicBrainzTestCase):
|
|||
}
|
||||
|
||||
if multi_artist_credit:
|
||||
release["artist-credit"].append(" & ") # add join phase
|
||||
release["artist-credit"][0]["joinphrase"] = " & "
|
||||
release["artist-credit"].append(
|
||||
{
|
||||
"artist": {
|
||||
|
|
@ -124,7 +124,7 @@ class MBAlbumInfoTest(MusicBrainzTestCase):
|
|||
]
|
||||
|
||||
if multi_artist_credit:
|
||||
track["artist-credit"].append(" & ") # add join phase
|
||||
track["artist-credit"][0]["joinphrase"] = " & "
|
||||
track["artist-credit"].append(
|
||||
{
|
||||
"artist": {
|
||||
|
|
@ -148,11 +148,11 @@ class MBAlbumInfoTest(MusicBrainzTestCase):
|
|||
"number": "A1",
|
||||
}
|
||||
data_track_list.append(data_track)
|
||||
release["medium-list"].append(
|
||||
release["media"].append(
|
||||
{
|
||||
"position": "1",
|
||||
"track-list": track_list,
|
||||
"data-track-list": data_track_list,
|
||||
"tracks": track_list,
|
||||
"data-tracks": data_track_list,
|
||||
"format": medium_format,
|
||||
"title": "MEDIUM TITLE",
|
||||
}
|
||||
|
|
@ -188,7 +188,7 @@ class MBAlbumInfoTest(MusicBrainzTestCase):
|
|||
}
|
||||
]
|
||||
if multi_artist_credit:
|
||||
track["artist-credit"].append(" & ") # add join phase
|
||||
track["artist-credit"][0]["joinphrase"] = " & "
|
||||
track["artist-credit"].append(
|
||||
{
|
||||
"artist": {
|
||||
|
|
@ -200,7 +200,7 @@ class MBAlbumInfoTest(MusicBrainzTestCase):
|
|||
}
|
||||
)
|
||||
if remixer:
|
||||
track["artist-relation-list"] = [
|
||||
track["artist-relations"] = [
|
||||
{
|
||||
"type": "remixer",
|
||||
"type-id": "RELATION TYPE ID",
|
||||
|
|
@ -215,7 +215,7 @@ class MBAlbumInfoTest(MusicBrainzTestCase):
|
|||
}
|
||||
]
|
||||
if video:
|
||||
track["video"] = "true"
|
||||
track["video"] = True
|
||||
if disambiguation:
|
||||
track["disambiguation"] = disambiguation
|
||||
return track
|
||||
|
|
@ -301,10 +301,10 @@ class MBAlbumInfoTest(MusicBrainzTestCase):
|
|||
"number": "A1",
|
||||
}
|
||||
]
|
||||
release["medium-list"].append(
|
||||
release["media"].append(
|
||||
{
|
||||
"position": "2",
|
||||
"track-list": second_track_list,
|
||||
"tracks": second_track_list,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -700,15 +700,15 @@ class ArtistFlatteningTest(unittest.TestCase):
|
|||
|
||||
def _add_alias(self, credit_dict, suffix="", locale="", primary=False):
|
||||
alias = {
|
||||
"alias": f"ALIAS{suffix}",
|
||||
"name": f"ALIAS{suffix}",
|
||||
"locale": locale,
|
||||
"sort-name": f"ALIASSORT{suffix}",
|
||||
}
|
||||
if primary:
|
||||
alias["primary"] = "primary"
|
||||
if "alias-list" not in credit_dict["artist"]:
|
||||
credit_dict["artist"]["alias-list"] = []
|
||||
credit_dict["artist"]["alias-list"].append(alias)
|
||||
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()]
|
||||
|
|
@ -725,7 +725,10 @@ class ArtistFlatteningTest(unittest.TestCase):
|
|||
assert c == ["CREDIT"]
|
||||
|
||||
def test_two_artists(self):
|
||||
credit = [self._credit_dict("a"), " AND ", self._credit_dict("b")]
|
||||
credit = [
|
||||
{**self._credit_dict("a"), "joinphrase": " AND "},
|
||||
self._credit_dict("b"),
|
||||
]
|
||||
a, s, c = musicbrainz._flatten_artist_credit(credit)
|
||||
assert a == "NAMEa AND NAMEb"
|
||||
assert s == "SORTa AND SORTb"
|
||||
|
|
@ -783,86 +786,84 @@ class MBLibraryTest(MusicBrainzTestCase):
|
|||
def test_follow_pseudo_releases(self):
|
||||
side_effect = [
|
||||
{
|
||||
"release": {
|
||||
"title": "pseudo",
|
||||
"id": "d2a6f856-b553-40a0-ac54-a321e8e2da02",
|
||||
"status": "Pseudo-Release",
|
||||
"medium-list": [
|
||||
{
|
||||
"track-list": [
|
||||
{
|
||||
"id": "baz",
|
||||
"recording": {
|
||||
"title": "translated title",
|
||||
"id": "bar",
|
||||
"length": 42,
|
||||
},
|
||||
"position": 9,
|
||||
"number": "A1",
|
||||
}
|
||||
],
|
||||
"position": 5,
|
||||
}
|
||||
],
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"name": "some-artist",
|
||||
"id": "some-id",
|
||||
},
|
||||
}
|
||||
],
|
||||
"release-group": {
|
||||
"id": "another-id",
|
||||
},
|
||||
"release-relation-list": [
|
||||
{
|
||||
"type": "transl-tracklisting",
|
||||
"target": "d2a6f856-b553-40a0-ac54-a321e8e2da01",
|
||||
"direction": "backward",
|
||||
}
|
||||
],
|
||||
}
|
||||
"title": "pseudo",
|
||||
"id": "d2a6f856-b553-40a0-ac54-a321e8e2da02",
|
||||
"status": "Pseudo-Release",
|
||||
"media": [
|
||||
{
|
||||
"tracks": [
|
||||
{
|
||||
"id": "baz",
|
||||
"recording": {
|
||||
"title": "translated title",
|
||||
"id": "bar",
|
||||
"length": 42,
|
||||
},
|
||||
"position": 9,
|
||||
"number": "A1",
|
||||
}
|
||||
],
|
||||
"position": 5,
|
||||
}
|
||||
],
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"name": "some-artist",
|
||||
"id": "some-id",
|
||||
},
|
||||
}
|
||||
],
|
||||
"release-group": {
|
||||
"id": "another-id",
|
||||
},
|
||||
"release-relations": [
|
||||
{
|
||||
"type": "transl-tracklisting",
|
||||
"target": "d2a6f856-b553-40a0-ac54-a321e8e2da01",
|
||||
"direction": "backward",
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"release": {
|
||||
"title": "actual",
|
||||
"id": "d2a6f856-b553-40a0-ac54-a321e8e2da01",
|
||||
"status": "Official",
|
||||
"medium-list": [
|
||||
{
|
||||
"track-list": [
|
||||
{
|
||||
"id": "baz",
|
||||
"recording": {
|
||||
"title": "original title",
|
||||
"id": "bar",
|
||||
"length": 42,
|
||||
},
|
||||
"position": 9,
|
||||
"number": "A1",
|
||||
}
|
||||
],
|
||||
"position": 5,
|
||||
}
|
||||
],
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"name": "some-artist",
|
||||
"id": "some-id",
|
||||
},
|
||||
}
|
||||
],
|
||||
"release-group": {
|
||||
"id": "another-id",
|
||||
},
|
||||
"country": "COUNTRY",
|
||||
}
|
||||
"title": "actual",
|
||||
"id": "d2a6f856-b553-40a0-ac54-a321e8e2da01",
|
||||
"status": "Official",
|
||||
"media": [
|
||||
{
|
||||
"tracks": [
|
||||
{
|
||||
"id": "baz",
|
||||
"recording": {
|
||||
"title": "original title",
|
||||
"id": "bar",
|
||||
"length": 42,
|
||||
},
|
||||
"position": 9,
|
||||
"number": "A1",
|
||||
}
|
||||
],
|
||||
"position": 5,
|
||||
}
|
||||
],
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"name": "some-artist",
|
||||
"id": "some-id",
|
||||
},
|
||||
}
|
||||
],
|
||||
"release-group": {
|
||||
"id": "another-id",
|
||||
},
|
||||
"country": "COUNTRY",
|
||||
},
|
||||
]
|
||||
|
||||
with mock.patch("musicbrainzngs.get_release_by_id") as gp:
|
||||
with mock.patch(
|
||||
"beetsplug.musicbrainz.MusicBrainzAPI.get_release"
|
||||
) as gp:
|
||||
gp.side_effect = side_effect
|
||||
album = self.mb.album_for_id("d2a6f856-b553-40a0-ac54-a321e8e2da02")
|
||||
assert album.country == "COUNTRY"
|
||||
|
|
@ -870,44 +871,43 @@ class MBLibraryTest(MusicBrainzTestCase):
|
|||
def test_pseudo_releases_with_empty_links(self):
|
||||
side_effect = [
|
||||
{
|
||||
"release": {
|
||||
"title": "pseudo",
|
||||
"id": "d2a6f856-b553-40a0-ac54-a321e8e2da02",
|
||||
"status": "Pseudo-Release",
|
||||
"medium-list": [
|
||||
{
|
||||
"track-list": [
|
||||
{
|
||||
"id": "baz",
|
||||
"recording": {
|
||||
"title": "translated title",
|
||||
"id": "bar",
|
||||
"length": 42,
|
||||
},
|
||||
"position": 9,
|
||||
"number": "A1",
|
||||
}
|
||||
],
|
||||
"position": 5,
|
||||
}
|
||||
],
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"name": "some-artist",
|
||||
"id": "some-id",
|
||||
},
|
||||
}
|
||||
],
|
||||
"release-group": {
|
||||
"id": "another-id",
|
||||
},
|
||||
"release-relation-list": [],
|
||||
}
|
||||
},
|
||||
"title": "pseudo",
|
||||
"id": "d2a6f856-b553-40a0-ac54-a321e8e2da02",
|
||||
"status": "Pseudo-Release",
|
||||
"media": [
|
||||
{
|
||||
"tracks": [
|
||||
{
|
||||
"id": "baz",
|
||||
"recording": {
|
||||
"title": "translated title",
|
||||
"id": "bar",
|
||||
"length": 42,
|
||||
},
|
||||
"position": 9,
|
||||
"number": "A1",
|
||||
}
|
||||
],
|
||||
"position": 5,
|
||||
}
|
||||
],
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"name": "some-artist",
|
||||
"id": "some-id",
|
||||
},
|
||||
}
|
||||
],
|
||||
"release-group": {
|
||||
"id": "another-id",
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
with mock.patch("musicbrainzngs.get_release_by_id") as gp:
|
||||
with mock.patch(
|
||||
"beetsplug.musicbrainz.MusicBrainzAPI.get_release"
|
||||
) as gp:
|
||||
gp.side_effect = side_effect
|
||||
album = self.mb.album_for_id("d2a6f856-b553-40a0-ac54-a321e8e2da02")
|
||||
assert album.country is None
|
||||
|
|
@ -915,43 +915,43 @@ class MBLibraryTest(MusicBrainzTestCase):
|
|||
def test_pseudo_releases_without_links(self):
|
||||
side_effect = [
|
||||
{
|
||||
"release": {
|
||||
"title": "pseudo",
|
||||
"id": "d2a6f856-b553-40a0-ac54-a321e8e2da02",
|
||||
"status": "Pseudo-Release",
|
||||
"medium-list": [
|
||||
{
|
||||
"track-list": [
|
||||
{
|
||||
"id": "baz",
|
||||
"recording": {
|
||||
"title": "translated title",
|
||||
"id": "bar",
|
||||
"length": 42,
|
||||
},
|
||||
"position": 9,
|
||||
"number": "A1",
|
||||
}
|
||||
],
|
||||
"position": 5,
|
||||
}
|
||||
],
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"name": "some-artist",
|
||||
"id": "some-id",
|
||||
},
|
||||
}
|
||||
],
|
||||
"release-group": {
|
||||
"id": "another-id",
|
||||
},
|
||||
}
|
||||
},
|
||||
"title": "pseudo",
|
||||
"id": "d2a6f856-b553-40a0-ac54-a321e8e2da02",
|
||||
"status": "Pseudo-Release",
|
||||
"media": [
|
||||
{
|
||||
"tracks": [
|
||||
{
|
||||
"id": "baz",
|
||||
"recording": {
|
||||
"title": "translated title",
|
||||
"id": "bar",
|
||||
"length": 42,
|
||||
},
|
||||
"position": 9,
|
||||
"number": "A1",
|
||||
}
|
||||
],
|
||||
"position": 5,
|
||||
}
|
||||
],
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"name": "some-artist",
|
||||
"id": "some-id",
|
||||
},
|
||||
}
|
||||
],
|
||||
"release-group": {
|
||||
"id": "another-id",
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
with mock.patch("musicbrainzngs.get_release_by_id") as gp:
|
||||
with mock.patch(
|
||||
"beetsplug.musicbrainz.MusicBrainzAPI.get_release"
|
||||
) as gp:
|
||||
gp.side_effect = side_effect
|
||||
album = self.mb.album_for_id("d2a6f856-b553-40a0-ac54-a321e8e2da02")
|
||||
assert album.country is None
|
||||
|
|
@ -959,50 +959,50 @@ class MBLibraryTest(MusicBrainzTestCase):
|
|||
def test_pseudo_releases_with_unsupported_links(self):
|
||||
side_effect = [
|
||||
{
|
||||
"release": {
|
||||
"title": "pseudo",
|
||||
"id": "d2a6f856-b553-40a0-ac54-a321e8e2da02",
|
||||
"status": "Pseudo-Release",
|
||||
"medium-list": [
|
||||
{
|
||||
"track-list": [
|
||||
{
|
||||
"id": "baz",
|
||||
"recording": {
|
||||
"title": "translated title",
|
||||
"id": "bar",
|
||||
"length": 42,
|
||||
},
|
||||
"position": 9,
|
||||
"number": "A1",
|
||||
}
|
||||
],
|
||||
"position": 5,
|
||||
}
|
||||
],
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"name": "some-artist",
|
||||
"id": "some-id",
|
||||
},
|
||||
}
|
||||
],
|
||||
"release-group": {
|
||||
"id": "another-id",
|
||||
},
|
||||
"release-relation-list": [
|
||||
{
|
||||
"type": "remaster",
|
||||
"target": "d2a6f856-b553-40a0-ac54-a321e8e2da01",
|
||||
"direction": "backward",
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
"title": "pseudo",
|
||||
"id": "d2a6f856-b553-40a0-ac54-a321e8e2da02",
|
||||
"status": "Pseudo-Release",
|
||||
"media": [
|
||||
{
|
||||
"tracks": [
|
||||
{
|
||||
"id": "baz",
|
||||
"recording": {
|
||||
"title": "translated title",
|
||||
"id": "bar",
|
||||
"length": 42,
|
||||
},
|
||||
"position": 9,
|
||||
"number": "A1",
|
||||
}
|
||||
],
|
||||
"position": 5,
|
||||
}
|
||||
],
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"name": "some-artist",
|
||||
"id": "some-id",
|
||||
},
|
||||
}
|
||||
],
|
||||
"release-group": {
|
||||
"id": "another-id",
|
||||
},
|
||||
"release-relations": [
|
||||
{
|
||||
"type": "remaster",
|
||||
"target": "d2a6f856-b553-40a0-ac54-a321e8e2da01",
|
||||
"direction": "backward",
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
with mock.patch("musicbrainzngs.get_release_by_id") as gp:
|
||||
with mock.patch(
|
||||
"beetsplug.musicbrainz.MusicBrainzAPI.get_release"
|
||||
) as gp:
|
||||
gp.side_effect = side_effect
|
||||
album = self.mb.album_for_id("d2a6f856-b553-40a0-ac54-a321e8e2da02")
|
||||
assert album.country is None
|
||||
|
|
@ -1069,30 +1069,28 @@ class TestMusicBrainzPlugin(PluginMixin):
|
|||
lambda *_, **__: {"release-list": [{"id": self.mbid}]},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"musicbrainzngs.get_release_by_id",
|
||||
"beetsplug.musicbrainz.MusicBrainzAPI.get_release",
|
||||
lambda *_, **__: {
|
||||
"release": {
|
||||
"title": "hi",
|
||||
"id": self.mbid,
|
||||
"status": "status",
|
||||
"medium-list": [
|
||||
{
|
||||
"track-list": [
|
||||
{
|
||||
"id": "baz",
|
||||
"recording": self.RECORDING,
|
||||
"position": 9,
|
||||
"number": "A1",
|
||||
}
|
||||
],
|
||||
"position": 5,
|
||||
}
|
||||
],
|
||||
"artist-credit": [
|
||||
{"artist": {"name": "some-artist", "id": "some-id"}}
|
||||
],
|
||||
"release-group": {"id": "another-id"},
|
||||
}
|
||||
"title": "hi",
|
||||
"id": self.mbid,
|
||||
"status": "status",
|
||||
"media": [
|
||||
{
|
||||
"tracks": [
|
||||
{
|
||||
"id": "baz",
|
||||
"recording": self.RECORDING,
|
||||
"position": 9,
|
||||
"number": "A1",
|
||||
}
|
||||
],
|
||||
"position": 5,
|
||||
}
|
||||
],
|
||||
"artist-credit": [
|
||||
{"artist": {"name": "some-artist", "id": "some-id"}}
|
||||
],
|
||||
"release-group": {"id": "another-id"},
|
||||
},
|
||||
)
|
||||
candidates = list(mb.candidates([], "hello", "there", False))
|
||||
|
|
@ -1100,3 +1098,84 @@ class TestMusicBrainzPlugin(PluginMixin):
|
|||
assert len(candidates) == 1
|
||||
assert candidates[0].tracks[0].track_id == self.RECORDING["id"]
|
||||
assert candidates[0].album == "hi"
|
||||
|
||||
|
||||
def test_group_relations():
|
||||
raw_release = {
|
||||
"id": "r1",
|
||||
"relations": [
|
||||
{"target-type": "artist", "type": "vocal", "name": "A"},
|
||||
{"target-type": "url", "type": "streaming", "url": "http://s"},
|
||||
{"target-type": "url", "type": "purchase", "url": "http://p"},
|
||||
{
|
||||
"target-type": "work",
|
||||
"type": "performance",
|
||||
"work": {
|
||||
"relations": [
|
||||
{
|
||||
"artist": {"name": "幾田りら"},
|
||||
"target-type": "artist",
|
||||
"type": "composer",
|
||||
},
|
||||
{
|
||||
"target-type": "url",
|
||||
"type": "lyrics",
|
||||
"url": {
|
||||
"resource": "https://utaten.com/lyric/tt24121002/"
|
||||
},
|
||||
},
|
||||
{
|
||||
"artist": {"name": "幾田りら"},
|
||||
"target-type": "artist",
|
||||
"type": "lyricist",
|
||||
},
|
||||
{
|
||||
"target-type": "url",
|
||||
"type": "lyrics",
|
||||
"url": {
|
||||
"resource": "https://www.uta-net.com/song/366579/"
|
||||
},
|
||||
},
|
||||
],
|
||||
"title": "百花繚乱",
|
||||
"type": "Song",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
assert musicbrainz.MusicBrainzAPI._group_relations(raw_release) == {
|
||||
"id": "r1",
|
||||
"artist-relations": [{"type": "vocal", "name": "A"}],
|
||||
"url-relations": [
|
||||
{"type": "streaming", "url": "http://s"},
|
||||
{"type": "purchase", "url": "http://p"},
|
||||
],
|
||||
"work-relations": [
|
||||
{
|
||||
"type": "performance",
|
||||
"work": {
|
||||
"artist-relations": [
|
||||
{"type": "composer", "artist": {"name": "幾田りら"}},
|
||||
{"type": "lyricist", "artist": {"name": "幾田りら"}},
|
||||
],
|
||||
"url-relations": [
|
||||
{
|
||||
"type": "lyrics",
|
||||
"url": {
|
||||
"resource": "https://utaten.com/lyric/tt24121002/"
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "lyrics",
|
||||
"url": {
|
||||
"resource": "https://www.uta-net.com/song/366579/"
|
||||
},
|
||||
},
|
||||
],
|
||||
"title": "百花繚乱",
|
||||
"type": "Song",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,346 +1,515 @@
|
|||
{
|
||||
"release": {
|
||||
"id": "dc3ee2df-0bc1-49eb-b8c4-34473d279a43",
|
||||
"title": "In Bloom",
|
||||
"status": "Pseudo-Release",
|
||||
"quality": "normal",
|
||||
"text-representation": {
|
||||
"language": "eng",
|
||||
"script": "Latn"
|
||||
},
|
||||
"artist-credit": [
|
||||
{
|
||||
"name": "Lilas Ikuta",
|
||||
"artist": {
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"type": "Person",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"country": "JP",
|
||||
"alias-list": [
|
||||
"aliases": [],
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"aliases": [
|
||||
{
|
||||
"begin": null,
|
||||
"end": null,
|
||||
"ended": false,
|
||||
"locale": "en",
|
||||
"name": "Lilas Ikuta",
|
||||
"primary": true,
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"type": "Artist name",
|
||||
"type-id": "894afba6-2816-3c24-8072-eadb66bd04bc"
|
||||
}
|
||||
],
|
||||
"country": "JP",
|
||||
"disambiguation": "",
|
||||
"genres": [
|
||||
{
|
||||
"count": 1,
|
||||
"disambiguation": "",
|
||||
"id": "eba7715e-ee26-4989-8d49-9db382955419",
|
||||
"name": "j-pop"
|
||||
},
|
||||
{
|
||||
"count": 1,
|
||||
"disambiguation": "",
|
||||
"id": "455f264b-db00-4716-991d-fbd32dc24523",
|
||||
"name": "singer-songwriter"
|
||||
}
|
||||
],
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"tags": [
|
||||
{
|
||||
"count": 1,
|
||||
"name": "j-pop"
|
||||
},
|
||||
{
|
||||
"count": 1,
|
||||
"name": "singer-songwriter"
|
||||
}
|
||||
],
|
||||
"type": "Person",
|
||||
"type-id": "b6e035f4-3ce9-331c-97df-83397230b0df"
|
||||
},
|
||||
"joinphrase": "",
|
||||
"name": "Lilas Ikuta"
|
||||
}
|
||||
],
|
||||
"asin": null,
|
||||
"barcode": null,
|
||||
"cover-art-archive": {
|
||||
"artwork": false,
|
||||
"back": false,
|
||||
"count": 0,
|
||||
"darkened": false,
|
||||
"front": false
|
||||
},
|
||||
"disambiguation": "",
|
||||
"genres": [],
|
||||
"id": "dc3ee2df-0bc1-49eb-b8c4-34473d279a43",
|
||||
"label-info": [],
|
||||
"media": [
|
||||
{
|
||||
"format": "Digital Media",
|
||||
"format-id": "907a28d9-b3b2-3ef6-89a8-7b18d91d4794",
|
||||
"id": "606faab7-60fa-3a8b-a40f-2c66150cce81",
|
||||
"position": 1,
|
||||
"title": "",
|
||||
"track-count": 1,
|
||||
"track-offset": 0,
|
||||
"tracks": [
|
||||
{
|
||||
"artist-credit": [
|
||||
{
|
||||
"locale": "en",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"type": "Artist name",
|
||||
"primary": "primary",
|
||||
"alias": "Lilas Ikuta"
|
||||
"artist": {
|
||||
"aliases": [
|
||||
{
|
||||
"begin": null,
|
||||
"end": null,
|
||||
"ended": false,
|
||||
"locale": "en",
|
||||
"name": "Lilas Ikuta",
|
||||
"primary": true,
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"type": "Artist name",
|
||||
"type-id": "894afba6-2816-3c24-8072-eadb66bd04bc"
|
||||
}
|
||||
],
|
||||
"country": "JP",
|
||||
"disambiguation": "",
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"type": "Person",
|
||||
"type-id": "b6e035f4-3ce9-331c-97df-83397230b0df"
|
||||
},
|
||||
"joinphrase": "",
|
||||
"name": "Lilas Ikuta"
|
||||
}
|
||||
],
|
||||
"alias-count": 1,
|
||||
"tag-list": [
|
||||
{
|
||||
"count": "1",
|
||||
"name": "j-pop"
|
||||
},
|
||||
{
|
||||
"count": "1",
|
||||
"name": "singer-songwriter"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"release-group": {
|
||||
"id": "da0d6bbb-f44b-4fff-8739-9d72db0402a1",
|
||||
"type": "Single",
|
||||
"title": "百花繚乱",
|
||||
"first-release-date": "2025-01-10",
|
||||
"primary-type": "Single",
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"type": "Person",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"country": "JP",
|
||||
"alias-list": [
|
||||
{
|
||||
"locale": "en",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"type": "Artist name",
|
||||
"primary": "primary",
|
||||
"alias": "Lilas Ikuta"
|
||||
}
|
||||
],
|
||||
"alias-count": 1,
|
||||
"tag-list": [
|
||||
{
|
||||
"count": "1",
|
||||
"name": "j-pop"
|
||||
},
|
||||
{
|
||||
"count": "1",
|
||||
"name": "singer-songwriter"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"artist-credit-phrase": "幾田りら"
|
||||
},
|
||||
"cover-art-archive": {
|
||||
"artwork": "false",
|
||||
"count": "0",
|
||||
"front": "false",
|
||||
"back": "false"
|
||||
},
|
||||
"label-info-list": [],
|
||||
"label-info-count": 0,
|
||||
"medium-list": [
|
||||
{
|
||||
"position": "1",
|
||||
"format": "Digital Media",
|
||||
"track-list": [
|
||||
{
|
||||
"id": "2018b012-a184-49a2-a464-fb4628a89588",
|
||||
"position": "1",
|
||||
"number": "1",
|
||||
"title": "In Bloom",
|
||||
"length": "179239",
|
||||
"id": "2018b012-a184-49a2-a464-fb4628a89588",
|
||||
"length": 179239,
|
||||
"number": "1",
|
||||
"position": 1,
|
||||
"recording": {
|
||||
"aliases": [],
|
||||
"artist-credit": [
|
||||
{
|
||||
"name": "Lilas Ikuta",
|
||||
"artist": {
|
||||
"country": "JP",
|
||||
"disambiguation": "",
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"type": "Person",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"type": "Person",
|
||||
"type-id": "b6e035f4-3ce9-331c-97df-83397230b0df"
|
||||
},
|
||||
"joinphrase": "",
|
||||
"name": "幾田りら"
|
||||
}
|
||||
],
|
||||
"artist-relations": [
|
||||
{
|
||||
"artist": {
|
||||
"country": "JP",
|
||||
"alias-list": [
|
||||
"disambiguation": "Japanese composer/arranger/guitarist, agehasprings",
|
||||
"id": "f24241fb-4d89-4bf2-8336-3f2a7d2c0025",
|
||||
"name": "KOHD",
|
||||
"sort-name": "KOHD",
|
||||
"type": "Person",
|
||||
"type-id": "b6e035f4-3ce9-331c-97df-83397230b0df"
|
||||
},
|
||||
"attribute-ids": {},
|
||||
"attribute-values": {},
|
||||
"attributes": [],
|
||||
"begin": null,
|
||||
"direction": "backward",
|
||||
"end": null,
|
||||
"ended": false,
|
||||
"source-credit": "",
|
||||
"target-credit": "",
|
||||
"type": "arranger",
|
||||
"type-id": "22661fb8-cdb7-4f67-8385-b2a8be6c9f0d"
|
||||
},
|
||||
{
|
||||
"artist": {
|
||||
"country": "JP",
|
||||
"disambiguation": "",
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"type": "Person",
|
||||
"type-id": "b6e035f4-3ce9-331c-97df-83397230b0df"
|
||||
},
|
||||
"attribute-ids": {},
|
||||
"attribute-values": {},
|
||||
"attributes": [],
|
||||
"begin": "2025",
|
||||
"direction": "backward",
|
||||
"end": "2025",
|
||||
"ended": true,
|
||||
"source-credit": "",
|
||||
"target-credit": "Lilas Ikuta",
|
||||
"type": "phonographic copyright",
|
||||
"type-id": "7fd5fbc0-fbf4-4d04-be23-417d50a4dc30"
|
||||
},
|
||||
{
|
||||
"artist": {
|
||||
"country": "JP",
|
||||
"disambiguation": "",
|
||||
"id": "1d27ab8a-a0df-47cf-b4cc-d2d7a0712a05",
|
||||
"name": "山本秀哉",
|
||||
"sort-name": "Yamamoto, Shuya",
|
||||
"type": "Person",
|
||||
"type-id": "b6e035f4-3ce9-331c-97df-83397230b0df"
|
||||
},
|
||||
"attribute-ids": {},
|
||||
"attribute-values": {},
|
||||
"attributes": [],
|
||||
"begin": null,
|
||||
"direction": "backward",
|
||||
"end": null,
|
||||
"ended": false,
|
||||
"source-credit": "",
|
||||
"target-credit": "",
|
||||
"type": "producer",
|
||||
"type-id": "5c0ceac3-feb4-41f0-868d-dc06f6e27fc0"
|
||||
},
|
||||
{
|
||||
"artist": {
|
||||
"country": "JP",
|
||||
"disambiguation": "",
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"type": "Person",
|
||||
"type-id": "b6e035f4-3ce9-331c-97df-83397230b0df"
|
||||
},
|
||||
"attribute-ids": {},
|
||||
"attribute-values": {},
|
||||
"attributes": [],
|
||||
"begin": null,
|
||||
"direction": "backward",
|
||||
"end": null,
|
||||
"ended": false,
|
||||
"source-credit": "",
|
||||
"target-credit": "",
|
||||
"type": "vocal",
|
||||
"type-id": "0fdbe3c6-7700-4a31-ae54-b53f06ae1cfa"
|
||||
}
|
||||
],
|
||||
"disambiguation": "",
|
||||
"first-release-date": "2025-01-10",
|
||||
"genres": [],
|
||||
"id": "781724c1-a039-41e6-bd9b-770c3b9d5b8e",
|
||||
"isrcs": [
|
||||
"JPP302400868"
|
||||
],
|
||||
"length": 179546,
|
||||
"tags": [],
|
||||
"title": "百花繚乱",
|
||||
"url-relations": [
|
||||
{
|
||||
"attribute-ids": {},
|
||||
"attribute-values": {},
|
||||
"attributes": [],
|
||||
"begin": null,
|
||||
"direction": "forward",
|
||||
"end": null,
|
||||
"ended": false,
|
||||
"source-credit": "",
|
||||
"target-credit": "",
|
||||
"type": "free streaming",
|
||||
"type-id": "7e41ef12-a124-4324-afdb-fdbae687a89c",
|
||||
"url": {
|
||||
"id": "d076eaf9-5fde-4f6e-a946-cde16b67aa3b",
|
||||
"resource": "https://open.spotify.com/track/782PTXsbAWB70ySDZ5NHmP"
|
||||
}
|
||||
},
|
||||
{
|
||||
"attribute-ids": {},
|
||||
"attribute-values": {},
|
||||
"attributes": [],
|
||||
"begin": null,
|
||||
"direction": "forward",
|
||||
"end": null,
|
||||
"ended": false,
|
||||
"source-credit": "",
|
||||
"target-credit": "",
|
||||
"type": "purchase for download",
|
||||
"type-id": "92777657-504c-4acb-bd33-51a201bd57e1",
|
||||
"url": {
|
||||
"id": "64879627-6eca-4755-98b5-b2234a8dbc61",
|
||||
"resource": "https://music.apple.com/jp/song/1857886416"
|
||||
}
|
||||
},
|
||||
{
|
||||
"attribute-ids": {},
|
||||
"attribute-values": {},
|
||||
"attributes": [],
|
||||
"begin": null,
|
||||
"direction": "forward",
|
||||
"end": null,
|
||||
"ended": false,
|
||||
"source-credit": "",
|
||||
"target-credit": "",
|
||||
"type": "streaming",
|
||||
"type-id": "b5f3058a-666c-406f-aafb-f9249fc7b122",
|
||||
"url": {
|
||||
"id": "64879627-6eca-4755-98b5-b2234a8dbc61",
|
||||
"resource": "https://music.apple.com/jp/song/1857886416"
|
||||
}
|
||||
}
|
||||
],
|
||||
"video": false,
|
||||
"work-relations": [
|
||||
{
|
||||
"attribute-ids": {},
|
||||
"attribute-values": {},
|
||||
"attributes": [],
|
||||
"begin": null,
|
||||
"direction": "forward",
|
||||
"end": null,
|
||||
"ended": false,
|
||||
"source-credit": "",
|
||||
"target-credit": "",
|
||||
"type": "performance",
|
||||
"type-id": "a3005666-a872-32c3-ad06-98af558e99b0",
|
||||
"work": {
|
||||
"artist-relations": [
|
||||
{
|
||||
"locale": "en",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"type": "Artist name",
|
||||
"primary": "primary",
|
||||
"alias": "Lilas Ikuta"
|
||||
}
|
||||
],
|
||||
"alias-count": 1,
|
||||
"tag-list": [
|
||||
{
|
||||
"count": "1",
|
||||
"name": "j-pop"
|
||||
"artist": {
|
||||
"country": "JP",
|
||||
"disambiguation": "",
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"type": "Person",
|
||||
"type-id": "b6e035f4-3ce9-331c-97df-83397230b0df"
|
||||
},
|
||||
"attribute-ids": {},
|
||||
"attribute-values": {},
|
||||
"attributes": [],
|
||||
"begin": null,
|
||||
"direction": "backward",
|
||||
"end": null,
|
||||
"ended": false,
|
||||
"source-credit": "",
|
||||
"target-credit": "",
|
||||
"type": "composer",
|
||||
"type-id": "d59d99ea-23d4-4a80-b066-edca32ee158f"
|
||||
},
|
||||
{
|
||||
"count": "1",
|
||||
"name": "singer-songwriter"
|
||||
"artist": {
|
||||
"country": "JP",
|
||||
"disambiguation": "",
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"type": "Person",
|
||||
"type-id": "b6e035f4-3ce9-331c-97df-83397230b0df"
|
||||
},
|
||||
"attribute-ids": {},
|
||||
"attribute-values": {},
|
||||
"attributes": [],
|
||||
"begin": null,
|
||||
"direction": "backward",
|
||||
"end": null,
|
||||
"ended": false,
|
||||
"source-credit": "",
|
||||
"target-credit": "",
|
||||
"type": "lyricist",
|
||||
"type-id": "3e48faba-ec01-47fd-8e89-30e81161661c"
|
||||
}
|
||||
],
|
||||
"attributes": [],
|
||||
"disambiguation": "",
|
||||
"id": "9e14d6b2-ac7d-43e9-82a9-561bc76ce2ed",
|
||||
"iswcs": [],
|
||||
"language": "jpn",
|
||||
"languages": [
|
||||
"jpn"
|
||||
],
|
||||
"title": "百花繚乱",
|
||||
"type": "Song",
|
||||
"type-id": "f061270a-2fd6-32f1-a641-f0f8676d14e6",
|
||||
"url-relations": [
|
||||
{
|
||||
"attribute-ids": {},
|
||||
"attribute-values": {},
|
||||
"attributes": [],
|
||||
"begin": null,
|
||||
"direction": "backward",
|
||||
"end": null,
|
||||
"ended": false,
|
||||
"source-credit": "",
|
||||
"target-credit": "",
|
||||
"type": "lyrics",
|
||||
"type-id": "e38e65aa-75e0-42ba-ace0-072aeb91a538",
|
||||
"url": {
|
||||
"id": "dfac3640-6b23-4991-a59c-7cb80e8eb950",
|
||||
"resource": "https://utaten.com/lyric/tt24121002/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"attribute-ids": {},
|
||||
"attribute-values": {},
|
||||
"attributes": [],
|
||||
"begin": null,
|
||||
"direction": "backward",
|
||||
"end": null,
|
||||
"ended": false,
|
||||
"source-credit": "",
|
||||
"target-credit": "",
|
||||
"type": "lyrics",
|
||||
"type-id": "e38e65aa-75e0-42ba-ace0-072aeb91a538",
|
||||
"url": {
|
||||
"id": "b1b5d5df-e79d-4cda-bb2a-8014e5505415",
|
||||
"resource": "https://www.uta-net.com/song/366579/"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"recording": {
|
||||
"id": "781724c1-a039-41e6-bd9b-770c3b9d5b8e",
|
||||
"title": "百花繚乱",
|
||||
"length": "179546",
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"type": "Person",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"country": "JP",
|
||||
"alias-list": [
|
||||
{
|
||||
"locale": "en",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"type": "Artist name",
|
||||
"primary": "primary",
|
||||
"alias": "Lilas Ikuta"
|
||||
}
|
||||
],
|
||||
"alias-count": 1,
|
||||
"tag-list": [
|
||||
{
|
||||
"count": "1",
|
||||
"name": "j-pop"
|
||||
},
|
||||
{
|
||||
"count": "1",
|
||||
"name": "singer-songwriter"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"isrc-list": [
|
||||
"JPP302400868"
|
||||
],
|
||||
"isrc-count": 1,
|
||||
"artist-relation-list": [
|
||||
{
|
||||
"type": "arranger",
|
||||
"type-id": "22661fb8-cdb7-4f67-8385-b2a8be6c9f0d",
|
||||
"target": "f24241fb-4d89-4bf2-8336-3f2a7d2c0025",
|
||||
"direction": "backward",
|
||||
"artist": {
|
||||
"id": "f24241fb-4d89-4bf2-8336-3f2a7d2c0025",
|
||||
"type": "Person",
|
||||
"name": "KOHD",
|
||||
"sort-name": "KOHD",
|
||||
"country": "JP",
|
||||
"disambiguation": "Japanese composer/arranger/guitarist, agehasprings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "phonographic copyright",
|
||||
"type-id": "7fd5fbc0-fbf4-4d04-be23-417d50a4dc30",
|
||||
"target": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"direction": "backward",
|
||||
"begin": "2025",
|
||||
"end": "2025",
|
||||
"ended": "true",
|
||||
"artist": {
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"type": "Person",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"country": "JP"
|
||||
},
|
||||
"target-credit": "Lilas Ikuta"
|
||||
},
|
||||
{
|
||||
"type": "producer",
|
||||
"type-id": "5c0ceac3-feb4-41f0-868d-dc06f6e27fc0",
|
||||
"target": "1d27ab8a-a0df-47cf-b4cc-d2d7a0712a05",
|
||||
"direction": "backward",
|
||||
"artist": {
|
||||
"id": "1d27ab8a-a0df-47cf-b4cc-d2d7a0712a05",
|
||||
"type": "Person",
|
||||
"name": "山本秀哉",
|
||||
"sort-name": "Yamamoto, Shuya",
|
||||
"country": "JP"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "vocal",
|
||||
"type-id": "0fdbe3c6-7700-4a31-ae54-b53f06ae1cfa",
|
||||
"target": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"direction": "backward",
|
||||
"artist": {
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"type": "Person",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"country": "JP"
|
||||
}
|
||||
}
|
||||
],
|
||||
"work-relation-list": [
|
||||
{
|
||||
"type": "performance",
|
||||
"type-id": "a3005666-a872-32c3-ad06-98af558e99b0",
|
||||
"target": "9e14d6b2-ac7d-43e9-82a9-561bc76ce2ed",
|
||||
"direction": "forward",
|
||||
"work": {
|
||||
"id": "9e14d6b2-ac7d-43e9-82a9-561bc76ce2ed",
|
||||
"type": "Song",
|
||||
"title": "百花繚乱",
|
||||
"language": "jpn",
|
||||
"artist-relation-list": [
|
||||
{
|
||||
"type": "composer",
|
||||
"type-id": "d59d99ea-23d4-4a80-b066-edca32ee158f",
|
||||
"target": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"direction": "backward",
|
||||
"artist": {
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"type": "Person",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"country": "JP"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "lyricist",
|
||||
"type-id": "3e48faba-ec01-47fd-8e89-30e81161661c",
|
||||
"target": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"direction": "backward",
|
||||
"artist": {
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"type": "Person",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"country": "JP"
|
||||
}
|
||||
}
|
||||
],
|
||||
"url-relation-list": [
|
||||
{
|
||||
"type": "lyrics",
|
||||
"type-id": "e38e65aa-75e0-42ba-ace0-072aeb91a538",
|
||||
"target": "https://utaten.com/lyric/tt24121002/",
|
||||
"direction": "backward"
|
||||
},
|
||||
{
|
||||
"type": "lyrics",
|
||||
"type-id": "e38e65aa-75e0-42ba-ace0-072aeb91a538",
|
||||
"target": "https://www.uta-net.com/song/366579/",
|
||||
"direction": "backward"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"artist-credit-phrase": "幾田りら"
|
||||
]
|
||||
},
|
||||
"title": "In Bloom"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"packaging": null,
|
||||
"packaging-id": null,
|
||||
"quality": "normal",
|
||||
"release-group": {
|
||||
"aliases": [],
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"aliases": [
|
||||
{
|
||||
"begin": null,
|
||||
"end": null,
|
||||
"ended": false,
|
||||
"locale": "en",
|
||||
"name": "Lilas Ikuta",
|
||||
"primary": true,
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"type": "Artist name",
|
||||
"type-id": "894afba6-2816-3c24-8072-eadb66bd04bc"
|
||||
}
|
||||
],
|
||||
"country": "JP",
|
||||
"disambiguation": "",
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"type": "Person",
|
||||
"type-id": "b6e035f4-3ce9-331c-97df-83397230b0df"
|
||||
},
|
||||
"joinphrase": "",
|
||||
"name": "幾田りら"
|
||||
}
|
||||
],
|
||||
"disambiguation": "",
|
||||
"first-release-date": "2025-01-10",
|
||||
"genres": [],
|
||||
"id": "da0d6bbb-f44b-4fff-8739-9d72db0402a1",
|
||||
"primary-type": "Single",
|
||||
"primary-type-id": "d6038452-8ee0-3f68-affc-2de9a1ede0b9",
|
||||
"secondary-type-ids": [],
|
||||
"secondary-types": [],
|
||||
"tags": [],
|
||||
"title": "百花繚乱"
|
||||
},
|
||||
"release-relations": [
|
||||
{
|
||||
"attribute-ids": {},
|
||||
"attribute-values": {},
|
||||
"attributes": [],
|
||||
"begin": null,
|
||||
"direction": "backward",
|
||||
"end": null,
|
||||
"ended": false,
|
||||
"release": {
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"country": "JP",
|
||||
"disambiguation": "",
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"type": null,
|
||||
"type-id": null
|
||||
},
|
||||
"artist-credit-phrase": "Lilas Ikuta",
|
||||
"track_or_recording_length": "179239"
|
||||
"joinphrase": "",
|
||||
"name": "幾田りら"
|
||||
}
|
||||
],
|
||||
"track-count": 1
|
||||
}
|
||||
],
|
||||
"medium-count": 1,
|
||||
"release-relation-list": [
|
||||
{
|
||||
"type": "transl-tracklisting",
|
||||
"type-id": "fc399d47-23a7-4c28-bfcf-0607a562b644",
|
||||
"target": "a5ce1d11-2e32-45a4-b37f-c1589d46b103",
|
||||
"direction": "backward",
|
||||
"release": {
|
||||
"id": "a5ce1d11-2e32-45a4-b37f-c1589d46b103",
|
||||
"title": "百花繚乱",
|
||||
"quality": "normal",
|
||||
"text-representation": {
|
||||
"language": "jpn",
|
||||
"script": "Jpan"
|
||||
},
|
||||
"artist-credit": [
|
||||
{
|
||||
"artist": {
|
||||
"id": "55e42264-ef27-49d8-93fd-29f930dc96e4",
|
||||
"name": "幾田りら",
|
||||
"sort-name": "Ikuta, Lilas",
|
||||
"country": "JP"
|
||||
}
|
||||
}
|
||||
],
|
||||
"date": "2025-01-10",
|
||||
"country": "XW",
|
||||
"release-event-list": [
|
||||
{
|
||||
"date": "2025-01-10",
|
||||
"area": {
|
||||
"id": "525d4e18-3d00-31b9-a58b-a146a916de8f",
|
||||
"name": "[Worldwide]",
|
||||
"sort-name": "[Worldwide]",
|
||||
"iso-3166-1-code-list": [
|
||||
"XW"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"release-event-count": 1,
|
||||
"barcode": "199066336168",
|
||||
"medium-list": [],
|
||||
"medium-count": 0,
|
||||
"artist-credit-phrase": "幾田りら"
|
||||
}
|
||||
}
|
||||
],
|
||||
"artist-credit-phrase": "Lilas Ikuta"
|
||||
}
|
||||
}
|
||||
"barcode": "199066336168",
|
||||
"country": "XW",
|
||||
"date": "2025-01-10",
|
||||
"disambiguation": "",
|
||||
"id": "a5ce1d11-2e32-45a4-b37f-c1589d46b103",
|
||||
"media": [],
|
||||
"packaging": null,
|
||||
"packaging-id": null,
|
||||
"quality": "normal",
|
||||
"release-events": [
|
||||
{
|
||||
"area": {
|
||||
"disambiguation": "",
|
||||
"id": "525d4e18-3d00-31b9-a58b-a146a916de8f",
|
||||
"iso-3166-1-codes": [
|
||||
"XW"
|
||||
],
|
||||
"name": "[Worldwide]",
|
||||
"sort-name": "[Worldwide]",
|
||||
"type": null,
|
||||
"type-id": null
|
||||
},
|
||||
"date": "2025-01-10"
|
||||
}
|
||||
],
|
||||
"release-group": null,
|
||||
"status": null,
|
||||
"status-id": null,
|
||||
"text-representation": {
|
||||
"language": "jpn",
|
||||
"script": "Jpan"
|
||||
},
|
||||
"title": "百花繚乱"
|
||||
},
|
||||
"source-credit": "",
|
||||
"target-credit": "",
|
||||
"type": "transl-tracklisting",
|
||||
"type-id": "fc399d47-23a7-4c28-bfcf-0607a562b644"
|
||||
}
|
||||
],
|
||||
"status": "Pseudo-Release",
|
||||
"status-id": "41121bb9-3413-3818-8a9a-9742318349aa",
|
||||
"tags": [],
|
||||
"text-representation": {
|
||||
"language": "eng",
|
||||
"script": "Latn"
|
||||
},
|
||||
"title": "In Bloom"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue