mirror of
https://github.com/beetbox/beets.git
synced 2026-01-13 19:52:48 +01:00
Migrate parentwork to use MusicBrainzAPI
This commit is contained in:
parent
741f5c4be1
commit
a33371b6ef
7 changed files with 106 additions and 122 deletions
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
|
|
@ -66,7 +66,7 @@ jobs:
|
|||
- if: ${{ env.IS_MAIN_PYTHON != 'true' }}
|
||||
name: Test without coverage
|
||||
run: |
|
||||
poetry install --without=lint --extras=autobpm --extras=lyrics --extras=replaygain --extras=reflink --extras=fetchart --extras=chroma --extras=sonosupdate --extras=parentwork
|
||||
poetry install --without=lint --extras=autobpm --extras=lyrics --extras=replaygain --extras=reflink --extras=fetchart --extras=chroma --extras=sonosupdate
|
||||
poe test
|
||||
|
||||
- if: ${{ env.IS_MAIN_PYTHON == 'true' }}
|
||||
|
|
@ -74,7 +74,7 @@ jobs:
|
|||
env:
|
||||
LYRICS_UPDATED: ${{ steps.lyrics-update.outputs.any_changed }}
|
||||
run: |
|
||||
poetry install --extras=autobpm --extras=lyrics --extras=docs --extras=replaygain --extras=reflink --extras=fetchart --extras=chroma --extras=sonosupdate --extras=parentwork
|
||||
poetry install --extras=autobpm --extras=lyrics --extras=docs --extras=replaygain --extras=reflink --extras=fetchart --extras=chroma --extras=sonosupdate
|
||||
poe docs
|
||||
poe test-with-coverage
|
||||
|
||||
|
|
|
|||
|
|
@ -91,6 +91,9 @@ class MusicBrainzAPI(RequestHandler):
|
|||
def get_recording(self, id_: str, **kwargs) -> JSONDict:
|
||||
return self.get_entity(f"recording/{id_}", **kwargs)
|
||||
|
||||
def get_work(self, id_: str, **kwargs) -> JSONDict:
|
||||
return self.get_entity(f"work/{id_}", **kwargs)
|
||||
|
||||
def browse_recordings(self, **kwargs) -> list[JSONDict]:
|
||||
return self.get_entity("recording", **kwargs)["recordings"]
|
||||
|
||||
|
|
|
|||
|
|
@ -20,50 +20,15 @@ from __future__ import annotations
|
|||
|
||||
from typing import Any
|
||||
|
||||
import musicbrainzngs
|
||||
import requests
|
||||
|
||||
from beets import __version__, ui
|
||||
from beets import ui
|
||||
from beets.plugins import BeetsPlugin
|
||||
|
||||
musicbrainzngs.set_useragent("beets", __version__, "https://beets.io/")
|
||||
from ._utils.musicbrainz import MusicBrainzAPIMixin
|
||||
|
||||
|
||||
def find_parentwork_info(mb_workid: str) -> tuple[dict[str, Any], str | None]:
|
||||
"""Get the MusicBrainz information dict about a parent work, including
|
||||
the artist relations, and the composition date for a work's parent work.
|
||||
"""
|
||||
work_date = None
|
||||
|
||||
parent_id: str | None = mb_workid
|
||||
|
||||
while parent_id:
|
||||
current_id = parent_id
|
||||
work_info = musicbrainzngs.get_work_by_id(
|
||||
current_id, includes=["work-rels", "artist-rels"]
|
||||
)["work"]
|
||||
work_date = work_date or next(
|
||||
(
|
||||
end
|
||||
for a in work_info.get("artist-relation-list", [])
|
||||
if a["type"] == "composer" and (end := a.get("end"))
|
||||
),
|
||||
None,
|
||||
)
|
||||
parent_id = next(
|
||||
(
|
||||
w["work"]["id"]
|
||||
for w in work_info.get("work-relation-list", [])
|
||||
if w["type"] == "parts" and w["direction"] == "backward"
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
return musicbrainzngs.get_work_by_id(
|
||||
current_id, includes=["artist-rels"]
|
||||
), work_date
|
||||
|
||||
|
||||
class ParentWorkPlugin(BeetsPlugin):
|
||||
class ParentWorkPlugin(MusicBrainzAPIMixin, BeetsPlugin):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
|
@ -125,14 +90,13 @@ class ParentWorkPlugin(BeetsPlugin):
|
|||
parentwork_info = {}
|
||||
|
||||
composer_exists = False
|
||||
if "artist-relation-list" in work_info["work"]:
|
||||
for artist in work_info["work"]["artist-relation-list"]:
|
||||
if artist["type"] == "composer":
|
||||
composer_exists = True
|
||||
parent_composer.append(artist["artist"]["name"])
|
||||
parent_composer_sort.append(artist["artist"]["sort-name"])
|
||||
if "end" in artist.keys():
|
||||
parentwork_info["parentwork_date"] = artist["end"]
|
||||
for artist in work_info.get("artist-relations", []):
|
||||
if artist["type"] == "composer":
|
||||
composer_exists = True
|
||||
parent_composer.append(artist["artist"]["name"])
|
||||
parent_composer_sort.append(artist["artist"]["sort-name"])
|
||||
if "end" in artist.keys():
|
||||
parentwork_info["parentwork_date"] = artist["end"]
|
||||
|
||||
parentwork_info["parent_composer"] = ", ".join(parent_composer)
|
||||
parentwork_info["parent_composer_sort"] = ", ".join(
|
||||
|
|
@ -144,16 +108,14 @@ class ParentWorkPlugin(BeetsPlugin):
|
|||
"no composer for {}; add one at "
|
||||
"https://musicbrainz.org/work/{}",
|
||||
item,
|
||||
work_info["work"]["id"],
|
||||
work_info["id"],
|
||||
)
|
||||
|
||||
parentwork_info["parentwork"] = work_info["work"]["title"]
|
||||
parentwork_info["mb_parentworkid"] = work_info["work"]["id"]
|
||||
parentwork_info["parentwork"] = work_info["title"]
|
||||
parentwork_info["mb_parentworkid"] = work_info["id"]
|
||||
|
||||
if "disambiguation" in work_info["work"]:
|
||||
parentwork_info["parentwork_disambig"] = work_info["work"][
|
||||
"disambiguation"
|
||||
]
|
||||
if "disambiguation" in work_info:
|
||||
parentwork_info["parentwork_disambig"] = work_info["disambiguation"]
|
||||
|
||||
else:
|
||||
parentwork_info["parentwork_disambig"] = None
|
||||
|
|
@ -185,9 +147,9 @@ class ParentWorkPlugin(BeetsPlugin):
|
|||
work_changed = item.parentwork_workid_current != item.mb_workid
|
||||
if force or not hasparent or work_changed:
|
||||
try:
|
||||
work_info, work_date = find_parentwork_info(item.mb_workid)
|
||||
except musicbrainzngs.musicbrainz.WebServiceError as e:
|
||||
self._log.debug("error fetching work: {}", e)
|
||||
work_info, work_date = self.find_parentwork_info(item.mb_workid)
|
||||
except requests.exceptions.RequestException:
|
||||
self._log.debug("error fetching work", item, exc_info=True)
|
||||
return
|
||||
parent_info = self.get_info(item, work_info)
|
||||
parent_info["parentwork_workid_current"] = item.mb_workid
|
||||
|
|
@ -228,3 +190,37 @@ class ParentWorkPlugin(BeetsPlugin):
|
|||
"parentwork_date",
|
||||
],
|
||||
)
|
||||
|
||||
def find_parentwork_info(
|
||||
self, mb_workid: str
|
||||
) -> tuple[dict[str, Any], str | None]:
|
||||
"""Get the MusicBrainz information dict about a parent work, including
|
||||
the artist relations, and the composition date for a work's parent work.
|
||||
"""
|
||||
work_date = None
|
||||
|
||||
parent_id: str | None = mb_workid
|
||||
|
||||
while parent_id:
|
||||
current_id = parent_id
|
||||
work_info = self.mb_api.get_work(
|
||||
current_id, includes=["work-rels", "artist-rels"]
|
||||
)
|
||||
work_date = work_date or next(
|
||||
(
|
||||
end
|
||||
for a in work_info.get("artist-relations", [])
|
||||
if a["type"] == "composer" and (end := a.get("end"))
|
||||
),
|
||||
None,
|
||||
)
|
||||
parent_id = next(
|
||||
(
|
||||
w["work"]["id"]
|
||||
for w in work_info.get("work-relations", [])
|
||||
if w["type"] == "parts" and w["direction"] == "backward"
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
return work_info, work_date
|
||||
|
|
|
|||
|
|
@ -38,16 +38,6 @@ This plugin adds seven tags:
|
|||
to keep track of recordings whose works have changed.
|
||||
- **parentwork_date**: The composition date of the parent work.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
To use the ``parentwork`` plugin, first enable it in your configuration (see
|
||||
:ref:`using-plugins`). Then, install ``beets`` with ``parentwork`` extra
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install "beets[parentwork]"
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
|
|
|
|||
3
poetry.lock
generated
3
poetry.lock
generated
|
|
@ -4185,7 +4185,6 @@ mbcollection = ["musicbrainzngs"]
|
|||
metasync = ["dbus-python"]
|
||||
missing = ["musicbrainzngs"]
|
||||
mpdstats = ["python-mpd2"]
|
||||
parentwork = ["musicbrainzngs"]
|
||||
plexupdate = ["requests"]
|
||||
reflink = ["reflink"]
|
||||
replaygain = ["PyGObject"]
|
||||
|
|
@ -4198,4 +4197,4 @@ web = ["flask", "flask-cors"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.10,<4"
|
||||
content-hash = "d9141a482e4990a4466a121a59deaeaf46e5613ff0af315f277110935e391e63"
|
||||
content-hash = "dbe3785cbffd71f2ca758872f7654522228d6155c76a8f003bec22f03c8eada3"
|
||||
|
|
|
|||
|
|
@ -168,7 +168,6 @@ mbcollection = ["musicbrainzngs"]
|
|||
metasync = ["dbus-python"]
|
||||
missing = ["musicbrainzngs"]
|
||||
mpdstats = ["python-mpd2"]
|
||||
parentwork = ["musicbrainzngs"]
|
||||
plexupdate = ["requests"]
|
||||
reflink = ["reflink"]
|
||||
replaygain = [
|
||||
|
|
|
|||
|
|
@ -14,9 +14,6 @@
|
|||
|
||||
"""Tests for the 'parentwork' plugin."""
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from beets.library import Item
|
||||
|
|
@ -74,56 +71,56 @@ class ParentWorkIntegrationTest(PluginTestCase):
|
|||
assert item["mb_parentworkid"] == "XXX"
|
||||
|
||||
|
||||
def mock_workid_response(mbid, includes):
|
||||
works: list[dict[str, Any]] = [
|
||||
{
|
||||
"id": "1",
|
||||
"title": "work",
|
||||
"work-relation-list": [
|
||||
{
|
||||
"type": "parts",
|
||||
"direction": "backward",
|
||||
"work": {"id": "2"},
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"title": "directparentwork",
|
||||
"work-relation-list": [
|
||||
{
|
||||
"type": "parts",
|
||||
"direction": "backward",
|
||||
"work": {"id": "3"},
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"title": "parentwork",
|
||||
},
|
||||
]
|
||||
|
||||
return {
|
||||
"work": {
|
||||
**next(w for w in works if mbid == w["id"]),
|
||||
"artist-relation-list": [
|
||||
{
|
||||
"type": "composer",
|
||||
"artist": {
|
||||
"name": "random composer",
|
||||
"sort-name": "composer, random",
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@patch("musicbrainzngs.get_work_by_id", Mock(side_effect=mock_workid_response))
|
||||
class ParentWorkTest(PluginTestCase):
|
||||
plugin = "parentwork"
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patch_works(self, requests_mock):
|
||||
requests_mock.get(
|
||||
"/ws/2/work/1?inc=work-rels%2Bartist-rels",
|
||||
json={
|
||||
"id": "1",
|
||||
"title": "work",
|
||||
"work-relations": [
|
||||
{
|
||||
"type": "parts",
|
||||
"direction": "backward",
|
||||
"work": {"id": "2"},
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
requests_mock.get(
|
||||
"/ws/2/work/2?inc=work-rels%2Bartist-rels",
|
||||
json={
|
||||
"id": "2",
|
||||
"title": "directparentwork",
|
||||
"work-relations": [
|
||||
{
|
||||
"type": "parts",
|
||||
"direction": "backward",
|
||||
"work": {"id": "3"},
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
requests_mock.get(
|
||||
"/ws/2/work/3?inc=work-rels%2Bartist-rels",
|
||||
json={
|
||||
"id": "3",
|
||||
"title": "parentwork",
|
||||
"artist-relations": [
|
||||
{
|
||||
"type": "composer",
|
||||
"artist": {
|
||||
"name": "random composer",
|
||||
"sort-name": "composer, random",
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
def test_normal_case(self):
|
||||
item = Item(path="/file", mb_workid="1", parentwork_workid_current="1")
|
||||
item.add(self.lib)
|
||||
|
|
|
|||
Loading…
Reference in a new issue