Merge pull request #3568 from dosoe/beet_test_new_albuminfo

First try adding new albuminfo and trackinfo class
This commit is contained in:
Adrian Sampson 2020-05-09 10:53:43 -04:00 committed by GitHub
commit a907dac16c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 121 additions and 148 deletions

View file

@ -39,8 +39,25 @@ except AttributeError:
# Classes used to represent candidate options.
class AttrDict(dict):
"""A dictionary that supports attribute ("dot") access, so `d.field`
is equivalent to `d['field']`.
"""
class AlbumInfo(object):
def __getattr__(self, attr):
if attr in self:
return self.get(attr)
else:
raise AttributeError
def __setattr__(self, key, value):
self.__setitem__(key, value)
def __hash__(self):
return id(self)
class AlbumInfo(AttrDict):
"""Describes a canonical release that may be used to match a release
in the library. Consists of these data members:
@ -49,43 +66,21 @@ class AlbumInfo(object):
- ``artist``: name of the release's primary artist
- ``artist_id``
- ``tracks``: list of TrackInfo objects making up the release
- ``asin``: Amazon ASIN
- ``albumtype``: string describing the kind of release
- ``va``: boolean: whether the release has "various artists"
- ``year``: release year
- ``month``: release month
- ``day``: release day
- ``label``: music label responsible for the release
- ``mediums``: the number of discs in this release
- ``artist_sort``: name of the release's artist for sorting
- ``releasegroup_id``: MBID for the album's release group
- ``catalognum``: the label's catalog number for the release
- ``script``: character set used for metadata
- ``language``: human language of the metadata
- ``country``: the release country
- ``albumstatus``: MusicBrainz release status (Official, etc.)
- ``media``: delivery mechanism (Vinyl, etc.)
- ``albumdisambig``: MusicBrainz release disambiguation comment
- ``releasegroupdisambig``: MusicBrainz release group
disambiguation comment.
- ``artist_credit``: Release-specific artist name
- ``data_source``: The original data source (MusicBrainz, Discogs, etc.)
- ``data_url``: The data source release URL.
``mediums`` along with the fields up through ``tracks`` are required.
The others are optional and may be None.
"""
def __init__(self, album, album_id, artist, artist_id, tracks, asin=None,
albumtype=None, va=False, year=None, month=None, day=None,
label=None, mediums=None, artist_sort=None,
releasegroup_id=None, catalognum=None, script=None,
language=None, country=None, style=None, genre=None,
albumstatus=None, media=None, albumdisambig=None,
def __init__(self, tracks, album=None, album_id=None, artist=None,
artist_id=None, asin=None, albumtype=None, va=False,
year=None, month=None, day=None, label=None, mediums=None,
artist_sort=None, releasegroup_id=None, catalognum=None,
script=None, language=None, country=None, style=None,
genre=None, albumstatus=None, media=None, albumdisambig=None,
releasegroupdisambig=None, artist_credit=None,
original_year=None, original_month=None,
original_day=None, data_source=None, data_url=None,
discogs_albumid=None, discogs_labelid=None,
discogs_artistid=None):
discogs_artistid=None, **kwargs):
self.album = album
self.album_id = album_id
self.artist = artist
@ -120,6 +115,7 @@ class AlbumInfo(object):
self.discogs_albumid = discogs_albumid
self.discogs_labelid = discogs_labelid
self.discogs_artistid = discogs_artistid
self.update(kwargs)
# Work around a bug in python-musicbrainz-ngs that causes some
# strings to be bytes rather than Unicode.
@ -138,53 +134,36 @@ class AlbumInfo(object):
if isinstance(value, bytes):
setattr(self, fld, value.decode(codec, 'ignore'))
if self.tracks:
for track in self.tracks:
track.decode(codec)
for track in self.tracks:
track.decode(codec)
def copy(self):
dupe = AlbumInfo([])
dupe.update(self)
dupe.tracks = [track.copy() for track in self.tracks]
return dupe
class TrackInfo(object):
class TrackInfo(AttrDict):
"""Describes a canonical track present on a release. Appears as part
of an AlbumInfo's ``tracks`` list. Consists of these data members:
- ``title``: name of the track
- ``track_id``: MusicBrainz ID; UUID fragment only
- ``release_track_id``: MusicBrainz ID respective to a track on a
particular release; UUID fragment only
- ``artist``: individual track artist name
- ``artist_id``
- ``length``: float: duration of the track in seconds
- ``index``: position on the entire release
- ``media``: delivery mechanism (Vinyl, etc.)
- ``medium``: the disc number this track appears on in the album
- ``medium_index``: the track's position on the disc
- ``medium_total``: the number of tracks on the item's disc
- ``artist_sort``: name of the track artist for sorting
- ``disctitle``: name of the individual medium (subtitle)
- ``artist_credit``: Recording-specific artist name
- ``data_source``: The original data source (MusicBrainz, Discogs, etc.)
- ``data_url``: The data source release URL.
- ``lyricist``: individual track lyricist name
- ``composer``: individual track composer name
- ``composer_sort``: individual track composer sort name
- ``arranger`: individual track arranger name
- ``track_alt``: alternative track number (tape, vinyl, etc.)
- ``work`: individual track work title
- ``mb_workid`: individual track work id
- ``work_disambig`: individual track work diambiguation
Only ``title`` and ``track_id`` are required. The rest of the fields
may be None. The indices ``index``, ``medium``, and ``medium_index``
are all 1-based.
"""
def __init__(self, title, track_id, release_track_id=None, artist=None,
artist_id=None, length=None, index=None, medium=None,
medium_index=None, medium_total=None, artist_sort=None,
disctitle=None, artist_credit=None, data_source=None,
data_url=None, media=None, lyricist=None, composer=None,
composer_sort=None, arranger=None, track_alt=None,
work=None, mb_workid=None, work_disambig=None, bpm=None,
initial_key=None, genre=None):
def __init__(self, title=None, track_id=None, release_track_id=None,
artist=None, artist_id=None, length=None, index=None,
medium=None, medium_index=None, medium_total=None,
artist_sort=None, disctitle=None, artist_credit=None,
data_source=None, data_url=None, media=None, lyricist=None,
composer=None, composer_sort=None, arranger=None,
track_alt=None, work=None, mb_workid=None,
work_disambig=None, bpm=None, initial_key=None, genre=None,
**kwargs):
self.title = title
self.track_id = track_id
self.release_track_id = release_track_id
@ -212,6 +191,7 @@ class TrackInfo(object):
self.bpm = bpm
self.initial_key = initial_key
self.genre = genre
self.update(kwargs)
# As above, work around a bug in python-musicbrainz-ngs.
def decode(self, codec='utf-8'):
@ -224,6 +204,11 @@ class TrackInfo(object):
if isinstance(value, bytes):
setattr(self, fld, value.decode(codec, 'ignore'))
def copy(self):
dupe = TrackInfo()
dupe.update(self)
return dupe
# Candidate distance scoring.

View file

@ -193,8 +193,8 @@ def track_info(recording, index=None, medium=None, medium_index=None,
the number of tracks on the medium. Each number is a 1-based index.
"""
info = beets.autotag.hooks.TrackInfo(
recording['title'],
recording['id'],
title=recording['title'],
track_id=recording['id'],
index=index,
medium=medium,
medium_index=medium_index,
@ -341,11 +341,11 @@ def album_info(release):
track_infos.append(ti)
info = beets.autotag.hooks.AlbumInfo(
release['title'],
release['id'],
artist_name,
release['artist-credit'][0]['artist']['id'],
track_infos,
album=release['title'],
album_id=release['id'],
artist=artist_name,
artist_id=release['artist-credit'][0]['artist']['id'],
tracks=track_infos,
mediums=len(release['medium-list']),
artist_sort=artist_sort_name,
artist_credit=artist_credit_name,

View file

@ -53,5 +53,6 @@ class CuePlugin(BeetsPlugin):
title = "dunno lol"
track_id = "wtf"
index = int(path.basename(t)[len("split-track"):-len(".wav")])
yield TrackInfo(title, track_id, index=index, artist=artist)
yield TrackInfo(title=title, track_id=track_id, index=index,
artist=artist)
# generate TrackInfo instances

View file

@ -356,17 +356,14 @@ class DiscogsPlugin(BeetsPlugin):
# a master release, otherwise fetch the master release.
original_year = self.get_master_year(master_id) if master_id else year
return AlbumInfo(album, album_id, artist, artist_id, tracks, asin=None,
albumtype=albumtype, va=va, year=year, month=None,
day=None, label=label, mediums=len(set(mediums)),
artist_sort=None, releasegroup_id=master_id,
catalognum=catalogno, script=None, language=None,
return AlbumInfo(album=album, album_id=album_id, artist=artist,
artist_id=artist_id, tracks=tracks,
albumtype=albumtype, va=va, year=year,
label=label, mediums=len(set(mediums)),
releasegroup_id=master_id, catalognum=catalogno,
country=country, style=style, genre=genre,
albumstatus=None, media=media,
albumdisambig=None, artist_credit=None,
original_year=original_year, original_month=None,
original_day=None, data_source='Discogs',
data_url=data_url,
media=media, original_year=original_year,
data_source='Discogs', data_url=data_url,
discogs_albumid=discogs_albumid,
discogs_labelid=labelid, discogs_artistid=artist_id)
@ -567,10 +564,9 @@ class DiscogsPlugin(BeetsPlugin):
track.get('artists', [])
)
length = self.get_track_length(track['duration'])
return TrackInfo(title, track_id, artist=artist, artist_id=artist_id,
length=length, index=index,
medium=medium, medium_index=medium_index,
artist_sort=None, disctitle=None, artist_credit=None)
return TrackInfo(title=title, track_id=track_id, artist=artist,
artist_id=artist_id, length=length, index=index,
medium=medium, medium_index=medium_index)
def get_track_index(self, position):
"""Returns the medium, medium index and subtrack index for a discogs

View file

@ -116,6 +116,9 @@ New features:
:bug:`3459`
* :doc:`/plugins/fetchart`: Album art can now be fetched from `last.fm`_.
:bug:`3530`
* The classes ``AlbumInfo`` and ``TrackInfo`` now have flexible attributes,
allowing to solve :bug:`1547`.
Thanks to :user:`dosoe`.
* :doc:`/plugins/web`: The query API now interprets backslashes as path
separators to support path queries.
Thanks to :user:`nmeum`.

View file

@ -18,7 +18,6 @@
from __future__ import division, absolute_import, print_function
import re
import copy
import unittest
from test import _common
@ -106,9 +105,12 @@ def _make_item(title, track, artist=u'some artist'):
def _make_trackinfo():
return [
TrackInfo(u'one', None, artist=u'some artist', length=1, index=1),
TrackInfo(u'two', None, artist=u'some artist', length=1, index=2),
TrackInfo(u'three', None, artist=u'some artist', length=1, index=3),
TrackInfo(title=u'one', track_id=None, artist=u'some artist',
length=1, index=1),
TrackInfo(title=u'two', track_id=None, artist=u'some artist',
length=1, index=2),
TrackInfo(title=u'three', track_id=None, artist=u'some artist',
length=1, index=3),
]
@ -348,9 +350,7 @@ class AlbumDistanceTest(_common.TestCase):
artist=u'some artist',
album=u'some album',
tracks=_make_trackinfo(),
va=False,
album_id=None,
artist_id=None,
va=False
)
self.assertEqual(self._dist(items, info), 0)
@ -362,9 +362,7 @@ class AlbumDistanceTest(_common.TestCase):
artist=u'some artist',
album=u'some album',
tracks=_make_trackinfo(),
va=False,
album_id=None,
artist_id=None,
va=False
)
dist = self._dist(items, info)
self.assertNotEqual(dist, 0)
@ -380,9 +378,7 @@ class AlbumDistanceTest(_common.TestCase):
artist=u'someone else',
album=u'some album',
tracks=_make_trackinfo(),
va=False,
album_id=None,
artist_id=None,
va=False
)
self.assertNotEqual(self._dist(items, info), 0)
@ -395,9 +391,7 @@ class AlbumDistanceTest(_common.TestCase):
artist=u'should be ignored',
album=u'some album',
tracks=_make_trackinfo(),
va=True,
album_id=None,
artist_id=None,
va=True
)
self.assertEqual(self._dist(items, info), 0)
@ -411,9 +405,7 @@ class AlbumDistanceTest(_common.TestCase):
artist=u'should be ignored',
album=u'some album',
tracks=_make_trackinfo(),
va=True,
album_id=None,
artist_id=None,
va=True
)
info.tracks[0].artist = None
info.tracks[1].artist = None
@ -429,9 +421,7 @@ class AlbumDistanceTest(_common.TestCase):
artist=u'some artist',
album=u'some album',
tracks=_make_trackinfo(),
va=True,
album_id=None,
artist_id=None,
va=True
)
self.assertNotEqual(self._dist(items, info), 0)
@ -444,9 +434,7 @@ class AlbumDistanceTest(_common.TestCase):
artist=u'some artist',
album=u'some album',
tracks=_make_trackinfo(),
va=False,
album_id=None,
artist_id=None,
va=False
)
dist = self._dist(items, info)
self.assertTrue(0 < dist < 0.2)
@ -460,9 +448,7 @@ class AlbumDistanceTest(_common.TestCase):
artist=u'some artist',
album=u'some album',
tracks=_make_trackinfo(),
va=False,
album_id=None,
artist_id=None,
va=False
)
info.tracks[0].medium_index = 1
info.tracks[1].medium_index = 2
@ -479,9 +465,7 @@ class AlbumDistanceTest(_common.TestCase):
artist=u'some artist',
album=u'some album',
tracks=_make_trackinfo(),
va=False,
album_id=None,
artist_id=None,
va=False
)
info.tracks[0].medium_index = 1
info.tracks[1].medium_index = 2
@ -503,9 +487,9 @@ class AssignmentTest(unittest.TestCase):
items.append(self.item(u'three', 2))
items.append(self.item(u'two', 3))
trackinfo = []
trackinfo.append(TrackInfo(u'one', None))
trackinfo.append(TrackInfo(u'two', None))
trackinfo.append(TrackInfo(u'three', None))
trackinfo.append(TrackInfo(title=u'one'))
trackinfo.append(TrackInfo(title=u'two'))
trackinfo.append(TrackInfo(title=u'three'))
mapping, extra_items, extra_tracks = \
match.assign_items(items, trackinfo)
self.assertEqual(extra_items, [])
@ -522,9 +506,9 @@ class AssignmentTest(unittest.TestCase):
items.append(self.item(u'three', 1))
items.append(self.item(u'two', 1))
trackinfo = []
trackinfo.append(TrackInfo(u'one', None))
trackinfo.append(TrackInfo(u'two', None))
trackinfo.append(TrackInfo(u'three', None))
trackinfo.append(TrackInfo(title=u'one'))
trackinfo.append(TrackInfo(title=u'two'))
trackinfo.append(TrackInfo(title=u'three'))
mapping, extra_items, extra_tracks = \
match.assign_items(items, trackinfo)
self.assertEqual(extra_items, [])
@ -540,9 +524,9 @@ class AssignmentTest(unittest.TestCase):
items.append(self.item(u'one', 1))
items.append(self.item(u'three', 3))
trackinfo = []
trackinfo.append(TrackInfo(u'one', None))
trackinfo.append(TrackInfo(u'two', None))
trackinfo.append(TrackInfo(u'three', None))
trackinfo.append(TrackInfo(title=u'one'))
trackinfo.append(TrackInfo(title=u'two'))
trackinfo.append(TrackInfo(title=u'three'))
mapping, extra_items, extra_tracks = \
match.assign_items(items, trackinfo)
self.assertEqual(extra_items, [])
@ -558,8 +542,8 @@ class AssignmentTest(unittest.TestCase):
items.append(self.item(u'two', 2))
items.append(self.item(u'three', 3))
trackinfo = []
trackinfo.append(TrackInfo(u'one', None))
trackinfo.append(TrackInfo(u'three', None))
trackinfo.append(TrackInfo(title=u'one'))
trackinfo.append(TrackInfo(title=u'three'))
mapping, extra_items, extra_tracks = \
match.assign_items(items, trackinfo)
self.assertEqual(extra_items, [items[1]])
@ -595,7 +579,8 @@ class AssignmentTest(unittest.TestCase):
items.append(item(12, 186.45916150485752))
def info(index, title, length):
return TrackInfo(title, None, length=length, index=index)
return TrackInfo(title=title, length=length,
index=index)
trackinfo = []
trackinfo.append(info(1, u'Alone', 238.893))
trackinfo.append(info(2, u'The Woman in You', 341.44))
@ -638,8 +623,8 @@ class ApplyTest(_common.TestCase, ApplyTestUtil):
self.items.append(Item({}))
trackinfo = []
trackinfo.append(TrackInfo(
u'oneNew',
u'dfa939ec-118c-4d0f-84a0-60f3d1e6522c',
title=u'oneNew',
track_id=u'dfa939ec-118c-4d0f-84a0-60f3d1e6522c',
medium=1,
medium_index=1,
medium_total=1,
@ -648,8 +633,8 @@ class ApplyTest(_common.TestCase, ApplyTestUtil):
artist_sort='trackArtistSort',
))
trackinfo.append(TrackInfo(
u'twoNew',
u'40130ed1-a27c-42fd-a328-1ebefb6caef4',
title=u'twoNew',
track_id=u'40130ed1-a27c-42fd-a328-1ebefb6caef4',
medium=2,
medium_index=1,
index=2,
@ -749,13 +734,13 @@ class ApplyTest(_common.TestCase, ApplyTestUtil):
self.assertEqual(self.items[1].albumtype, 'album')
def test_album_artist_overrides_empty_track_artist(self):
my_info = copy.deepcopy(self.info)
my_info = self.info.copy()
self._apply(info=my_info)
self.assertEqual(self.items[0].artist, 'artistNew')
self.assertEqual(self.items[1].artist, 'artistNew')
def test_album_artist_overridden_by_nonempty_track_artist(self):
my_info = copy.deepcopy(self.info)
my_info = self.info.copy()
my_info.tracks[0].artist = 'artist1!'
my_info.tracks[1].artist = 'artist2!'
self._apply(info=my_info)
@ -777,7 +762,7 @@ class ApplyTest(_common.TestCase, ApplyTestUtil):
self.assertEqual(self.items[1].artist_sort, 'albumArtistSort')
def test_full_date_applied(self):
my_info = copy.deepcopy(self.info)
my_info = self.info.copy()
my_info.year = 2013
my_info.month = 12
my_info.day = 18
@ -792,7 +777,7 @@ class ApplyTest(_common.TestCase, ApplyTestUtil):
self.items.append(Item(year=1, month=2, day=3))
self.items.append(Item(year=4, month=5, day=6))
my_info = copy.deepcopy(self.info)
my_info = self.info.copy()
my_info.year = 2013
self._apply(info=my_info)
@ -812,7 +797,7 @@ class ApplyTest(_common.TestCase, ApplyTestUtil):
self.assertEqual(self.items[0].day, 3)
def test_data_source_applied(self):
my_info = copy.deepcopy(self.info)
my_info = self.info.copy()
my_info.data_source = 'MusicBrainz'
self._apply(info=my_info)
@ -828,15 +813,15 @@ class ApplyCompilationTest(_common.TestCase, ApplyTestUtil):
self.items.append(Item({}))
trackinfo = []
trackinfo.append(TrackInfo(
u'oneNew',
u'dfa939ec-118c-4d0f-84a0-60f3d1e6522c',
title=u'oneNew',
track_id=u'dfa939ec-118c-4d0f-84a0-60f3d1e6522c',
artist=u'artistOneNew',
artist_id=u'a05686fc-9db2-4c23-b99e-77f5db3e5282',
index=1,
))
trackinfo.append(TrackInfo(
u'twoNew',
u'40130ed1-a27c-42fd-a328-1ebefb6caef4',
title=u'twoNew',
track_id=u'40130ed1-a27c-42fd-a328-1ebefb6caef4',
artist=u'artistTwoNew',
artist_id=u'80b3cf5e-18fe-4c59-98c7-e5bb87210710',
index=2,
@ -874,7 +859,7 @@ class ApplyCompilationTest(_common.TestCase, ApplyTestUtil):
self.assertFalse(self.items[1].comp)
def test_va_flag_sets_comp(self):
va_info = copy.deepcopy(self.info)
va_info = self.info.copy()
va_info.va = True
self._apply(info=va_info)
self.assertTrue(self.items[0].comp)

View file

@ -22,7 +22,6 @@ import shutil
import re
import subprocess
import platform
from copy import deepcopy
import six
import unittest
@ -1051,8 +1050,10 @@ class ShowChangeTest(_common.TestCase):
self.items[0].track = 1
self.items[0].path = b'/path/to/file.mp3'
self.info = autotag.AlbumInfo(
u'the album', u'album id', u'the artist', u'artist id', [
autotag.TrackInfo(u'the title', u'track id', index=1)
album=u'the album', album_id=u'album id', artist=u'the artist',
artist_id=u'artist id', tracks=[
autotag.TrackInfo(title=u'the title', track_id=u'track id',
index=1)
]
)
@ -1136,7 +1137,9 @@ class SummarizeItemsTest(_common.TestCase):
summary = commands.summarize_items([self.item], False)
self.assertEqual(summary, u"1 items, F, 4kbps, 10:54, 987.0 B")
i2 = deepcopy(self.item)
# make a copy of self.item
i2 = self.item.copy()
summary = commands.summarize_items([self.item, i2], False)
self.assertEqual(summary, u"2 items, F, 4kbps, 21:48, 1.9 KiB")