"info dictionaries" replaced with AlbumInfo and TrackInfo

This commit is contained in:
Adrian Sampson 2011-10-23 14:12:13 -07:00
parent de2ee7e447
commit 95f38dbe52
12 changed files with 275 additions and 274 deletions

View file

@ -22,6 +22,7 @@ from beets.util import sorted_walk
# Parts of external interface.
from .hooks import AlbumInfo, TrackInfo
from .match import AutotagError
from .match import tag_item, tag_album
from .match import RECOMMEND_STRONG, RECOMMEND_MEDIUM, RECOMMEND_NONE
from .match import STRONG_REC_THRESH, MEDIUM_REC_THRESH, REC_GAP_THRESH
@ -55,54 +56,54 @@ def albums_in_dir(path):
if items:
yield root, items
def apply_item_metadata(item, track_data):
"""Set an item's metadata from its matched info dictionary.
def apply_item_metadata(item, track_info):
"""Set an item's metadata from its matched TrackInfo object.
"""
item.artist = track_data['artist']
item.title = track_data['title']
item.mb_trackid = track_data['id']
if 'artist_id' in track_data:
item.mb_artistid = track_data['artist_id']
item.artist = track_info.artist
item.title = track_info.title
item.mb_trackid = track_info.track_id
if track_info.artist_id:
item.mb_artistid = track_info.artist_id
# At the moment, the other metadata is left intact (including album
# and track number). Perhaps these should be emptied?
def apply_metadata(items, info):
"""Set the items' metadata to match the data given in info. The
list of items must be ordered.
def apply_metadata(items, album_info):
"""Set the items' metadata to match an AlbumInfo object. The list
of items must be ordered.
"""
for index, (item, track_data) in enumerate(zip(items, info['tracks'])):
for index, (item, track_info) in enumerate(zip(items, album_info.tracks)):
# Album, artist, track count.
if 'artist' in track_data:
item.artist = track_data['artist']
if track_info.artist:
item.artist = track_info.artist
else:
item.artist = info['artist']
item.albumartist = info['artist']
item.album = info['album']
item.artist = album_info.artist
item.albumartist = album_info.artist
item.album = album_info.album
item.tracktotal = len(items)
# Release date.
if 'year' in info:
item.year = info['year']
if 'month' in info:
item.month = info['month']
if 'day' in info:
item.day = info['day']
if album_info.year:
item.year = album_info.year
if album_info.month:
item.month = album_info.month
if album_info.day:
item.day = album_info.day
# Title and track index.
item.title = track_data['title']
item.title = track_info.title
item.track = index + 1
# MusicBrainz IDs.
item.mb_trackid = track_data['id']
item.mb_albumid = info['album_id']
if 'artist_id' in track_data:
item.mb_artistid = track_data['artist_id']
item.mb_trackid = track_info.track_id
item.mb_albumid = album_info.album_id
if track_info.artist_id:
item.mb_artistid = track_info.artist_id
else:
item.mb_artistid = info['artist_id']
item.mb_albumartistid = info['artist_id']
item.albumtype = info['albumtype']
if 'label' in info:
item.label = info['label']
item.mb_artistid = album_info.artist_id
item.mb_albumartistid = album_info.artist_id
item.albumtype = album_info.albumtype
if album_info.label:
item.label = album_info.label
# Compilation flag.
item.comp = info['va']
item.comp = album_info.va

View file

@ -89,9 +89,9 @@ def art_for_album(album, path):
if out:
return out
if album['asin']:
log.debug('Fetching album art for ASIN %s.' % album['asin'])
return art_for_asin(album['asin'])
if album.asin:
log.debug('Fetching album art for ASIN %s.' % album.asin)
return art_for_asin(album.asin)
else:
log.debug('No ASIN available: no art found.')
return None

View file

@ -15,7 +15,7 @@
"""Glue between metadata sources and the matching logic."""
from beets import plugins
from . import mb
from beets.autotag import mb
# Classes used to represent candidate options.
@ -40,7 +40,8 @@ class AlbumInfo(object):
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):
albumtype=None, va=False, year=None, month=None, day=None,
label=None):
self.album = album
self.album_id = album_id
self.artist = artist
@ -52,6 +53,7 @@ class AlbumInfo(object):
self.year = year
self.month = month
self.day = day
self.label = label
class TrackInfo(object):
"""Describes a canonical track present on a release. Appears as part
@ -71,7 +73,7 @@ class TrackInfo(object):
self.title = title
self.track_id = track_id
self.artist = artist
self.artist_id = artist
self.artist_id = artist_id
self.length = length

View file

@ -192,10 +192,10 @@ def order_items(items, trackinfo):
ordered_items[canon_idx] = items[cur_idx]
return ordered_items
def track_distance(item, track_data, track_index=None, incl_artist=False):
def track_distance(item, track_info, track_index=None, incl_artist=False):
"""Determines the significance of a track metadata change. Returns
a float in [0.0,1.0]. `track_index` is the track number of the
`track_data` metadata set. If `track_index` is provided and
`track_info` metadata set. If `track_index` is provided and
item.track is set, then these indices are used as a component of
the distance calculation. `incl_artist` indicates that a distance
component should be included for the track artist (i.e., for
@ -205,26 +205,26 @@ def track_distance(item, track_data, track_index=None, incl_artist=False):
dist, dist_max = 0.0, 0.0
# Check track length.
if 'length' not in track_data:
if not track_info.length:
# If there's no length to check, assume the worst.
dist += TRACK_LENGTH_WEIGHT
else:
diff = abs(item.length - track_data['length'])
diff = abs(item.length - track_info.length)
diff = max(diff - TRACK_LENGTH_GRACE, 0.0)
diff = min(diff, TRACK_LENGTH_MAX)
dist += (diff / TRACK_LENGTH_MAX) * TRACK_LENGTH_WEIGHT
dist_max += TRACK_LENGTH_WEIGHT
# Track title.
dist += string_dist(item.title, track_data['title']) * TRACK_TITLE_WEIGHT
dist += string_dist(item.title, track_info.title) * TRACK_TITLE_WEIGHT
dist_max += TRACK_TITLE_WEIGHT
# Track artist, if included.
# Attention: MB DB does not have artist info for all compilations,
# so only check artist distance if there is actually an artist in
# the MB track data.
if incl_artist and 'artist' in track_data:
dist += string_dist(item.artist, track_data['artist']) * \
if incl_artist and track_info.artist:
dist += string_dist(item.artist, track_info.artist) * \
TRACK_ARTIST_WEIGHT
dist_max += TRACK_ARTIST_WEIGHT
@ -236,18 +236,18 @@ def track_distance(item, track_data, track_index=None, incl_artist=False):
# MusicBrainz track ID.
if item.mb_trackid:
if item.mb_trackid != track_data['id']:
if item.mb_trackid != track_info.track_id:
dist += TRACK_ID_WEIGHT
dist_max += TRACK_ID_WEIGHT
# Plugin distances.
plugin_d, plugin_dm = plugins.track_distance(item, track_data)
plugin_d, plugin_dm = plugins.track_distance(item, track_info)
dist += plugin_d
dist_max += plugin_dm
return dist / dist_max
def distance(items, info):
def distance(items, album_info):
"""Determines how "significant" an album metadata change would be.
Returns a float in [0.0,1.0]. The list of items must be ordered.
"""
@ -261,20 +261,20 @@ def distance(items, info):
dist_max = 0.0
# Artist/album metadata.
if not info['va']:
dist += string_dist(cur_artist, info['artist']) * ARTIST_WEIGHT
if not album_info.va:
dist += string_dist(cur_artist, album_info.artist) * ARTIST_WEIGHT
dist_max += ARTIST_WEIGHT
dist += string_dist(cur_album, info['album']) * ALBUM_WEIGHT
dist += string_dist(cur_album, album_info.album) * ALBUM_WEIGHT
dist_max += ALBUM_WEIGHT
# Track distances.
for i, (item, track_data) in enumerate(zip(items, info['tracks'])):
dist += track_distance(item, track_data, i+1, info['va']) * \
for i, (item, track_info) in enumerate(zip(items, album_info.tracks)):
dist += track_distance(item, track_info, i+1, album_info.va) * \
TRACK_WEIGHT
dist_max += TRACK_WEIGHT
# Plugin distances.
plugin_d, plugin_dm = plugins.album_distance(items, info)
plugin_d, plugin_dm = plugins.album_distance(items, album_info)
dist += plugin_d
dist_max += plugin_dm
@ -340,20 +340,20 @@ def validate_candidate(items, tuple_dict, info):
the track count, ordering the items, checking for duplicates, and
calculating the distance.
"""
log.debug('Candidate: %s - %s' % (info['artist'], info['album']))
log.debug('Candidate: %s - %s' % (info.artist, info.album))
# Don't duplicate.
if info['album_id'] in tuple_dict:
if info.album_id in tuple_dict:
log.debug('Duplicate.')
return
# Make sure the album has the correct number of tracks.
if len(items) != len(info['tracks']):
if len(items) != len(info.tracks):
log.debug('Track count mismatch.')
return
# Put items in order.
ordered = order_items(items, info['tracks'])
ordered = order_items(items, info.tracks)
if not ordered:
log.debug('Not orderable.')
return
@ -362,7 +362,7 @@ def validate_candidate(items, tuple_dict, info):
dist = distance(ordered, info)
log.debug('Success. Distance: %f' % dist)
tuple_dict[info['album_id']] = dist, ordered, info
tuple_dict[info.album_id] = dist, ordered, info
def tag_album(items, timid=False, search_artist=None, search_album=None,
search_id=None):

View file

@ -30,6 +30,8 @@ from musicbrainz2.model import Release
from threading import Lock
from musicbrainz2.model import VARIOUS_ARTISTS_ID
import beets.autotag.hooks
SEARCH_LIMIT = 5
VARIOUS_ARTISTS_ID = VARIOUS_ARTISTS_ID.rsplit('/', 1)[1]
@ -115,7 +117,7 @@ def _query_wrap(fun, *args, **kwargs):
def get_releases(**params):
"""Given a list of parameters to ReleaseFilter, executes the
query and yields release dicts (complete with tracks).
query and yields AlbumInfo objects.
"""
# Replace special cases.
if 'artistName' in params:
@ -135,7 +137,7 @@ def get_releases(**params):
for result in results:
release = result.release
tracks, _ = release_info(release.id)
yield release_dict(release, tracks)
yield album_info(release, tracks)
def release_info(release_id):
"""Given a MusicBrainz release ID, fetch a list of tracks on the
@ -173,9 +175,9 @@ def _lucene_query(criteria):
return u' '.join(query_parts)
def find_releases(criteria, limit=SEARCH_LIMIT):
"""Get a list of release dictionaries from the MusicBrainz
database that match `criteria`. The latter is a dictionary whose
keys are MusicBrainz field names and whose values are search terms
"""Get a list of AlbumInfo objects from the MusicBrainz database
that match `criteria`. The latter is a dictionary whose keys are
MusicBrainz field names and whose values are search terms
for those fields.
The field names are from MusicBrainz's Lucene query syntax, which
@ -196,7 +198,7 @@ def find_releases(criteria, limit=SEARCH_LIMIT):
return get_releases(limit=limit, query=query)
def find_tracks(criteria, limit=SEARCH_LIMIT):
"""Get a sequence of track dictionaries from MusicBrainz that match
"""Get a sequence of TrackInfo objects from MusicBrainz that match
`criteria`, a search term dictionary similar to the one passed to
`find_releases`.
"""
@ -210,43 +212,44 @@ def find_tracks(criteria, limit=SEARCH_LIMIT):
results = ()
for result in results:
track = result.track
yield track_dict(track)
yield track_info(track)
def track_dict(track):
"""Produces a dictionary summarizing a MusicBrainz `Track` object.
def track_info(track):
"""Translates a MusicBrainz ``Track`` object into a beets
``TrackInfo`` object.
"""
t = {'title': track.title,
'id': track.id.rsplit('/', 1)[1]}
info = beets.autotag.hooks.TrackInfo(track.title,
track.id.rsplit('/', 1)[1])
if track.artist is not None:
# Track artists will only be present for releases with
# multiple artists.
t['artist'] = track.artist.name
t['artist_id'] = track.artist.id.rsplit('/', 1)[1]
info.artist = track.artist.name
info.artist_id = track.artist.id.rsplit('/', 1)[1]
if track.duration is not None:
# Duration not always present.
t['length'] = track.duration/(1000.0)
return t
info.length = track.duration/(1000.0)
return info
def release_dict(release, tracks=None):
"""Takes a MusicBrainz `Release` object and returns a dictionary
containing the interesting data about that release. A list of
`Track` objects may also be provided as `tracks`; they are then
included in the resulting dictionary.
def album_info(release, tracks):
"""Takes a MusicBrainz ``Release`` object and returns a beets
AlbumInfo object containing the interesting data about that release.
``tracks`` is a list of ``Track`` objects that make up the album.
"""
# Basic info.
out = {'album': release.title,
'album_id': release.id.rsplit('/', 1)[1],
'artist': release.artist.name,
'artist_id': release.artist.id.rsplit('/', 1)[1],
'asin': release.asin,
'albumtype': '',
}
out['va'] = out['artist_id'] == VARIOUS_ARTISTS_ID
info = beets.autotag.hooks.AlbumInfo(
release.title,
release.id.rsplit('/', 1)[1],
release.artist.name,
release.artist.id.rsplit('/', 1)[1],
[track_info(track) for track in tracks],
release.asin
)
info.va = info.artist_id == VARIOUS_ARTISTS_ID
# Release type not always populated.
for releasetype in release.types:
if releasetype in RELEASE_TYPES:
out['albumtype'] = releasetype.split('#')[1].lower()
info.albumtype = releasetype.split('#')[1].lower()
break
# Release date and label.
@ -255,7 +258,7 @@ def release_dict(release, tracks=None):
except:
# The python-musicbrainz2 module has a bug that will raise an
# exception when there is no release date to be found. In this
# case, we just skip adding a release date to the dict.
# case, we just skip adding a release date to the result.
pass
else:
if event:
@ -265,25 +268,20 @@ def release_dict(release, tracks=None):
date_parts = date_str.split('-')
for key in ('year', 'month', 'day'):
if date_parts:
out[key] = int(date_parts.pop(0))
setattr(info, key, int(date_parts.pop(0)))
# Label name.
label = event.getLabel()
if label:
name = label.getName()
if name and name != '[no label]':
out['label'] = name
info.label = name
# Tracks.
if tracks is not None:
out['tracks'] = map(track_dict, tracks)
return out
return info
def match_album(artist, album, tracks=None, limit=SEARCH_LIMIT):
"""Searches for a single album ("release" in MusicBrainz parlance)
and returns an iterator over dictionaries of information (as
returned by `release_dict`).
and returns an iterator over AlbumInfo objects.
The query consists of an artist name, an album name, and,
optionally, a number of tracks on the album.
@ -302,8 +300,8 @@ def match_album(artist, album, tracks=None, limit=SEARCH_LIMIT):
return find_releases(criteria, limit)
def match_track(artist, title):
"""Searches for a single track and returns an iterable of track
info dictionaries (as returned by `track_dict`).
"""Searches for a single track and returns an iterable of TrackInfo
objects.
"""
return find_tracks({
'artist': artist,
@ -311,8 +309,8 @@ def match_track(artist, title):
})
def album_for_id(albumid):
"""Fetches an album by its MusicBrainz ID and returns an
information dictionary. If no match is found, returns None.
"""Fetches an album by its MusicBrainz ID and returns an AlbumInfo
object or None if the album is not found.
"""
query = mbws.Query()
try:
@ -322,11 +320,11 @@ def album_for_id(albumid):
except (mbws.ResourceNotFoundError, mbws.RequestError), exc:
log.debug('Album ID match failed: ' + str(exc))
return None
return release_dict(album, album.tracks)
return album_info(album, album.tracks)
def track_for_id(trackid):
"""Fetches a track by its MusicBrainz ID. Returns a track info
dictionary or None if no track is found.
"""Fetches a track by its MusicBrainz ID. Returns a TrackInfo object
or None if no track is found.
"""
query = mbws.Query()
try:
@ -336,4 +334,4 @@ def track_for_id(trackid):
except (mbws.ResourceNotFoundError, mbws.RequestError), exc:
log.debug('Track ID match failed: ' + str(exc))
return None
return track_dict(track)
return track_info(track)

View file

@ -91,8 +91,8 @@ def _duplicate_check(lib, task, recent=None):
artist = task.cur_artist
album = task.cur_album
elif task.choice_flag is action.APPLY:
artist = task.info['artist']
album = task.info['album']
artist = task.info.artist
album = task.info.album
else:
return False
@ -125,8 +125,8 @@ def _item_duplicate_check(lib, task, recent=None):
artist = task.item.artist
title = task.item.title
elif task.choice_flag is action.APPLY:
artist = task.info['artist']
title = task.info['title']
artist = task.info.artist
title = task.info.title
else:
return False

View file

@ -55,14 +55,14 @@ class BeetsPlugin(object):
return 0.0, 0.0
def candidates(self, items):
"""Should return a sequence of MusicBrainz info dictionaries
that match the album whose items are provided.
"""Should return a sequence of AlbumInfo objects that match the
album whose items are provided.
"""
return ()
def item_candidates(self, item):
"""Should return a sequence of MusicBrainz track info
dictionaries that match the item provided.
"""Should return a sequence of TrackInfo objects that match the
item provided.
"""
return ()

View file

@ -130,10 +130,10 @@ def show_change(cur_artist, cur_album, items, info, dist, color=True):
print_(' (unknown album)')
# Identify the album in question.
if cur_artist != info['artist'] or \
(cur_album != info['album'] and info['album'] != VARIOUS_ARTISTS):
artist_l, artist_r = cur_artist or '', info['artist']
album_l, album_r = cur_album or '', info['album']
if cur_artist != info.artist or \
(cur_album != info.album and info.album != VARIOUS_ARTISTS):
artist_l, artist_r = cur_artist or '', info.artist
album_l, album_r = cur_album or '', info.album
if artist_r == VARIOUS_ARTISTS:
# Hide artists for VA releases.
artist_l, artist_r = u'', u''
@ -147,17 +147,17 @@ def show_change(cur_artist, cur_album, items, info, dist, color=True):
print_("To:")
show_album(artist_r, album_r)
else:
print_("Tagging: %s - %s" % (info['artist'], info['album']))
print_("Tagging: %s - %s" % (info.artist, info.album))
# Distance/similarity.
print_('(Similarity: %s)' % dist_string(dist, color))
# Tracks.
for i, (item, track_data) in enumerate(zip(items, info['tracks'])):
for i, (item, track_info) in enumerate(zip(items, info.tracks)):
cur_track = str(item.track)
new_track = str(i+1)
cur_title = item.title
new_title = track_data['title']
new_title = track_info.title
# Possibly colorize changes.
if color:
@ -183,8 +183,8 @@ def show_item_change(item, info, dist, color):
"""Print out the change that would occur by tagging `item` with the
metadata from `info`.
"""
cur_artist, new_artist = item.artist, info['artist']
cur_title, new_title = item.title, info['title']
cur_artist, new_artist = item.artist, info.artist
cur_title, new_title = item.title, info.title
if cur_artist != new_artist or cur_title != new_title:
if color:
@ -228,7 +228,7 @@ def choose_candidate(candidates, singleton, rec, color, timid,
Returns the result of the choice, which may SKIP, ASIS, TRACKS, or
MANUAL or a candidate. For albums, a candidate is a `(info, items)`
pair; for items, it is just an `info` dictionary.
pair; for items, it is just a TrackInfo object.
"""
# Sanity check.
if singleton:
@ -462,7 +462,7 @@ def choose_match(task, config):
def choose_item(task, config):
"""Ask the user for a choice about tagging a single item. Returns
either an action constant or a track info dictionary.
either an action constant or a TrackInfo object.
"""
print_()
print_(task.item.path)

View file

@ -18,6 +18,7 @@ import unittest
import _common
from beets.autotag import art
from beets.autotag import AlbumInfo
import os
import shutil
@ -76,25 +77,25 @@ class CombinedTest(unittest.TestCase):
def test_main_interface_returns_amazon_art(self):
art.urllib.urlretrieve = MockUrlRetrieve('anotherpath', 'image/jpeg')
album = {'asin': 'xxxx'}
album = AlbumInfo(None, None, None, None, None, asin='xxxx')
artpath = art.art_for_album(album, None)
self.assertEqual(artpath, 'anotherpath')
def test_main_interface_returns_none_for_missing_asin_and_path(self):
album = {'asin': None}
album = AlbumInfo(None, None, None, None, None, asin=None)
artpath = art.art_for_album(album, None)
self.assertEqual(artpath, None)
def test_main_interface_gives_precedence_to_fs_art(self):
_common.touch(os.path.join(self.dpath, 'a.jpg'))
art.urllib.urlretrieve = MockUrlRetrieve('anotherpath', 'image/jpeg')
album = {'asin': 'xxxx'}
album = AlbumInfo(None, None, None, None, None, asin='xxxx')
artpath = art.art_for_album(album, self.dpath)
self.assertEqual(artpath, os.path.join(self.dpath, 'a.jpg'))
def test_main_interface_falls_back_to_amazon(self):
art.urllib.urlretrieve = MockUrlRetrieve('anotherpath', 'image/jpeg')
album = {'asin': 'xxxx'}
album = AlbumInfo(None, None, None, None, None, asin='xxxx')
artpath = art.art_for_album(album, self.dpath)
self.assertEqual(artpath, 'anotherpath')

View file

@ -18,12 +18,14 @@ import unittest
import os
import shutil
import re
import copy
import _common
from beets import autotag
from beets.autotag import match
from beets.library import Item
from beets.util import plurality
from beets.autotag import AlbumInfo, TrackInfo
class PluralityTest(unittest.TestCase):
def test_plurality_consensus(self):
@ -73,12 +75,9 @@ class AlbumDistanceTest(unittest.TestCase):
def trackinfo(self):
ti = []
ti.append({'title': 'one', 'artist': 'some artist',
'track': 1, 'length': 1})
ti.append({'title': 'two', 'artist': 'some artist',
'track': 2, 'length': 1})
ti.append({'title': 'three', 'artist': 'some artist',
'track': 3, 'length': 1})
ti.append(TrackInfo('one', None, 'some artist', length=1))
ti.append(TrackInfo('two', None, 'some artist', length=1))
ti.append(TrackInfo('three', None, 'some artist', length=1))
return ti
def test_identical_albums(self):
@ -86,12 +85,13 @@ class AlbumDistanceTest(unittest.TestCase):
items.append(self.item('one', 1))
items.append(self.item('two', 2))
items.append(self.item('three', 3))
info = {
'artist': 'some artist',
'album': 'some album',
'tracks': self.trackinfo(),
'va': False,
}
info = AlbumInfo(
artist = 'some artist',
album = 'some album',
tracks = self.trackinfo(),
va = False,
album_id = None, artist_id = None,
)
self.assertEqual(match.distance(items, info), 0)
def test_global_artists_differ(self):
@ -99,12 +99,13 @@ class AlbumDistanceTest(unittest.TestCase):
items.append(self.item('one', 1))
items.append(self.item('two', 2))
items.append(self.item('three', 3))
info = {
'artist': 'someone else',
'album': 'some album',
'tracks': self.trackinfo(),
'va': False,
}
info = AlbumInfo(
artist = 'someone else',
album = 'some album',
tracks = self.trackinfo(),
va = False,
album_id = None, artist_id = None,
)
self.assertNotEqual(match.distance(items, info), 0)
def test_comp_track_artists_match(self):
@ -112,12 +113,13 @@ class AlbumDistanceTest(unittest.TestCase):
items.append(self.item('one', 1))
items.append(self.item('two', 2))
items.append(self.item('three', 3))
info = {
'artist': 'should be ignored',
'album': 'some album',
'tracks': self.trackinfo(),
'va': True,
}
info = AlbumInfo(
artist = 'should be ignored',
album = 'some album',
tracks = self.trackinfo(),
va = True,
album_id = None, artist_id = None,
)
self.assertEqual(match.distance(items, info), 0)
def test_comp_no_track_artists(self):
@ -126,15 +128,16 @@ class AlbumDistanceTest(unittest.TestCase):
items.append(self.item('one', 1))
items.append(self.item('two', 2))
items.append(self.item('three', 3))
info = {
'artist': 'should be ignored',
'album': 'some album',
'tracks': self.trackinfo(),
'va': True,
}
del info['tracks'][0]['artist']
del info['tracks'][1]['artist']
del info['tracks'][2]['artist']
info = AlbumInfo(
artist = 'should be ignored',
album = 'some album',
tracks = self.trackinfo(),
va = True,
album_id = None, artist_id = None,
)
info.tracks[0].artist = None
info.tracks[1].artist = None
info.tracks[2].artist = None
self.assertEqual(match.distance(items, info), 0)
def test_comp_track_artists_do_not_match(self):
@ -142,12 +145,13 @@ class AlbumDistanceTest(unittest.TestCase):
items.append(self.item('one', 1))
items.append(self.item('two', 2, 'someone else'))
items.append(self.item('three', 3))
info = {
'artist': 'some artist',
'album': 'some album',
'tracks': self.trackinfo(),
'va': True,
}
info = AlbumInfo(
artist = 'some artist',
album = 'some album',
tracks = self.trackinfo(),
va = True,
album_id = None, artist_id = None,
)
self.assertNotEqual(match.distance(items, info), 0)
def _mkmp3(path):
@ -206,9 +210,9 @@ class OrderingTest(unittest.TestCase):
items.append(self.item('three', 2))
items.append(self.item('two', 3))
trackinfo = []
trackinfo.append({'title': 'one', 'track': 1})
trackinfo.append({'title': 'two', 'track': 2})
trackinfo.append({'title': 'three', 'track': 3})
trackinfo.append(TrackInfo('one', None))
trackinfo.append(TrackInfo('two', None))
trackinfo.append(TrackInfo('three', None))
ordered = match.order_items(items, trackinfo)
self.assertEqual(ordered[0].title, 'one')
self.assertEqual(ordered[1].title, 'two')
@ -220,9 +224,9 @@ class OrderingTest(unittest.TestCase):
items.append(self.item('three', 1))
items.append(self.item('two', 1))
trackinfo = []
trackinfo.append({'title': 'one', 'track': 1})
trackinfo.append({'title': 'two', 'track': 2})
trackinfo.append({'title': 'three', 'track': 3})
trackinfo.append(TrackInfo('one', None))
trackinfo.append(TrackInfo('two', None))
trackinfo.append(TrackInfo('three', None))
ordered = match.order_items(items, trackinfo)
self.assertEqual(ordered[0].title, 'one')
self.assertEqual(ordered[1].title, 'two')
@ -233,7 +237,7 @@ class OrderingTest(unittest.TestCase):
items.append(self.item('one', 1))
items.append(self.item('two', 2))
trackinfo = []
trackinfo.append({'title': 'one', 'track': 1})
trackinfo.append(TrackInfo('one', None))
ordered = match.order_items(items, trackinfo)
self.assertEqual(ordered, None)
@ -263,10 +267,7 @@ class OrderingTest(unittest.TestCase):
items.append(item(12, 186.45916150485752))
def info(title, length):
return {
'title': title,
'length': length,
}
return TrackInfo(title, None, length=length)
trackinfo = []
trackinfo.append(info('Alone', 238.893))
trackinfo.append(info('The Woman in You', 341.44))
@ -291,23 +292,19 @@ class ApplyTest(unittest.TestCase):
self.items.append(Item({}))
self.items.append(Item({}))
trackinfo = []
trackinfo.append({
'title': 'oneNew',
'id': 'dfa939ec-118c-4d0f-84a0-60f3d1e6522c',
})
trackinfo.append({
'title': 'twoNew',
'id': '40130ed1-a27c-42fd-a328-1ebefb6caef4',
})
self.info = {
'tracks': trackinfo,
'artist': 'artistNew',
'album': 'albumNew',
'album_id': '7edb51cb-77d6-4416-a23c-3a8c2994a2c7',
'artist_id': 'a6623d39-2d8e-4f70-8242-0a9553b91e50',
'albumtype': 'album',
'va': False,
}
trackinfo.append(TrackInfo('oneNew',
'dfa939ec-118c-4d0f-84a0-60f3d1e6522c'))
trackinfo.append(TrackInfo('twoNew',
'40130ed1-a27c-42fd-a328-1ebefb6caef4'))
self.info = AlbumInfo(
tracks = trackinfo,
artist = 'artistNew',
album = 'albumNew',
album_id = '7edb51cb-77d6-4416-a23c-3a8c2994a2c7',
artist_id = 'a6623d39-2d8e-4f70-8242-0a9553b91e50',
albumtype = 'album',
va = False,
)
def test_titles_applied(self):
autotag.apply_metadata(self.items, self.info)
@ -352,17 +349,15 @@ class ApplyTest(unittest.TestCase):
self.assertEqual(self.items[1].albumtype, 'album')
def test_album_artist_overrides_empty_track_artist(self):
my_info = dict(self.info)
my_info['tracks'] = [dict(t) for t in self.info['tracks']]
my_info = copy.deepcopy(self.info)
autotag.apply_metadata(self.items, my_info)
self.assertEqual(self.items[0].artist, 'artistNew')
self.assertEqual(self.items[0].artist, 'artistNew')
def test_album_artist_overriden_by_nonempty_track_artist(self):
my_info = dict(self.info)
my_info['tracks'] = [dict(t) for t in self.info['tracks']]
my_info['tracks'][0]['artist'] = 'artist1!'
my_info['tracks'][1]['artist'] = 'artist2!'
my_info = copy.deepcopy(self.info)
my_info.tracks[0].artist = 'artist1!'
my_info.tracks[1].artist = 'artist2!'
autotag.apply_metadata(self.items, my_info)
self.assertEqual(self.items[0].artist, 'artist1!')
self.assertEqual(self.items[1].artist, 'artist2!')
@ -373,27 +368,27 @@ class ApplyCompilationTest(unittest.TestCase):
self.items.append(Item({}))
self.items.append(Item({}))
trackinfo = []
trackinfo.append({
'title': 'oneNew',
'id': 'dfa939ec-118c-4d0f-84a0-60f3d1e6522c',
'artist': 'artistOneNew',
'artist_id': 'a05686fc-9db2-4c23-b99e-77f5db3e5282',
})
trackinfo.append({
'title': 'twoNew',
'id': '40130ed1-a27c-42fd-a328-1ebefb6caef4',
'artist': 'artistTwoNew',
'artist_id': '80b3cf5e-18fe-4c59-98c7-e5bb87210710',
})
self.info = {
'tracks': trackinfo,
'artist': 'variousNew',
'album': 'albumNew',
'album_id': '3b69ea40-39b8-487f-8818-04b6eff8c21a',
'artist_id': '89ad4ac3-39f7-470e-963a-56509c546377',
'albumtype': 'compilation',
'va': False,
}
trackinfo.append(TrackInfo(
'oneNew',
'dfa939ec-118c-4d0f-84a0-60f3d1e6522c',
'artistOneNew',
'a05686fc-9db2-4c23-b99e-77f5db3e5282',
))
trackinfo.append(TrackInfo(
'twoNew',
'40130ed1-a27c-42fd-a328-1ebefb6caef4',
'artistTwoNew',
'80b3cf5e-18fe-4c59-98c7-e5bb87210710',
))
self.info = AlbumInfo(
tracks = trackinfo,
artist = 'variousNew',
album = 'albumNew',
album_id = '3b69ea40-39b8-487f-8818-04b6eff8c21a',
artist_id = '89ad4ac3-39f7-470e-963a-56509c546377',
albumtype = 'compilation',
va = False,
)
def test_album_and_track_artists_separate(self):
autotag.apply_metadata(self.items, self.info)
@ -419,8 +414,8 @@ class ApplyCompilationTest(unittest.TestCase):
self.assertFalse(self.items[1].comp)
def test_va_flag_sets_comp(self):
va_info = dict(self.info) # make a copy
va_info['va'] = True
va_info = copy.deepcopy(self.info)
va_info.va = True
autotag.apply_metadata(self.items, va_info)
self.assertTrue(self.items[0].comp)
self.assertTrue(self.items[1].comp)

View file

@ -22,6 +22,7 @@ import _common
from beets import library
from beets import importer
from beets import mediafile
from beets.autotag import AlbumInfo, TrackInfo
TEST_TITLES = ('The Opener', 'The Second Track', 'The Last Track')
class NonAutotaggedImportTest(unittest.TestCase):
@ -173,17 +174,17 @@ class ImportApplyTest(unittest.TestCase, _common.ExtraAsserts):
self.i = library.Item.from_path(self.srcpath)
self.i.comp = False
trackinfo = {'title': 'one', 'artist': 'some artist',
'track': 1, 'length': 1, 'id': 'trackid'}
self.info = {
'artist': 'some artist',
'album': 'some album',
'tracks': [trackinfo],
'va': False,
'album_id': 'albumid',
'artist_id': 'artistid',
'albumtype': 'soundtrack',
}
trackinfo = TrackInfo('one', 'trackid', 'some artist',
'artistid', 1)
self.info = AlbumInfo(
artist = 'some artist',
album = 'some album',
tracks = [trackinfo],
va = False,
album_id = 'albumid',
artist_id = 'artistid',
albumtype = 'soundtrack',
)
def tearDown(self):
shutil.rmtree(self.libdir)
@ -227,7 +228,7 @@ class ImportApplyTest(unittest.TestCase, _common.ExtraAsserts):
coro.next() # Prime coroutine.
task = importer.ImportTask.item_task(self.i)
task.set_choice(self.info['tracks'][0])
task.set_choice(self.info.tracks[0])
coro.send(task)
self.assertExists(
@ -559,7 +560,10 @@ class DuplicateCheckTest(unittest.TestCase):
if asis:
task.set_choice(importer.action.ASIS)
else:
task.set_choice(({'artist': artist, 'album': album}, [item]))
task.set_choice((
AlbumInfo(album, None, artist, None, None),
[item]
))
return task
def _item_task(self, asis, artist=None, title=None, existing=False):
@ -576,7 +580,7 @@ class DuplicateCheckTest(unittest.TestCase):
item.title = title
task.set_choice(importer.action.ASIS)
else:
task.set_choice({'artist': artist, 'title': title})
task.set_choice(TrackInfo(title, None, artist))
return task
def test_duplicate_album_apply(self):

View file

@ -100,7 +100,7 @@ class MBQueryErrorTest(unittest.TestCase):
with self.assertRaises(mb.ServerBusyError):
mb._query_wrap(raise_func(exc))
class MBReleaseDictTest(unittest.TestCase):
class MBAlbumInfoTest(unittest.TestCase):
def _make_release(self, date_str='2009'):
release = musicbrainz2.model.Release()
release.title = 'ALBUM TITLE'
@ -128,68 +128,68 @@ class MBReleaseDictTest(unittest.TestCase):
def test_parse_release_with_year(self):
release = self._make_release('1984')
d = mb.release_dict(release)
self.assertEqual(d['album'], 'ALBUM TITLE')
self.assertEqual(d['album_id'], 'ALBUM ID')
self.assertEqual(d['artist'], 'ARTIST NAME')
self.assertEqual(d['artist_id'], 'ARTIST ID')
self.assertEqual(d['year'], 1984)
d = mb.album_info(release, [])
self.assertEqual(d.album, 'ALBUM TITLE')
self.assertEqual(d.album_id, 'ALBUM ID')
self.assertEqual(d.artist, 'ARTIST NAME')
self.assertEqual(d.artist_id, 'ARTIST ID')
self.assertEqual(d.year, 1984)
def test_parse_release_type(self):
release = self._make_release('1984')
d = mb.release_dict(release)
self.assertEqual(d['albumtype'], 'album')
d = mb.album_info(release, [])
self.assertEqual(d.albumtype, 'album')
def test_parse_release_full_date(self):
release = self._make_release('1987-03-31')
d = mb.release_dict(release)
self.assertEqual(d['year'], 1987)
self.assertEqual(d['month'], 3)
self.assertEqual(d['day'], 31)
d = mb.album_info(release, [])
self.assertEqual(d.year, 1987)
self.assertEqual(d.month, 3)
self.assertEqual(d.day, 31)
def test_parse_tracks(self):
release = self._make_release()
tracks = [self._make_track('TITLE ONE', 'dom/ID ONE', 100.0 * 1000.0),
self._make_track('TITLE TWO', 'dom/ID TWO', 200.0 * 1000.0)]
d = mb.release_dict(release, tracks)
t = d['tracks']
d = mb.album_info(release, tracks)
t = d.tracks
self.assertEqual(len(t), 2)
self.assertEqual(t[0]['title'], 'TITLE ONE')
self.assertEqual(t[0]['id'], 'ID ONE')
self.assertEqual(t[0]['length'], 100.0)
self.assertEqual(t[1]['title'], 'TITLE TWO')
self.assertEqual(t[1]['id'], 'ID TWO')
self.assertEqual(t[1]['length'], 200.0)
self.assertEqual(t[0].title, 'TITLE ONE')
self.assertEqual(t[0].track_id, 'ID ONE')
self.assertEqual(t[0].length, 100.0)
self.assertEqual(t[1].title, 'TITLE TWO')
self.assertEqual(t[1].track_id, 'ID TWO')
self.assertEqual(t[1].length, 200.0)
def test_parse_release_year_month_only(self):
release = self._make_release('1987-03')
d = mb.release_dict(release)
self.assertEqual(d['year'], 1987)
self.assertEqual(d['month'], 3)
d = mb.album_info(release, [])
self.assertEqual(d.year, 1987)
self.assertEqual(d.month, 3)
def test_no_durations(self):
release = self._make_release()
tracks = [self._make_track('TITLE', 'dom/ID', None)]
d = mb.release_dict(release, tracks)
self.assertFalse('length' in d['tracks'][0])
d = mb.album_info(release, tracks)
self.assertEqual(d.tracks[0].length, None)
def test_no_release_date(self):
release = self._make_release(None)
d = mb.release_dict(release)
self.assertFalse('year' in d)
self.assertFalse('month' in d)
self.assertFalse('day' in d)
d = mb.album_info(release, [])
self.assertFalse(d.year)
self.assertFalse(d.month)
self.assertFalse(d.day)
def test_various_artists_defaults_false(self):
release = self._make_release(None)
d = mb.release_dict(release)
self.assertFalse(d['va'])
d = mb.album_info(release, [])
self.assertFalse(d.va)
def test_detect_various_artists(self):
release = self._make_release(None)
release.artist.id = musicbrainz2.model.VARIOUS_ARTISTS_ID
d = mb.release_dict(release)
self.assertTrue(d['va'])
d = mb.album_info(release, [])
self.assertTrue(d.va)
class QuerySanitationTest(unittest.TestCase):
def test_special_char_escaped(self):