mirror of
https://github.com/clinton-hall/nzbToMedia.git
synced 2025-08-20 21:33:13 -07:00
Update vendored beets to 1.6.0
Updates colorama to 0.4.6 Adds confuse version 1.7.0 Updates jellyfish to 0.9.0 Adds mediafile 0.10.1 Updates munkres to 1.1.4 Updates musicbrainzngs to 0.7.1 Updates mutagen to 1.46.0 Updates pyyaml to 6.0 Updates unidecode to 1.3.6
This commit is contained in:
parent
5073ec0c6f
commit
56c6773c6b
385 changed files with 25143 additions and 18080 deletions
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of beets.
|
||||
# Copyright 2016, Adrian Sampson.
|
||||
#
|
||||
|
@ -15,11 +14,9 @@
|
|||
|
||||
"""Adds Beatport release and track search support to the autotagger
|
||||
"""
|
||||
from __future__ import division, absolute_import, print_function
|
||||
|
||||
import json
|
||||
import re
|
||||
import six
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from requests_oauthlib import OAuth1Session
|
||||
|
@ -28,35 +25,35 @@ from requests_oauthlib.oauth1_session import (TokenRequestDenied, TokenMissing,
|
|||
|
||||
import beets
|
||||
import beets.ui
|
||||
from beets.autotag.hooks import AlbumInfo, TrackInfo, Distance
|
||||
from beets.plugins import BeetsPlugin
|
||||
from beets.util import confit
|
||||
from beets.autotag.hooks import AlbumInfo, TrackInfo
|
||||
from beets.plugins import BeetsPlugin, MetadataSourcePlugin, get_distance
|
||||
import confuse
|
||||
|
||||
|
||||
AUTH_ERRORS = (TokenRequestDenied, TokenMissing, VerifierMissing)
|
||||
USER_AGENT = u'beets/{0} +http://beets.io/'.format(beets.__version__)
|
||||
USER_AGENT = f'beets/{beets.__version__} +https://beets.io/'
|
||||
|
||||
|
||||
class BeatportAPIError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BeatportObject(object):
|
||||
class BeatportObject:
|
||||
def __init__(self, data):
|
||||
self.beatport_id = data['id']
|
||||
self.name = six.text_type(data['name'])
|
||||
self.name = str(data['name'])
|
||||
if 'releaseDate' in data:
|
||||
self.release_date = datetime.strptime(data['releaseDate'],
|
||||
'%Y-%m-%d')
|
||||
if 'artists' in data:
|
||||
self.artists = [(x['id'], six.text_type(x['name']))
|
||||
self.artists = [(x['id'], str(x['name']))
|
||||
for x in data['artists']]
|
||||
if 'genres' in data:
|
||||
self.genres = [six.text_type(x['name'])
|
||||
self.genres = [str(x['name'])
|
||||
for x in data['genres']]
|
||||
|
||||
|
||||
class BeatportClient(object):
|
||||
class BeatportClient:
|
||||
_api_base = 'https://oauth-api.beatport.com'
|
||||
|
||||
def __init__(self, c_key, c_secret, auth_key=None, auth_secret=None):
|
||||
|
@ -109,7 +106,7 @@ class BeatportClient(object):
|
|||
:rtype: (unicode, unicode) tuple
|
||||
"""
|
||||
self.api.parse_authorization_response(
|
||||
"http://beets.io/auth?" + auth_data)
|
||||
"https://beets.io/auth?" + auth_data)
|
||||
access_data = self.api.fetch_access_token(
|
||||
self._make_url('/identity/1/oauth/access-token'))
|
||||
return access_data['oauth_token'], access_data['oauth_token_secret']
|
||||
|
@ -131,7 +128,7 @@ class BeatportClient(object):
|
|||
"""
|
||||
response = self._get('catalog/3/search',
|
||||
query=query, perPage=5,
|
||||
facets=['fieldType:{0}'.format(release_type)])
|
||||
facets=[f'fieldType:{release_type}'])
|
||||
for item in response:
|
||||
if release_type == 'release':
|
||||
if details:
|
||||
|
@ -150,9 +147,11 @@ class BeatportClient(object):
|
|||
:rtype: :py:class:`BeatportRelease`
|
||||
"""
|
||||
response = self._get('/catalog/3/releases', id=beatport_id)
|
||||
release = BeatportRelease(response[0])
|
||||
release.tracks = self.get_release_tracks(beatport_id)
|
||||
return release
|
||||
if response:
|
||||
release = BeatportRelease(response[0])
|
||||
release.tracks = self.get_release_tracks(beatport_id)
|
||||
return release
|
||||
return None
|
||||
|
||||
def get_release_tracks(self, beatport_id):
|
||||
""" Get all tracks for a given release.
|
||||
|
@ -191,7 +190,7 @@ class BeatportClient(object):
|
|||
response = self.api.get(self._make_url(endpoint), params=kwargs)
|
||||
except Exception as e:
|
||||
raise BeatportAPIError("Error connecting to Beatport API: {}"
|
||||
.format(e.message))
|
||||
.format(e))
|
||||
if not response:
|
||||
raise BeatportAPIError(
|
||||
"Error {0.status_code} for '{0.request.path_url}"
|
||||
|
@ -199,21 +198,20 @@ class BeatportClient(object):
|
|||
return response.json()['results']
|
||||
|
||||
|
||||
@six.python_2_unicode_compatible
|
||||
class BeatportRelease(BeatportObject):
|
||||
def __str__(self):
|
||||
if len(self.artists) < 4:
|
||||
artist_str = ", ".join(x[1] for x in self.artists)
|
||||
else:
|
||||
artist_str = "Various Artists"
|
||||
return u"<BeatportRelease: {0} - {1} ({2})>".format(
|
||||
return "<BeatportRelease: {} - {} ({})>".format(
|
||||
artist_str,
|
||||
self.name,
|
||||
self.catalog_number,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return six.text_type(self).encode('utf-8')
|
||||
return str(self).encode('utf-8')
|
||||
|
||||
def __init__(self, data):
|
||||
BeatportObject.__init__(self, data)
|
||||
|
@ -224,26 +222,26 @@ class BeatportRelease(BeatportObject):
|
|||
if 'category' in data:
|
||||
self.category = data['category']
|
||||
if 'slug' in data:
|
||||
self.url = "http://beatport.com/release/{0}/{1}".format(
|
||||
self.url = "https://beatport.com/release/{}/{}".format(
|
||||
data['slug'], data['id'])
|
||||
self.genre = data.get('genre')
|
||||
|
||||
|
||||
@six.python_2_unicode_compatible
|
||||
class BeatportTrack(BeatportObject):
|
||||
def __str__(self):
|
||||
artist_str = ", ".join(x[1] for x in self.artists)
|
||||
return (u"<BeatportTrack: {0} - {1} ({2})>"
|
||||
return ("<BeatportTrack: {} - {} ({})>"
|
||||
.format(artist_str, self.name, self.mix_name))
|
||||
|
||||
def __repr__(self):
|
||||
return six.text_type(self).encode('utf-8')
|
||||
return str(self).encode('utf-8')
|
||||
|
||||
def __init__(self, data):
|
||||
BeatportObject.__init__(self, data)
|
||||
if 'title' in data:
|
||||
self.title = six.text_type(data['title'])
|
||||
self.title = str(data['title'])
|
||||
if 'mixName' in data:
|
||||
self.mix_name = six.text_type(data['mixName'])
|
||||
self.mix_name = str(data['mixName'])
|
||||
self.length = timedelta(milliseconds=data.get('lengthMs', 0) or 0)
|
||||
if not self.length:
|
||||
try:
|
||||
|
@ -252,14 +250,26 @@ class BeatportTrack(BeatportObject):
|
|||
except ValueError:
|
||||
pass
|
||||
if 'slug' in data:
|
||||
self.url = "http://beatport.com/track/{0}/{1}".format(data['slug'],
|
||||
data['id'])
|
||||
self.url = "https://beatport.com/track/{}/{}" \
|
||||
.format(data['slug'], data['id'])
|
||||
self.track_number = data.get('trackNumber')
|
||||
self.bpm = data.get('bpm')
|
||||
self.initial_key = str(
|
||||
(data.get('key') or {}).get('shortName')
|
||||
)
|
||||
|
||||
# Use 'subgenre' and if not present, 'genre' as a fallback.
|
||||
if data.get('subGenres'):
|
||||
self.genre = str(data['subGenres'][0].get('name'))
|
||||
elif data.get('genres'):
|
||||
self.genre = str(data['genres'][0].get('name'))
|
||||
|
||||
|
||||
class BeatportPlugin(BeetsPlugin):
|
||||
data_source = 'Beatport'
|
||||
|
||||
def __init__(self):
|
||||
super(BeatportPlugin, self).__init__()
|
||||
super().__init__()
|
||||
self.config.add({
|
||||
'apikey': '57713c3906af6f5def151b33601389176b37b429',
|
||||
'apisecret': 'b3fe08c93c80aefd749fe871a16cd2bb32e2b954',
|
||||
|
@ -279,7 +289,7 @@ class BeatportPlugin(BeetsPlugin):
|
|||
try:
|
||||
with open(self._tokenfile()) as f:
|
||||
tokendata = json.load(f)
|
||||
except IOError:
|
||||
except OSError:
|
||||
# No token yet. Generate one.
|
||||
token, secret = self.authenticate(c_key, c_secret)
|
||||
else:
|
||||
|
@ -294,22 +304,22 @@ class BeatportPlugin(BeetsPlugin):
|
|||
try:
|
||||
url = auth_client.get_authorize_url()
|
||||
except AUTH_ERRORS as e:
|
||||
self._log.debug(u'authentication error: {0}', e)
|
||||
raise beets.ui.UserError(u'communication with Beatport failed')
|
||||
self._log.debug('authentication error: {0}', e)
|
||||
raise beets.ui.UserError('communication with Beatport failed')
|
||||
|
||||
beets.ui.print_(u"To authenticate with Beatport, visit:")
|
||||
beets.ui.print_("To authenticate with Beatport, visit:")
|
||||
beets.ui.print_(url)
|
||||
|
||||
# Ask for the verifier data and validate it.
|
||||
data = beets.ui.input_(u"Enter the string displayed in your browser:")
|
||||
data = beets.ui.input_("Enter the string displayed in your browser:")
|
||||
try:
|
||||
token, secret = auth_client.get_access_token(data)
|
||||
except AUTH_ERRORS as e:
|
||||
self._log.debug(u'authentication error: {0}', e)
|
||||
raise beets.ui.UserError(u'Beatport token request failed')
|
||||
self._log.debug('authentication error: {0}', e)
|
||||
raise beets.ui.UserError('Beatport token request failed')
|
||||
|
||||
# Save the token for later use.
|
||||
self._log.debug(u'Beatport token {0}, secret {1}', token, secret)
|
||||
self._log.debug('Beatport token {0}, secret {1}', token, secret)
|
||||
with open(self._tokenfile(), 'w') as f:
|
||||
json.dump({'token': token, 'secret': secret}, f)
|
||||
|
||||
|
@ -318,74 +328,80 @@ class BeatportPlugin(BeetsPlugin):
|
|||
def _tokenfile(self):
|
||||
"""Get the path to the JSON file for storing the OAuth token.
|
||||
"""
|
||||
return self.config['tokenfile'].get(confit.Filename(in_app_dir=True))
|
||||
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):
|
||||
def candidates(self, items, artist, release, va_likely, extra_tags=None):
|
||||
"""Returns a list of AlbumInfo objects for beatport search results
|
||||
matching release and artist (if not various).
|
||||
"""
|
||||
if va_likely:
|
||||
query = release
|
||||
else:
|
||||
query = '%s %s' % (artist, release)
|
||||
query = f'{artist} {release}'
|
||||
try:
|
||||
return self._get_releases(query)
|
||||
except BeatportAPIError as e:
|
||||
self._log.debug(u'API Error: {0} (query: {1})', e, query)
|
||||
self._log.debug('API Error: {0} (query: {1})', e, query)
|
||||
return []
|
||||
|
||||
def item_candidates(self, item, artist, title):
|
||||
"""Returns a list of TrackInfo objects for beatport search results
|
||||
matching title and artist.
|
||||
"""
|
||||
query = '%s %s' % (artist, title)
|
||||
query = f'{artist} {title}'
|
||||
try:
|
||||
return self._get_tracks(query)
|
||||
except BeatportAPIError as e:
|
||||
self._log.debug(u'API Error: {0} (query: {1})', e, query)
|
||||
self._log.debug('API Error: {0} (query: {1})', e, query)
|
||||
return []
|
||||
|
||||
def album_for_id(self, release_id):
|
||||
"""Fetches a release by its Beatport ID and returns an AlbumInfo object
|
||||
or None if the release is not found.
|
||||
or None if the query is not a valid ID or release is not found.
|
||||
"""
|
||||
self._log.debug(u'Searching for release {0}', release_id)
|
||||
self._log.debug('Searching for release {0}', release_id)
|
||||
match = re.search(r'(^|beatport\.com/release/.+/)(\d+)$', release_id)
|
||||
if not match:
|
||||
self._log.debug('Not a valid Beatport release ID.')
|
||||
return None
|
||||
release = self.client.get_release(match.group(2))
|
||||
album = self._get_album_info(release)
|
||||
return album
|
||||
if release:
|
||||
return self._get_album_info(release)
|
||||
return None
|
||||
|
||||
def track_for_id(self, track_id):
|
||||
"""Fetches a track by its Beatport ID and returns a TrackInfo object
|
||||
or None if the track is not found.
|
||||
or None if the track is not a valid Beatport ID or track is not found.
|
||||
"""
|
||||
self._log.debug(u'Searching for track {0}', track_id)
|
||||
self._log.debug('Searching for track {0}', track_id)
|
||||
match = re.search(r'(^|beatport\.com/track/.+/)(\d+)$', track_id)
|
||||
if not match:
|
||||
self._log.debug('Not a valid Beatport track ID.')
|
||||
return None
|
||||
bp_track = self.client.get_track(match.group(2))
|
||||
track = self._get_track_info(bp_track)
|
||||
return track
|
||||
if bp_track is not None:
|
||||
return self._get_track_info(bp_track)
|
||||
return None
|
||||
|
||||
def _get_releases(self, query):
|
||||
"""Returns a list of AlbumInfo objects for a beatport search query.
|
||||
|
@ -408,7 +424,7 @@ class BeatportPlugin(BeetsPlugin):
|
|||
va = len(release.artists) > 3
|
||||
artist, artist_id = self._get_artist(release.artists)
|
||||
if va:
|
||||
artist = u"Various Artists"
|
||||
artist = "Various Artists"
|
||||
tracks = [self._get_track_info(x) for x in release.tracks]
|
||||
|
||||
return AlbumInfo(album=release.name, album_id=release.beatport_id,
|
||||
|
@ -418,40 +434,33 @@ class BeatportPlugin(BeetsPlugin):
|
|||
month=release.release_date.month,
|
||||
day=release.release_date.day,
|
||||
label=release.label_name,
|
||||
catalognum=release.catalog_number, media=u'Digital',
|
||||
data_source=u'Beatport', data_url=release.url)
|
||||
catalognum=release.catalog_number, media='Digital',
|
||||
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.
|
||||
"""
|
||||
title = track.name
|
||||
if track.mix_name != u"Original Mix":
|
||||
title += u" ({0})".format(track.mix_name)
|
||||
if track.mix_name != "Original Mix":
|
||||
title += f" ({track.mix_name})"
|
||||
artist, artist_id = self._get_artist(track.artists)
|
||||
length = track.length.total_seconds()
|
||||
return TrackInfo(title=title, track_id=track.beatport_id,
|
||||
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)
|
||||
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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue