Merge remote-tracking branch 'upstream/master'

This commit is contained in:
kerobaros 2014-10-27 21:21:49 -05:00
commit e9a532ddac
11 changed files with 125 additions and 16 deletions

View file

@ -1272,11 +1272,11 @@ class MediaFile(object):
elif (type(self.mgfile).__name__ == 'M4A' or
type(self.mgfile).__name__ == 'MP4'):
# This hack differentiates AAC and ALAC until we find a more
# deterministic approach. Mutagen only sets the sample rate
# deterministic approach. Mutagen only sets the bitrate
# for AAC files. See:
# https://github.com/sampsyo/beets/pull/295
if hasattr(self.mgfile.info, 'sample_rate') and \
self.mgfile.info.sample_rate > 0:
if hasattr(self.mgfile.info, 'bitrate') and \
self.mgfile.info.bitrate > 0:
self.type = 'aac'
else:
self.type = 'alac'

View file

@ -1105,12 +1105,19 @@ class Filename(Template):
they are relative to the current working directory. This helps
attain the expected behavior when using command-line options.
"""
def __init__(self, default=REQUIRED, cwd=None, relative_to=None):
def __init__(self, default=REQUIRED, cwd=None, relative_to=None,
in_app_dir=False):
""" `relative_to` is the name of a sibling value that is
being validated at the same time.
`in_app_dir` indicates whether the path should be resolved
inside the application's config directory (even when the setting
does not come from a file).
"""
super(Filename, self).__init__(default)
self.cwd, self.relative_to = cwd, relative_to
self.cwd = cwd
self.relative_to = relative_to
self.in_app_dir = in_app_dir
def __repr__(self):
args = []
@ -1124,6 +1131,9 @@ class Filename(Template):
if self.relative_to is not None:
args.append('relative_to=' + repr(self.relative_to))
if self.in_app_dir:
args.append('in_app_dir=True')
return 'Filename({0})'.format(', '.join(args))
def resolve_relative_to(self, view, template):
@ -1198,7 +1208,7 @@ class Filename(Template):
path,
)
elif source.filename:
elif source.filename or self.in_app_dir:
# From defaults: relative to the app's directory.
path = os.path.join(view.root().config_dir(), path)

View file

@ -17,6 +17,7 @@ discogs-client library.
"""
from beets.autotag.hooks import AlbumInfo, TrackInfo, Distance
from beets.plugins import BeetsPlugin
from beets.util import confit
from discogs_client import Release, Client
from discogs_client.exceptions import DiscogsAPIError
from requests.exceptions import ConnectionError
@ -24,6 +25,7 @@ import beets
import logging
import re
import time
import json
log = logging.getLogger('beets')
@ -31,16 +33,62 @@ log = logging.getLogger('beets')
urllib3_logger = logging.getLogger('requests.packages.urllib3')
urllib3_logger.setLevel(logging.CRITICAL)
USER_AGENT = 'beets/{0} +http://beets.radbox.org/'.format(beets.__version__)
class DiscogsPlugin(BeetsPlugin):
def __init__(self):
super(DiscogsPlugin, self).__init__()
self.config.add({
'apikey': 'rAzVUQYRaoFjeBjyWuWZ',
'apisecret': 'plxtUTqoCzwxZpqdPysCwGuBSmZNdZVy',
'tokenfile': 'discogs_token.json',
'source_weight': 0.5,
})
self.discogs_client = Client('beets/%s +http://beets.radbox.org/' %
beets.__version__)
c_key = self.config['apikey'].get(unicode)
c_secret = self.config['apisecret'].get(unicode)
# Get the OAuth token from a file or log in.
try:
with open(self._tokenfile()) as f:
tokendata = json.load(f)
except IOError:
# No token yet. Generate one.
token, secret = self.authenticate(c_key, c_secret)
else:
token = tokendata['token']
secret = tokendata['secret']
self.discogs_client = Client(USER_AGENT, c_key, c_secret,
token, secret)
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))
def authenticate(self, c_key, c_secret):
# Get the link for the OAuth page.
auth_client = Client(USER_AGENT, c_key, c_secret)
_, _, url = auth_client.get_authorize_url()
beets.ui.print_("To authenticate with Discogs, visit:")
beets.ui.print_(url)
# Ask for the code and validate it.
code = beets.ui.input_("Enter the code:")
try:
token, secret = auth_client.get_access_token(code)
except DiscogsAPIError:
raise beets.ui.UserError('Discogs authorization failed')
# Save the token for later use.
log.debug('Discogs token {0}, secret {1}'.format(token, secret))
with open(self._tokenfile(), 'w') as f:
json.dump({'token': token, 'secret': secret}, f)
return token, secret
def album_distance(self, items, album_info, mapping):
"""Returns the album distance.

View file

@ -72,7 +72,7 @@ def update_metadata(item, feat_part, drop_feat):
item.title = new_title
def ft_in_title(item, drop_feat, write):
def ft_in_title(item, drop_feat):
"""Look for featured artists in the item's artist fields and move
them to the title.
"""

View file

@ -101,7 +101,10 @@ def play_music(lib, opts, args):
# Invoke the command and log the output.
output = util.command_output(command)
if output:
log.debug(u'Output of {0}: {1}'.format(command[0], output))
log.debug(u'Output of {0}: {1}'.format(
util.displayable_path(command[0]),
output.decode('utf8', 'ignore'),
))
ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type))

View file

@ -22,6 +22,13 @@ class TypesPlugin(BeetsPlugin):
@property
def item_types(self):
return self._types()
@property
def album_types(self):
return self._types()
def _types(self):
if not self.config.exists():
return {}

View file

@ -50,6 +50,11 @@ Fixes:
during the import process.
* Fix a crash in the autotagger when files had only whitespace in their
metadata.
* :doc:`/plugins/discogs`: Authenticate with the Discogs server. The plugin
now requires a Discogs account due to new API restrictions. Thanks to
:user:`multikatt`. :bug:`1027`, :bug:`1040`
* :doc:`/plugins/play`: Fix a potential crash when the command outputs special
characters. :bug:`1041`
1.3.8 (September 17, 2014)

View file

@ -2,7 +2,7 @@ AUTHOR = u'Adrian Sampson'
# General configuration
extensions = ['sphinx.ext.autodoc']
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.extlinks']
exclude_patterns = ['_build']
source_suffix = '.rst'
@ -16,20 +16,23 @@ release = '1.3.9'
pygments_style = 'sphinx'
# Options for HTML output
# External links to the bug tracker.
extlinks = {
'bug': ('https://github.com/sampsyo/beets/issues/%s', '#'),
'user': ('https://github.com/%s', ''),
}
# Options for HTML output
html_theme = 'default'
htmlhelp_basename = 'beetsdoc'
# Options for LaTeX output
latex_documents = [
('index', 'beets.tex', u'beets Documentation',
AUTHOR, 'manual'),
]
# Options for manual page output
man_pages = [
('reference/cli', 'beet', u'music tagger and library organizer',
[AUTHOR], 1),

View file

@ -14,8 +14,13 @@ install the `discogs-client`_ library by typing::
pip install discogs-client
That's it! Matches from Discogs will now show up during import alongside
matches from MusicBrainz.
You will also need to register for a `Discogs`_ account. The first time you
run beets after enabling the plugin, it will ask you to authorize with Discogs
by visiting the site in a browser. Subsequent runs will not require
re-authorization.
Matches from Discogs will now show up during import alongside matches from
MusicBrainz.
If you have a Discogs ID for an album you want to tag, you can also enter it
at the "enter Id" prompt in the importer.

View file

@ -15,3 +15,12 @@ Here's an example::
types:
rating: int
Now you can assign numeric ratings to tracks and albums and use :ref:`range
queries <numericquery>` to filter them.::
beet modify "My favorite track" rating=5
beet ls rating:4..5
beet modify --album "My favorite album" rating=5
beet modify --album rating:4..5

View file

@ -47,6 +47,22 @@ class TypesPluginTest(unittest.TestCase, TestHelper):
out = self.list('myint:1..3')
self.assertIn('aaa', out)
def test_album_integer_modify_and_query(self):
self.config['types'] = {'myint': 'int'}
album = self.add_album(albumartist='aaa')
# Do not match unset values
out = self.list_album('myint:1..3')
self.assertEqual('', out)
self.modify('-a', 'myint=2')
album.load()
self.assertEqual(album['myint'], 2)
# Match in range
out = self.list_album('myint:1..3')
self.assertIn('aaa', out)
def test_float_modify_and_query(self):
self.config['types'] = {'myfloat': 'float'}
item = self.add_item(artist='aaa')
@ -121,6 +137,9 @@ class TypesPluginTest(unittest.TestCase, TestHelper):
def list(self, query, fmt='$artist - $album - $title'):
return self.run_with_output('ls', '-f', fmt, query).strip()
def list_album(self, query, fmt='$albumartist - $album - $title'):
return self.run_with_output('ls', '-a', '-f', fmt, query).strip()
def mktime(*args):
return time.mktime(datetime(*args).timetuple())