Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BPSyncPlugin #3389

Merged
merged 18 commits into from
Oct 6, 2019
2 changes: 1 addition & 1 deletion beets/autotag/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def apply_metadata(album_info, mapping):
'mb_workid',
'work_disambig',
'bpm',
'musical_key',
'initial_key',
'genre'
)
}
Expand Down
4 changes: 2 additions & 2 deletions beets/autotag/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def __init__(self, title, track_id, release_track_id=None, artist=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,
musical_key=None, genre=None):
initial_key=None, genre=None):
self.title = title
self.track_id = track_id
self.release_track_id = release_track_id
Expand All @@ -206,7 +206,7 @@ def __init__(self, title, track_id, release_track_id=None, artist=None,
self.mb_workid = mb_workid
self.work_disambig = work_disambig
self.bpm = bpm
self.musical_key = musical_key
self.initial_key = initial_key
self.genre = genre

# As above, work around a bug in python-musicbrainz-ngs.
Expand Down
60 changes: 50 additions & 10 deletions beets/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def add_media_field(self, name, descriptor):

``descriptor`` must be an instance of ``mediafile.MediaField``.
"""
# Defer impor to prevent circular dependency
# Defer import to prevent circular dependency
from beets import library
mediafile.MediaFile.add_field(name, descriptor)
library.Item._media_fields.add(name)
Expand Down Expand Up @@ -590,6 +590,36 @@ def get_distance(config, data_source, info):
return dist


def apply_item_changes(lib, item, move, pretend, write):
"""Store, move, and write the item according to the arguments.

:param lib: beets library.
:type lib: beets.library.Library
:param item: Item whose changes to apply.
:type item: beets.library.Item
:param move: Move the item if it's in the library.
:type move: bool
:param pretend: Return without moving, writing, or storing the item's
metadata.
:type pretend: bool
:param write: Write the item's metadata to its media file.
:type write: bool
"""
if pretend:
return

from beets import util

# Move the item if it's in the library.
if move and lib.directory in util.ancestry(item.path):
item.move(with_album=False)

if write:
item.try_write()

item.store()


@six.add_metaclass(abc.ABCMeta)
class MetadataSourcePlugin(object):
def __init__(self):
Expand Down Expand Up @@ -633,13 +663,20 @@ def get_artist(artists, id_key='id', name_key='name'):
"""Returns an artist string (all artists) and an artist_id (the main
artist) for a list of artist object dicts.

:param artists: Iterable of artist dicts returned by API.
:type artists: list[dict]
:param id_key: Key corresponding to ``artist_id`` value.
:type id_key: str
:param name_key: Keys corresponding to values to concatenate
for ``artist``.
:type name_key: str
For each artist, this function moves articles (such as 'a', 'an',
and 'the') to the front and strips trailing disambiguation numbers. It
returns a tuple containing the comma-separated string of all
normalized artists and the ``id`` of the main/first artist.

:param artists: Iterable of artist dicts or lists returned by API.
:type artists: list[dict] or list[list]
:param id_key: Key or index corresponding to the value of ``id`` for
the main/first artist. Defaults to 'id'.
:type id_key: str or int
:param name_key: Key or index corresponding to values of names
to concatenate for the artist string (containing all artists).
Defaults to 'name'.
:type name_key: str or int
:return: Normalized artist string.
:rtype: str
"""
Expand All @@ -649,6 +686,8 @@ def get_artist(artists, id_key='id', name_key='name'):
if not artist_id:
artist_id = artist[id_key]
name = artist[name_key]
# Strip disambiguation number.
name = re.sub(r' \(\d+\)$', '', name)
rhlahuja marked this conversation as resolved.
Show resolved Hide resolved
# Move articles to the front.
name = re.sub(r'^(.*?), (a|an|the)$', r'\2 \1', name, flags=re.I)
artist_names.append(name)
Expand Down Expand Up @@ -694,8 +733,9 @@ def candidates(self, items, artist, album, va_likely):
query_filters = {'album': album}
if not va_likely:
query_filters['artist'] = artist
albums = self._search_api(query_type='album', filters=query_filters)
return [self.album_for_id(album_id=a['id']) for a in albums]
results = self._search_api(query_type='album', filters=query_filters)
albums = [self.album_for_id(album_id=r['id']) for r in results]
return [a for a in albums if a is not None]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bit logic along with this early return were added to exclude "empty" (0-track) albums I encountered such as https://www.deezer.com/us/album/6979394.


def item_candidates(self, item, artist, title):
"""Returns a list of TrackInfo objects for Search API results
Expand Down
55 changes: 26 additions & 29 deletions beetsplug/beatport.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@

import beets
import beets.ui
from beets.autotag.hooks import AlbumInfo, TrackInfo, Distance
from beets.plugins import BeetsPlugin
from beets.autotag.hooks import AlbumInfo, TrackInfo
from beets.plugins import BeetsPlugin, MetadataSourcePlugin, get_distance
import confuse


Expand Down Expand Up @@ -228,6 +228,7 @@ def __init__(self, data):
if 'slug' in data:
self.url = "https://beatport.com/release/{0}/{1}".format(
data['slug'], data['id'])
self.genre = data.get('genre')


@six.python_2_unicode_compatible
Expand Down Expand Up @@ -258,7 +259,7 @@ def __init__(self, data):
.format(data['slug'], data['id'])
self.track_number = data.get('trackNumber')
self.bpm = data.get('bpm')
self.musical_key = six.text_type(
self.initial_key = six.text_type(
(data.get('key') or {}).get('shortName')
)

Expand All @@ -270,6 +271,8 @@ def __init__(self, data):


class BeatportPlugin(BeetsPlugin):
data_source = 'Beatport'

def __init__(self):
super(BeatportPlugin, self).__init__()
self.config.add({
Expand Down Expand Up @@ -333,22 +336,24 @@ def _tokenfile(self):
return self.config['tokenfile'].get(confuse.Filename(in_app_dir=True))

def album_distance(self, items, album_info, mapping):
"""Returns the beatport source weight and the maximum source weight
"""Returns the Beatport source weight and the maximum source weight
for albums.
"""
dist = Distance()
if album_info.data_source == 'Beatport':
dist.add('source', self.config['source_weight'].as_number())
return dist
return get_distance(
data_source=self.data_source,
info=album_info,
config=self.config
)

def track_distance(self, item, track_info):
"""Returns the beatport source weight and the maximum source weight
"""Returns the Beatport source weight and the maximum source weight
for individual tracks.
"""
dist = Distance()
if track_info.data_source == 'Beatport':
dist.add('source', self.config['source_weight'].as_number())
return dist
return get_distance(
data_source=self.data_source,
info=track_info,
config=self.config
)

def candidates(self, items, artist, release, va_likely):
"""Returns a list of AlbumInfo objects for beatport search results
Expand Down Expand Up @@ -435,7 +440,8 @@ def _get_album_info(self, release):
day=release.release_date.day,
label=release.label_name,
catalognum=release.catalog_number, media=u'Digital',
data_source=u'Beatport', data_url=release.url)
data_source=self.data_source, data_url=release.url,
genre=release.genre)

def _get_track_info(self, track):
"""Returns a TrackInfo object for a Beatport Track object.
Expand All @@ -449,26 +455,17 @@ def _get_track_info(self, track):
artist=artist, artist_id=artist_id,
length=length, index=track.track_number,
medium_index=track.track_number,
data_source=u'Beatport', data_url=track.url,
bpm=track.bpm, musical_key=track.musical_key)
data_source=self.data_source, data_url=track.url,
bpm=track.bpm, initial_key=track.initial_key,
genre=track.genre)

def _get_artist(self, artists):
"""Returns an artist string (all artists) and an artist_id (the main
artist) for a list of Beatport release or track artists.
"""
artist_id = None
bits = []
for artist in artists:
if not artist_id:
artist_id = artist[0]
name = artist[1]
# Strip disambiguation number.
name = re.sub(r' \(\d+\)$', '', name)
# Move articles to the front.
name = re.sub(r'^(.*?), (a|an|the)$', r'\2 \1', name, flags=re.I)
bits.append(name)
artist = ', '.join(bits).replace(' ,', ',') or None
return artist, artist_id
return MetadataSourcePlugin.get_artist(
artists=artists, id_key=0, name_key=1
)

def _get_tracks(self, query):
"""Returns a list of TrackInfo objects for a Beatport query.
Expand Down
Loading