musicbrainz: update patches

This commit is contained in:
Šarūnas Nejus 2025-02-17 23:29:36 +00:00
parent bef0bcbaa6
commit 0980c82959
No known key found for this signature in database
GPG key ID: DD28F6704DBE3435
3 changed files with 88 additions and 172 deletions

View file

@ -49,7 +49,7 @@ from mediafile import Image, MediaFile
import beets import beets
import beets.plugins import beets.plugins
from beets import autotag, importer, logging, util from beets import importer, logging, util
from beets.autotag.hooks import AlbumInfo, TrackInfo from beets.autotag.hooks import AlbumInfo, TrackInfo
from beets.importer import ImportSession from beets.importer import ImportSession
from beets.library import Album, Item, Library from beets.library import Album, Item, Library
@ -791,40 +791,37 @@ class AutotagStub:
length = 2 length = 2
def install(self): def install(self):
self.mb_match_album = autotag.mb.match_album self.patchers = [
self.mb_match_track = autotag.mb.match_track patch("beets.plugins.album_for_id", lambda *_: None),
self.mb_album_for_id = autotag.mb.album_for_id patch("beets.plugins.track_for_id", lambda *_: None),
self.mb_track_for_id = autotag.mb.track_for_id patch("beets.plugins.candidates", self.candidates),
patch("beets.plugins.item_candidates", self.item_candidates),
autotag.mb.match_album = self.match_album ]
autotag.mb.match_track = self.match_track for p in self.patchers:
autotag.mb.album_for_id = self.album_for_id p.start()
autotag.mb.track_for_id = self.track_for_id
return self return self
def restore(self): def restore(self):
autotag.mb.match_album = self.mb_match_album for p in self.patchers:
autotag.mb.match_track = self.mb_match_track p.stop()
autotag.mb.album_for_id = self.mb_album_for_id
autotag.mb.track_for_id = self.mb_track_for_id
def match_album(self, albumartist, album, tracks, extra_tags): def candidates(self, items, artist, album, va_likely, extra_tags=None):
if self.matching == self.IDENT: if self.matching == self.IDENT:
yield self._make_album_match(albumartist, album, tracks) yield self._make_album_match(artist, album, len(items))
elif self.matching == self.GOOD: elif self.matching == self.GOOD:
for i in range(self.length): for i in range(self.length):
yield self._make_album_match(albumartist, album, tracks, i) yield self._make_album_match(artist, album, len(items), i)
elif self.matching == self.BAD: elif self.matching == self.BAD:
for i in range(self.length): for i in range(self.length):
yield self._make_album_match(albumartist, album, tracks, i + 1) yield self._make_album_match(artist, album, len(items), i + 1)
elif self.matching == self.MISSING: elif self.matching == self.MISSING:
yield self._make_album_match(albumartist, album, tracks, missing=1) yield self._make_album_match(artist, album, len(items), missing=1)
def match_track(self, artist, title): def item_candidates(self, item, artist, title):
yield TrackInfo( yield TrackInfo(
title=title.replace("Tag", "Applied"), title=title.replace("Tag", "Applied"),
track_id="trackid", track_id="trackid",
@ -834,12 +831,6 @@ class AutotagStub:
index=0, index=0,
) )
def album_for_id(self, mbid):
return None
def track_for_id(self, mbid):
return None
def _make_track_match(self, artist, album, number): def _make_track_match(self, artist, album, number):
return TrackInfo( return TrackInfo(
title="Applied Track %d" % number, title="Applied Track %d" % number,

View file

@ -363,7 +363,7 @@ class ChangeRepresentation:
self.indent_header + f"Match ({dist_string(self.match.distance)}):" self.indent_header + f"Match ({dist_string(self.match.distance)}):"
) )
if self.match.info.get("album"): if isinstance(self.match.info, autotag.hooks.AlbumInfo):
# Matching an album - print that # Matching an album - print that
artist_album_str = ( artist_album_str = (
f"{self.match.info.artist}" + f" - {self.match.info.album}" f"{self.match.info.artist}" + f" - {self.match.info.album}"

View file

@ -1061,26 +1061,22 @@ class InferAlbumDataTest(BeetsTestCase):
assert not self.items[0].comp assert not self.items[0].comp
def match_album_mock(*args, **kwargs): def album_candidates_mock(*args, **kwargs):
"""Create an AlbumInfo object for testing.""" """Create an AlbumInfo object for testing."""
track_info = TrackInfo( yield AlbumInfo(
title="new title",
track_id="trackid",
index=0,
)
album_info = AlbumInfo(
artist="artist", artist="artist",
album="album", album="album",
tracks=[track_info], tracks=[TrackInfo(title="new title", track_id="trackid", index=0)],
album_id="albumid", album_id="albumid",
artist_id="artistid", artist_id="artistid",
flex="flex", flex="flex",
) )
return iter([album_info])
@patch("beets.autotag.mb.match_album", Mock(side_effect=match_album_mock)) @patch("beets.plugins.candidates", Mock(side_effect=album_candidates_mock))
class ImportDuplicateAlbumTest(ImportTestCase): class ImportDuplicateAlbumTest(PluginMixin, ImportTestCase):
plugin = "musicbrainz"
def setUp(self): def setUp(self):
super().setUp() super().setUp()
@ -1186,20 +1182,16 @@ class ImportDuplicateAlbumTest(ImportTestCase):
return album return album
def match_track_mock(*args, **kwargs): def item_candidates_mock(*args, **kwargs):
return iter( yield TrackInfo(
[
TrackInfo(
artist="artist", artist="artist",
title="title", title="title",
track_id="new trackid", track_id="new trackid",
index=0, index=0,
) )
]
)
@patch("beets.autotag.mb.match_track", Mock(side_effect=match_track_mock)) @patch("beets.plugins.item_candidates", Mock(side_effect=item_candidates_mock))
class ImportDuplicateSingletonTest(ImportTestCase): class ImportDuplicateSingletonTest(ImportTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
@ -1633,7 +1625,7 @@ class ReimportTest(AutotagImportTestCase):
def test_reimported_album_not_preserves_flexattr(self): def test_reimported_album_not_preserves_flexattr(self):
self._setup_session() self._setup_session()
assert self._album().data_source == "original_source"
self.importer.run() self.importer.run()
assert self._album().data_source == "match_source" assert self._album().data_source == "match_source"
@ -1657,6 +1649,7 @@ class ImportPretendTest(AutotagImportTestCase):
assert len(self.lib.albums()) == 0 assert len(self.lib.albums()) == 0
return [line for line in logs if not line.startswith("Sending event:")] return [line for line in logs if not line.startswith("Sending event:")]
assert self._album().data_source == "original_source"
def test_import_singletons_pretend(self): def test_import_singletons_pretend(self):
assert self.__run(self.setup_singleton_importer(pretend=True)) == [ assert self.__run(self.setup_singleton_importer(pretend=True)) == [
@ -1681,112 +1674,64 @@ class ImportPretendTest(AutotagImportTestCase):
assert self.__run(importer) == [f"No files imported from {empty_path}"] assert self.__run(importer) == [f"No files imported from {empty_path}"]
# Helpers for ImportMusicBrainzIdTest. def mocked_get_album_by_id(id_):
"""Return album candidate for the given id.
The two albums differ only in the release title and artist name, so that
def mocked_get_release_by_id( ID_RELEASE_0 is a closer match to the items created by
id_, includes=[], release_status=[], release_type=[] ImportHelper.prepare_album_for_import().
): """
"""Mimic musicbrainzngs.get_release_by_id, accepting only a restricted list
of MB ids (ID_RELEASE_0, ID_RELEASE_1). The returned dict differs only in
the release title and artist name, so that ID_RELEASE_0 is a closer match
to the items created by ImportHelper.prepare_album_for_import()."""
# Map IDs to (release title, artist), so the distances are different. # Map IDs to (release title, artist), so the distances are different.
releases = { album, artist = {
ImportMusicBrainzIdTest.ID_RELEASE_0: ("VALID_RELEASE_0", "TAG ARTIST"), ImportIdTest.ID_RELEASE_0: ("VALID_RELEASE_0", "TAG ARTIST"),
ImportMusicBrainzIdTest.ID_RELEASE_1: ( ImportIdTest.ID_RELEASE_1: ("VALID_RELEASE_1", "DISTANT_MATCH"),
"VALID_RELEASE_1", }[id_]
"DISTANT_MATCH",
),
}
return { return AlbumInfo(
"release": { album_id=id_,
"title": releases[id_][0], album=album,
"id": id_, artist_id="some-id",
"medium-list": [ artist=artist,
{ albumstatus="Official",
"track-list": [ tracks=[
{ TrackInfo(
"id": "baz", track_id="bar",
"recording": { title="foo",
"title": "foo", artist_id="some-id",
"id": "bar", artist=artist,
"length": 59, length=59,
}, index=9,
"position": 9, track_allt="A2",
"number": "A2", )
}
], ],
"position": 5, )
}
],
"artist-credit": [
{
"artist": {
"name": releases[id_][1],
"id": "some-id",
},
}
],
"release-group": {
"id": "another-id",
},
"status": "Official",
}
}
def mocked_get_recording_by_id( def mocked_get_track_by_id(id_):
id_, includes=[], release_status=[], release_type=[] """Return track candidate for the given id.
):
"""Mimic musicbrainzngs.get_recording_by_id, accepting only a restricted The two tracks differ only in the release title and artist name, so that
list of MB ids (ID_RECORDING_0, ID_RECORDING_1). The returned dict differs ID_RELEASE_0 is a closer match to the items created by
only in the recording title and artist name, so that ID_RECORDING_0 is a ImportHelper.prepare_album_for_import().
closer match to the items created by ImportHelper.prepare_album_for_import().
""" """
# Map IDs to (recording title, artist), so the distances are different. # Map IDs to (recording title, artist), so the distances are different.
releases = { title, artist = {
ImportMusicBrainzIdTest.ID_RECORDING_0: ( ImportIdTest.ID_RECORDING_0: ("VALID_RECORDING_0", "TAG ARTIST"),
"VALID_RECORDING_0", ImportIdTest.ID_RECORDING_1: ("VALID_RECORDING_1", "DISTANT_MATCH"),
"TAG ARTIST", }[id_]
),
ImportMusicBrainzIdTest.ID_RECORDING_1: (
"VALID_RECORDING_1",
"DISTANT_MATCH",
),
}
return { return TrackInfo(
"recording": { track_id=id_,
"title": releases[id_][0], title=title,
"id": id_, artist_id="some-id",
"length": 59, artist=artist,
"artist-credit": [ length=59,
{ )
"artist": {
"name": releases[id_][1],
"id": "some-id",
},
}
],
}
}
@patch( @patch("beets.plugins.track_for_id", Mock(side_effect=mocked_get_track_by_id))
"musicbrainzngs.get_recording_by_id", @patch("beets.plugins.album_for_id", Mock(side_effect=mocked_get_album_by_id))
Mock(side_effect=mocked_get_recording_by_id), class ImportIdTest(ImportTestCase):
)
@patch(
"musicbrainzngs.get_release_by_id",
Mock(side_effect=mocked_get_release_by_id),
)
class ImportMusicBrainzIdTest(ImportTestCase):
"""Test the --musicbrainzid argument."""
MB_RELEASE_PREFIX = "https://musicbrainz.org/release/"
MB_RECORDING_PREFIX = "https://musicbrainz.org/recording/"
ID_RELEASE_0 = "00000000-0000-0000-0000-000000000000" ID_RELEASE_0 = "00000000-0000-0000-0000-000000000000"
ID_RELEASE_1 = "11111111-1111-1111-1111-111111111111" ID_RELEASE_1 = "11111111-1111-1111-1111-111111111111"
ID_RECORDING_0 = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" ID_RECORDING_0 = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
@ -1797,21 +1742,14 @@ class ImportMusicBrainzIdTest(ImportTestCase):
self.prepare_album_for_import(1) self.prepare_album_for_import(1)
def test_one_mbid_one_album(self): def test_one_mbid_one_album(self):
self.setup_importer( self.setup_importer(search_ids=[self.ID_RELEASE_0])
search_ids=[self.MB_RELEASE_PREFIX + self.ID_RELEASE_0]
)
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.action.APPLY)
self.importer.run() self.importer.run()
assert self.lib.albums().get().album == "VALID_RELEASE_0" assert self.lib.albums().get().album == "VALID_RELEASE_0"
def test_several_mbid_one_album(self): def test_several_mbid_one_album(self):
self.setup_importer( self.setup_importer(search_ids=[self.ID_RELEASE_0, self.ID_RELEASE_1])
search_ids=[
self.MB_RELEASE_PREFIX + self.ID_RELEASE_0,
self.MB_RELEASE_PREFIX + self.ID_RELEASE_1,
]
)
self.importer.add_choice(2) # Pick the 2nd best match (release 1). self.importer.add_choice(2) # Pick the 2nd best match (release 1).
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.action.APPLY)
@ -1819,9 +1757,7 @@ class ImportMusicBrainzIdTest(ImportTestCase):
assert self.lib.albums().get().album == "VALID_RELEASE_1" assert self.lib.albums().get().album == "VALID_RELEASE_1"
def test_one_mbid_one_singleton(self): def test_one_mbid_one_singleton(self):
self.setup_singleton_importer( self.setup_singleton_importer(search_ids=[self.ID_RECORDING_0])
search_ids=[self.MB_RECORDING_PREFIX + self.ID_RECORDING_0]
)
self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.action.APPLY)
self.importer.run() self.importer.run()
@ -1829,10 +1765,7 @@ class ImportMusicBrainzIdTest(ImportTestCase):
def test_several_mbid_one_singleton(self): def test_several_mbid_one_singleton(self):
self.setup_singleton_importer( self.setup_singleton_importer(
search_ids=[ search_ids=[self.ID_RECORDING_0, self.ID_RECORDING_1]
self.MB_RECORDING_PREFIX + self.ID_RECORDING_0,
self.MB_RECORDING_PREFIX + self.ID_RECORDING_1,
]
) )
self.importer.add_choice(2) # Pick the 2nd best match (recording 1). self.importer.add_choice(2) # Pick the 2nd best match (recording 1).
@ -1845,11 +1778,7 @@ class ImportMusicBrainzIdTest(ImportTestCase):
task = importer.ImportTask( task = importer.ImportTask(
paths=self.import_dir, toppath="top path", items=[_common.item()] paths=self.import_dir, toppath="top path", items=[_common.item()]
) )
task.search_ids = [ task.search_ids = [self.ID_RELEASE_0, self.ID_RELEASE_1]
self.MB_RELEASE_PREFIX + self.ID_RELEASE_0,
self.MB_RELEASE_PREFIX + self.ID_RELEASE_1,
"an invalid and discarded id",
]
task.lookup_candidates() task.lookup_candidates()
assert {"VALID_RELEASE_0", "VALID_RELEASE_1"} == { assert {"VALID_RELEASE_0", "VALID_RELEASE_1"} == {
@ -1861,11 +1790,7 @@ class ImportMusicBrainzIdTest(ImportTestCase):
task = importer.SingletonImportTask( task = importer.SingletonImportTask(
toppath="top path", item=_common.item() toppath="top path", item=_common.item()
) )
task.search_ids = [ task.search_ids = [self.ID_RECORDING_0, self.ID_RECORDING_1]
self.MB_RECORDING_PREFIX + self.ID_RECORDING_0,
self.MB_RECORDING_PREFIX + self.ID_RECORDING_1,
"an invalid and discarded id",
]
task.lookup_candidates() task.lookup_candidates()
assert {"VALID_RECORDING_0", "VALID_RECORDING_1"} == { assert {"VALID_RECORDING_0", "VALID_RECORDING_1"} == {