resolve transl-tracklisting relations for pseudo releases

This commit is contained in:
fence 2023-03-15 22:30:42 +01:00
parent 79435c3b6f
commit e992b82bdf
3 changed files with 233 additions and 2 deletions

View file

@ -73,7 +73,7 @@ log = logging.getLogger('beets')
RELEASE_INCLUDES = ['artists', 'media', 'recordings', 'release-groups',
'labels', 'artist-credits', 'aliases',
'recording-level-rels', 'work-rels',
'work-level-rels', 'artist-rels', 'isrcs', 'url-rels']
'work-level-rels', 'artist-rels', 'isrcs', 'url-rels', 'release-rels']
BROWSE_INCLUDES = ['artist-credits', 'work-rels',
'artist-rels', 'recording-rels', 'release-rels']
if "work-level-rels" in musicbrainzngs.VALID_BROWSE_INCLUDES['recording']:
@ -670,6 +670,61 @@ def _parse_id(s: str) -> Optional[str]:
return None
# this was defined within the function below but pep8 made me move it here
_trans_key = 'transl-tracklisting'
_is_trans = lambda r: r['type'] == _trans_key and r['direction'] == "backward"
def _find_actual_release_from_pseudo_release(pseudo_rel: Dict)\
-> Optional[Dict]:
relations = pseudo_rel['release']["release-relation-list"]
# currently we only support trans(liter)ation's
actual_id = next(filter(_is_trans, relations), {'target': None})['target']
if actual_id is None:
return None
return musicbrainzngs.get_release_by_id(actual_id,
RELEASE_INCLUDES)
def _merge_pseudo_and_actual_album(
pseudo: beets.autotag.hooks.AlbumInfo,
actual: beets.autotag.hooks.AlbumInfo
) -> Optional[beets.autotag.hooks.AlbumInfo]:
"""
Merges a pseudo release with its actual release.
This implementation is naive, it doesn't overwrite fields,
like status or ids.
According to the ticket PICARD-145, the main release id should be used.
But the ticket has been in limbo since over a decade now.
It also suggests the introduction of the tag `musicbrainz_pseudoreleaseid`,
but as of this field can't be found in any offical Picard docs,
hence why we did not implement that for now.
"""
merged = pseudo.copy()
merged.update({
"media": actual.media,
"mediums": actual.mediums,
"country": actual.country,
"catalognum": actual.catalognum,
"year": actual.year,
"month": actual.month,
"day": actual.day,
"original_year": actual.original_year,
"original_month": actual.original_month,
"original_day": actual.original_day,
"label": actual.label,
"asin": actual.asin,
"style": actual.style,
"genre": actual.genre,
})
return merged
def album_for_id(releaseid: str) -> Optional[beets.autotag.hooks.AlbumInfo]:
"""Fetches an album by its MusicBrainz ID and returns an AlbumInfo
object or None if the album is not found. May raise a
@ -683,13 +738,27 @@ def album_for_id(releaseid: str) -> Optional[beets.autotag.hooks.AlbumInfo]:
try:
res = musicbrainzngs.get_release_by_id(albumid,
RELEASE_INCLUDES)
# resolve linked release relations
actual_res = None
if res['release']['status'] == 'Pseudo-Release':
actual_res = _find_actual_release_from_pseudo_release(res)
except musicbrainzngs.ResponseError:
log.debug('Album ID match failed.')
return None
except musicbrainzngs.MusicBrainzError as exc:
raise MusicBrainzAPIError(exc, 'get release by ID', albumid,
traceback.format_exc())
return album_info(res['release'])
release = album_info(res['release'])
if actual_res is not None:
actual_release = album_info(actual_res['release'])
return _merge_pseudo_and_actual_album(release, actual_release)
else:
return release
def track_for_id(releaseid: str) -> Optional[beets.autotag.hooks.TrackInfo]:

View file

@ -211,6 +211,9 @@ def disambig_string(info):
disambig.append(info.catalognum)
if info.albumdisambig:
disambig.append(info.albumdisambig)
# pseudo releases can't be differentiated from real release otherwise
if info.albumstatus == 'Pseudo-Release':
disambig.append(info.albumstatus)
if disambig:
return ', '.join(disambig)

View file

@ -604,6 +604,7 @@ class MBLibraryTest(unittest.TestCase):
'release': {
'title': 'hi',
'id': mbid,
'status': 'status',
'medium-list': [{
'track-list': [{
'id': 'baz',
@ -648,6 +649,164 @@ class MBLibraryTest(unittest.TestCase):
self.assertFalse(p.called)
self.assertEqual(ail, [])
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'
}
]
}
},
{
'release': {
'title': 'actual',
'id': 'd2a6f856-b553-40a0-ac54-a321e8e2da01',
'status': 'Offical',
'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',
}
}
]
with mock.patch('musicbrainzngs.get_release_by_id') as gp:
gp.side_effect = side_effect
album = mb.album_for_id('d2a6f856-b553-40a0-ac54-a321e8e2da02')
self.assertEqual(album.country, 'COUNTRY')
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',
},
'release-relation-list': []
}
},
]
with mock.patch('musicbrainzngs.get_release_by_id') as gp:
gp.side_effect = side_effect
album = mb.album_for_id('d2a6f856-b553-40a0-ac54-a321e8e2da02')
self.assertEqual(album.country, None)
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'
}
]
}
},
]
with mock.patch('musicbrainzngs.get_release_by_id') as gp:
gp.side_effect = side_effect
album = mb.album_for_id('d2a6f856-b553-40a0-ac54-a321e8e2da02')
self.assertEqual(album.country, None)
def suite():
return unittest.TestLoader().loadTestsFromName(__name__)