Update multiple plugins: pass the logger around

This commit is contained in:
Bruno Cauet 2015-01-06 11:25:51 +01:00
parent 7d58a38428
commit 32673b87e7
10 changed files with 289 additions and 322 deletions

View file

@ -16,13 +16,11 @@
"""
import shlex
from beets import logging
from beets.plugins import BeetsPlugin
from beets.ui import decargs, print_obj, vararg_callback, Subcommand, UserError
from beets.util import command_output, displayable_path, subprocess
PLUGIN = 'duplicates'
log = logging.getLogger(__name__)
def _process_item(item, lib, copy=False, move=False, delete=False,
@ -47,7 +45,7 @@ def _process_item(item, lib, copy=False, move=False, delete=False,
print_obj(item, lib, fmt=format)
def _checksum(item, prog):
def _checksum(item, prog, log):
"""Run external `prog` on file path associated with `item`, cache
output as flexattr on a key that is the name of the program, and
return the key, checksum tuple.
@ -73,7 +71,7 @@ def _checksum(item, prog):
return key, checksum
def _group_by(objs, keys):
def _group_by(objs, keys, log):
"""Return a dictionary with keys arbitrary concatenations of attributes and
values lists of objects (Albums or Items) with those keys.
"""
@ -92,11 +90,11 @@ def _group_by(objs, keys):
return counts
def _duplicates(objs, keys, full):
def _duplicates(objs, keys, full, log):
"""Generate triples of keys, duplicate counts, and constituent objects.
"""
offset = 0 if full else 1
for k, objs in _group_by(objs, keys).iteritems():
for k, objs in _group_by(objs, keys, log).iteritems():
if len(objs) > 1:
yield (k, len(objs) - offset, objs[offset:])
@ -214,12 +212,13 @@ class DuplicatesPlugin(BeetsPlugin):
'duplicates: "checksum" option must be a command'
)
for i in items:
k, _ = _checksum(i, checksum)
k, _ = self._checksum(i, checksum, self._log)
keys = [k]
for obj_id, obj_count, objs in _duplicates(items,
keys=keys,
full=full):
full=full,
log=self._log):
if obj_id: # Skip empty IDs.
for o in objs:
_process_item(o, lib,

View file

@ -20,9 +20,6 @@ from beets import plugins
from beets import ui
from beets.util import displayable_path
from beets import config
from beets import logging
log = logging.getLogger(__name__)
def split_on_feat(artist):
@ -46,69 +43,6 @@ def contains_feat(title):
return bool(re.search(plugins.feat_tokens(), title, flags=re.IGNORECASE))
def update_metadata(item, feat_part, drop_feat):
"""Choose how to add new artists to the title and set the new
metadata. Also, print out messages about any changes that are made.
If `drop_feat` is set, then do not add the artist to the title; just
remove it from the artist field.
"""
# In all cases, update the artist fields.
log.info(u'artist: {0} -> {1}', item.artist, item.albumartist)
item.artist = item.albumartist
if item.artist_sort:
# Just strip the featured artist from the sort name.
item.artist_sort, _ = split_on_feat(item.artist_sort)
# Only update the title if it does not already contain a featured
# artist and if we do not drop featuring information.
if not drop_feat and not contains_feat(item.title):
new_title = u"{0} feat. {1}".format(item.title, feat_part)
log.info(u'title: {0} -> {1}', item.title, new_title)
item.title = new_title
def ft_in_title(item, drop_feat):
"""Look for featured artists in the item's artist fields and move
them to the title.
"""
artist = item.artist.strip()
albumartist = item.albumartist.strip()
# Check whether there is a featured artist on this track and the
# artist field does not exactly match the album artist field. In
# that case, we attempt to move the featured artist to the title.
_, featured = split_on_feat(artist)
if featured and albumartist != artist and albumartist:
log.info(displayable_path(item.path))
feat_part = None
# Look for the album artist in the artist field. If it's not
# present, give up.
albumartist_split = artist.split(albumartist, 1)
if len(albumartist_split) <= 1:
log.info('album artist not present in artist')
# If the last element of the split (the right-hand side of the
# album artist) is nonempty, then it probably contains the
# featured artist.
elif albumartist_split[-1] != '':
# Extract the featured artist from the right-hand side.
_, feat_part = split_on_feat(albumartist_split[-1])
# Otherwise, if there's nothing on the right-hand side, look for a
# featuring artist on the left-hand side.
else:
lhs, rhs = split_on_feat(albumartist_split[0])
if rhs:
feat_part = lhs
# If we have a featuring artist, move it to the title.
if feat_part:
update_metadata(item, feat_part, drop_feat)
else:
log.info(u'no featuring artists found')
class FtInTitlePlugin(plugins.BeetsPlugin):
def __init__(self):
super(FtInTitlePlugin, self).__init__()
@ -138,7 +72,7 @@ class FtInTitlePlugin(plugins.BeetsPlugin):
write = config['import']['write'].get(bool)
for item in lib.items(ui.decargs(args)):
ft_in_title(item, drop_feat)
self.ft_in_title(item, drop_feat)
item.store()
if write:
item.try_write()
@ -152,5 +86,66 @@ class FtInTitlePlugin(plugins.BeetsPlugin):
drop_feat = self.config['drop'].get(bool)
for item in task.imported_items():
ft_in_title(item, drop_feat)
self.ft_in_title(item, drop_feat)
item.store()
def update_metadata(self, item, feat_part, drop_feat):
"""Choose how to add new artists to the title and set the new
metadata. Also, print out messages about any changes that are made.
If `drop_feat` is set, then do not add the artist to the title; just
remove it from the artist field.
"""
# In all cases, update the artist fields.
self._log.info(u'artist: {0} -> {1}', item.artist, item.albumartist)
item.artist = item.albumartist
if item.artist_sort:
# Just strip the featured artist from the sort name.
item.artist_sort, _ = split_on_feat(item.artist_sort)
# Only update the title if it does not already contain a featured
# artist and if we do not drop featuring information.
if not drop_feat and not contains_feat(item.title):
new_title = u"{0} feat. {1}".format(item.title, feat_part)
self._log.info(u'title: {0} -> {1}', item.title, new_title)
item.title = new_title
def ft_in_title(self, item, drop_feat):
"""Look for featured artists in the item's artist fields and move
them to the title.
"""
artist = item.artist.strip()
albumartist = item.albumartist.strip()
# Check whether there is a featured artist on this track and the
# artist field does not exactly match the album artist field. In
# that case, we attempt to move the featured artist to the title.
_, featured = split_on_feat(artist)
if featured and albumartist != artist and albumartist:
self._log.info(displayable_path(item.path))
feat_part = None
# Look for the album artist in the artist field. If it's not
# present, give up.
albumartist_split = artist.split(albumartist, 1)
if len(albumartist_split) <= 1:
self._log.info('album artist not present in artist')
# If the last element of the split (the right-hand side of the
# album artist) is nonempty, then it probably contains the
# featured artist.
elif albumartist_split[-1] != '':
# Extract the featured artist from the right-hand side.
_, feat_part = split_on_feat(albumartist_split[-1])
# Otherwise, if there's nothing on the right-hand side, look for a
# featuring artist on the left-hand side.
else:
lhs, rhs = split_on_feat(albumartist_split[0])
if rhs:
feat_part = lhs
# If we have a featuring artist, move it to the title.
if feat_part:
self.update_metadata(item, feat_part, drop_feat)
else:
self._log.info(u'no featuring artists found')

View file

@ -17,56 +17,12 @@
import os
from beets import logging
from beets.plugins import BeetsPlugin
from beets import ui
from beets import mediafile
from beets.util import displayable_path, normpath, syspath
log = logging.getLogger(__name__)
def run(lib, opts, args):
"""Print tag info or library data for each file referenced by args.
Main entry point for the `beet info ARGS...` command.
If an argument is a path pointing to an existing file, then the tags
of that file are printed. All other arguments are considered
queries, and for each item matching all those queries the tags from
the file are printed.
If `opts.summarize` is true, the function merges all tags into one
dictionary and only prints that. If two files have different values
for the same tag, the value is set to '[various]'
"""
if opts.library:
data_collector = library_data
else:
data_collector = tag_data
first = True
summary = {}
for data_emitter in data_collector(lib, ui.decargs(args)):
try:
data = data_emitter()
except mediafile.UnreadableFileError as ex:
log.error(u'cannot read file: {0}', ex.message)
continue
if opts.summarize:
update_summary(summary, data)
else:
if not first:
ui.print_()
print_data(data)
first = False
if opts.summarize:
print_data(summary)
def tag_data(lib, args):
query = []
for arg in args:
@ -143,9 +99,48 @@ class InfoPlugin(BeetsPlugin):
def commands(self):
cmd = ui.Subcommand('info', help='show file metadata')
cmd.func = run
cmd.func = self.run
cmd.parser.add_option('-l', '--library', action='store_true',
help='show library fields instead of tags')
cmd.parser.add_option('-s', '--summarize', action='store_true',
help='summarize the tags of all files')
return [cmd]
def run(self, lib, opts, args):
"""Print tag info or library data for each file referenced by args.
Main entry point for the `beet info ARGS...` command.
If an argument is a path pointing to an existing file, then the tags
of that file are printed. All other arguments are considered
queries, and for each item matching all those queries the tags from
the file are printed.
If `opts.summarize` is true, the function merges all tags into one
dictionary and only prints that. If two files have different values
for the same tag, the value is set to '[various]'
"""
if opts.library:
data_collector = library_data
else:
data_collector = tag_data
first = True
summary = {}
for data_emitter in data_collector(lib, ui.decargs(args)):
try:
data = data_emitter()
except mediafile.UnreadableFileError as ex:
self._log.error(u'cannot read file: {0}', ex.message)
continue
if opts.summarize:
update_summary(summary, data)
else:
if not first:
ui.print_()
print_data(data)
first = False
if opts.summarize:
print_data(summary)

View file

@ -18,9 +18,7 @@ import traceback
import itertools
from beets.plugins import BeetsPlugin
from beets import config, logging
log = logging.getLogger(__name__)
from beets import config
FUNC_NAME = u'__INLINE_FUNC__'
@ -49,7 +47,32 @@ def _compile_func(body):
return env[FUNC_NAME]
def compile_inline(python_code, album):
class InlinePlugin(BeetsPlugin):
def __init__(self):
super(InlinePlugin, self).__init__()
config.add({
'pathfields': {}, # Legacy name.
'item_fields': {},
'album_fields': {},
})
# Item fields.
for key, view in itertools.chain(config['item_fields'].items(),
config['pathfields'].items()):
self._log.debug(u'adding item field {0}', key)
func = self.compile_inline(view.get(unicode), False)
if func is not None:
self.template_fields[key] = func
# Album fields.
for key, view in config['album_fields'].items():
self._log.debug(u'adding album field {0}', key)
func = self.compile_inline(view.get(unicode), True)
if func is not None:
self.album_template_fields[key] = func
def compile_inline(self, python_code, album):
"""Given a Python expression or function body, compile it as a path
field function. The returned function takes a single argument, an
Item, and returns a Unicode string. If the expression cannot be
@ -63,8 +86,8 @@ def compile_inline(python_code, album):
try:
func = _compile_func(python_code)
except SyntaxError:
log.error(u'syntax error in inline field definition:\n{0}',
traceback.format_exc())
self._log.error(u'syntax error in inline field definition:\n'
u'{0}', traceback.format_exc())
return
else:
is_expr = False
@ -96,29 +119,3 @@ def compile_inline(python_code, album):
except Exception as exc:
raise InlineError(python_code, exc)
return _func_func
class InlinePlugin(BeetsPlugin):
def __init__(self):
super(InlinePlugin, self).__init__()
config.add({
'pathfields': {}, # Legacy name.
'item_fields': {},
'album_fields': {},
})
# Item fields.
for key, view in itertools.chain(config['item_fields'].items(),
config['pathfields'].items()):
self._log.debug(u'adding item field {0}', key)
func = compile_inline(view.get(unicode), False)
if func is not None:
self.template_fields[key] = func
# Album fields.
for key, view in config['album_fields'].items():
self._log.debug(u'adding album field {0}', key)
func = compile_inline(view.get(unicode), True)
if func is not None:
self.album_template_fields[key] = func

View file

@ -24,14 +24,12 @@ import pylast
import os
import yaml
from beets import logging
from beets import plugins
from beets import ui
from beets.util import normpath, plurality
from beets import config
from beets import library
log = logging.getLogger(__name__)
LASTFM = pylast.LastFMNetwork(api_key=plugins.LASTFM_KEY)
@ -53,40 +51,8 @@ def deduplicate(seq):
return [x for x in seq if x not in seen and not seen.add(x)]
# Core genre identification routine.
def _tags_for(obj, min_weight=None):
"""Given a pylast entity (album or track), return a list of
tag names for that entity. Return an empty list if the entity is
not found or another error occurs.
If `min_weight` is specified, tags are filtered by weight.
"""
try:
# Work around an inconsistency in pylast where
# Album.get_top_tags() does not return TopItem instances.
# https://code.google.com/p/pylast/issues/detail?id=85
if isinstance(obj, pylast.Album):
res = super(pylast.Album, obj).get_top_tags()
else:
res = obj.get_top_tags()
except PYLAST_EXCEPTIONS as exc:
log.debug(u'last.fm error: {0}', exc)
return []
# Filter by weight (optionally).
if min_weight:
res = [el for el in res if (el.weight or 0) >= min_weight]
# Get strings from tags.
res = [el.item.get_name().lower() for el in res]
return res
# Canonicalization tree processing.
def flatten_tree(elem, path, branches):
"""Flatten nested lists/dictionaries into lists of strings
(branches).
@ -225,7 +191,7 @@ class LastGenrePlugin(plugins.BeetsPlugin):
can be found. Ex. 'Electronic, House, Dance'
"""
min_weight = self.config['min_weight'].get(int)
return self._resolve_genres(_tags_for(lastfm_obj, min_weight))
return self._resolve_genres(self._tags_for(lastfm_obj, min_weight))
def _is_allowed(self, genre):
"""Determine whether the genre is present in the whitelist,
@ -371,8 +337,8 @@ class LastGenrePlugin(plugins.BeetsPlugin):
for album in lib.albums(ui.decargs(args)):
album.genre, src = self._get_genre(album)
log.info(u'genre for album {0} - {1} ({2}): {3}',
album.albumartist, album.album, src, album.genre)
self._log.info(u'genre for album {0.albumartist} - {0.album} '
u'({1}): {0.genre}', album, src)
album.store()
for item in album.items():
@ -381,8 +347,8 @@ class LastGenrePlugin(plugins.BeetsPlugin):
if 'track' in self.sources:
item.genre, src = self._get_genre(item)
item.store()
log.info(u'genre for track {0} - {1} ({2}): {3}',
item.artist, item.title, src, item.genre)
self._log.info(u'genre for track {0.artist} - {0.tit'
u'le} ({1}): {0.genre}', item, src)
if write:
item.try_write()
@ -395,20 +361,50 @@ class LastGenrePlugin(plugins.BeetsPlugin):
if task.is_album:
album = task.album
album.genre, src = self._get_genre(album)
log.debug(u'added last.fm album genre ({0}): {1}',
self._log.debug(u'added last.fm album genre ({0}): {1}',
src, album.genre)
album.store()
if 'track' in self.sources:
for item in album.items():
item.genre, src = self._get_genre(item)
log.debug(u'added last.fm item genre ({0}): {1}',
self._log.debug(u'added last.fm item genre ({0}): {1}',
src, item.genre)
item.store()
else:
item = task.item
item.genre, src = self._get_genre(item)
log.debug(u'added last.fm item genre ({0}): {1}',
self._log.debug(u'added last.fm item genre ({0}): {1}',
src, item.genre)
item.store()
def _tags_for(self, obj, min_weight=None):
"""Core genre identification routine.
Given a pylast entity (album or track), return a list of
tag names for that entity. Return an empty list if the entity is
not found or another error occurs.
If `min_weight` is specified, tags are filtered by weight.
"""
try:
# Work around an inconsistency in pylast where
# Album.get_top_tags() does not return TopItem instances.
# https://code.google.com/p/pylast/issues/detail?id=85
if isinstance(obj, pylast.Album):
res = super(pylast.Album, obj).get_top_tags()
else:
res = obj.get_top_tags()
except PYLAST_EXCEPTIONS as exc:
self._log.debug(u'last.fm error: {0}', exc)
return []
# Filter by weight (optionally).
if min_weight:
res = [el for el in res if (el.weight or 0) >= min_weight]
# Get strings from tags.
res = [el.item.get_name().lower() for el in res]
return res

View file

@ -17,10 +17,8 @@ from beets import ui
from beets import dbcore
from beets import config
from beets import plugins
from beets import logging
from beets.dbcore import types
log = logging.getLogger(__name__)
API_URL = 'http://ws.audioscrobbler.com/2.0/'
@ -43,13 +41,13 @@ class LastImportPlugin(plugins.BeetsPlugin):
cmd = ui.Subcommand('lastimport', help='import last.fm play-count')
def func(lib, opts, args):
import_lastfm(lib)
import_lastfm(lib, self._log)
cmd.func = func
return [cmd]
def import_lastfm(lib):
def import_lastfm(lib, log):
user = config['lastfm']['user']
per_page = config['lastimport']['per_page']
@ -78,7 +76,8 @@ def import_lastfm(lib):
# It means nothing to us!
raise ui.UserError('Last.fm reported no data.')
found, unknown = process_tracks(lib, page['tracks']['track'])
track = page['tracks']['track']
found, unknown = process_tracks(lib, track, log)
found_total += found
unknown_total += unknown
break
@ -112,7 +111,7 @@ def fetch_tracks(user, page, limit):
}).json()
def process_tracks(lib, tracks):
def process_tracks(lib, tracks, log):
total = len(tracks)
total_found = 0
total_fails = 0

View file

@ -16,7 +16,6 @@ from __future__ import print_function
from beets.plugins import BeetsPlugin
from beets.ui import Subcommand
from beets import logging
from beets import ui
from beets import config
import musicbrainzngs
@ -26,8 +25,6 @@ import re
SUBMISSION_CHUNK_SIZE = 200
UUID_REGEX = r'^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$'
log = logging.getLogger(__name__)
def mb_call(func, *args, **kwargs):
"""Call a MusicBrainz API function and catch exceptions.
@ -54,7 +51,32 @@ def submit_albums(collection_id, release_ids):
)
def update_album_list(album_list):
class MusicBrainzCollectionPlugin(BeetsPlugin):
def __init__(self):
super(MusicBrainzCollectionPlugin, self).__init__()
musicbrainzngs.auth(
config['musicbrainz']['user'].get(unicode),
config['musicbrainz']['pass'].get(unicode),
)
self.config.add({'auto': False})
if self.config['auto']:
self._import_stages = [self.imported]
def commands(self):
mbupdate = Subcommand('mbupdate', help='Update MusicBrainz collection')
mbupdate.func = self.update_collection
return [mbupdate]
def update_collection(self, lib, opts, args):
self.update_album_list(lib.albums())
def imported(self, session, task):
"""Add each imported album to the collection.
"""
if task.is_album:
self.update_album_list([task.album])
def update_album_list(self, album_list):
"""Update the MusicBrainz colleciton from a list of Beets albums
"""
# Get the available collections.
@ -79,39 +101,9 @@ def update_album_list(album_list):
if re.match(UUID_REGEX, aid):
album_ids.append(aid)
else:
log.info(u'skipping invalid MBID: {0}', aid)
self._log.info(u'skipping invalid MBID: {0}', aid)
# Submit to MusicBrainz.
print('Updating MusicBrainz collection {0}...'.format(collection_id))
submit_albums(collection_id, album_ids)
print('...MusicBrainz collection updated.')
def update_collection(lib, opts, args):
update_album_list(lib.albums())
update_mb_collection_cmd = Subcommand('mbupdate',
help='Update MusicBrainz collection')
update_mb_collection_cmd.func = update_collection
class MusicBrainzCollectionPlugin(BeetsPlugin):
def __init__(self):
super(MusicBrainzCollectionPlugin, self).__init__()
musicbrainzngs.auth(
config['musicbrainz']['user'].get(unicode),
config['musicbrainz']['pass'].get(unicode),
)
self.config.add({'auto': False})
if self.config['auto']:
self._import_stages = [self.imported]
def commands(self):
return [update_mb_collection_cmd]
def imported(self, session, task):
"""Add each imported album to the collection.
"""
if task.is_album:
update_album_list([task.album])

View file

@ -14,14 +14,11 @@
"""List missing tracks.
"""
from beets import logging
from beets.autotag import hooks
from beets.library import Item
from beets.plugins import BeetsPlugin
from beets.ui import decargs, print_obj, Subcommand
log = logging.getLogger(__name__)
def _missing_count(album):
"""Return number of missing items in `album`.
@ -29,23 +26,6 @@ def _missing_count(album):
return (album.tracktotal or 0) - len(album.items())
def _missing(album):
"""Query MusicBrainz to determine items missing from `album`.
"""
item_mbids = map(lambda x: x.mb_trackid, album.items())
if len([i for i in album.items()]) < album.tracktotal:
# fetch missing items
# TODO: Implement caching that without breaking other stuff
album_info = hooks.album_for_mbid(album.mb_albumid)
for track_info in getattr(album_info, 'tracks', []):
if track_info.track_id not in item_mbids:
item = _item(track_info, album_info, album.id)
log.debug(u'track {1} in album {2}',
track_info.track_id, album_info.album_id)
yield item
def _item(track_info, album_info, album_id):
"""Build and return `item` from `track_info` and `album info`
objects. `item` is missing what fields cannot be obtained from
@ -148,8 +128,24 @@ class MissingPlugin(BeetsPlugin):
print_obj(album, lib, fmt=fmt)
else:
for item in _missing(album):
for item in self._missing(album):
print_obj(item, lib, fmt=fmt)
self._command.func = _miss
return [self._command]
def _missing(self, album):
"""Query MusicBrainz to determine items missing from `album`.
"""
item_mbids = map(lambda x: x.mb_trackid, album.items())
if len([i for i in album.items()]) < album.tracktotal:
# fetch missing items
# TODO: Implement caching that without breaking other stuff
album_info = hooks.album_for_mbid(album.mb_albumid)
for track_info in getattr(album_info, 'tracks', []):
if track_info.track_id not in item_mbids:
item = _item(track_info, album_info, album.id)
self._log.debug(u'track {1} in album {2}',
track_info.track_id, album_info.album_id)
yield item

View file

@ -19,7 +19,6 @@ import select
import time
import os
from beets import logging
from beets import ui
from beets import config
from beets import plugins
@ -27,8 +26,6 @@ from beets import library
from beets.util import displayable_path
from beets.dbcore import types
log = logging.getLogger(__name__)
# If we lose the connection, how many times do we want to retry and how
# much time should we wait between retries?
RETRIES = 10
@ -56,7 +53,9 @@ class MPDClient(mpd.MPDClient):
class MPDClientWrapper(object):
def __init__(self):
def __init__(self, log):
self._log = log
self.music_directory = (
config['mpdstats']['music_directory'].get(unicode))
@ -71,7 +70,7 @@ class MPDClientWrapper(object):
if host[0] in ['/', '~']:
host = os.path.expanduser(host)
log.info(u'connecting to {0}:{1}', host, port)
self._log.info(u'connecting to {0}:{1}', host, port)
try:
self.client.connect(host, port)
except socket.error as e:
@ -99,7 +98,7 @@ class MPDClientWrapper(object):
try:
return getattr(self.client, command)()
except (select.error, mpd.ConnectionError) as err:
log.error(u'{0}', err)
self._log.error(u'{0}', err)
if retries <= 0:
# if we exited without breaking, we couldn't reconnect in time :(
@ -141,15 +140,16 @@ class MPDClientWrapper(object):
class MPDStats(object):
def __init__(self, lib):
def __init__(self, lib, log):
self.lib = lib
self._log = log
self.do_rating = config['mpdstats']['rating'].get(bool)
self.rating_mix = config['mpdstats']['rating_mix'].get(float)
self.time_threshold = 10.0 # TODO: maybe add config option?
self.now_playing = None
self.mpd = MPDClientWrapper()
self.mpd = MPDClientWrapper(log)
def rating(self, play_count, skip_count, rating, skipped):
"""Calculate a new rating for a song based on play count, skip count,
@ -171,10 +171,9 @@ class MPDStats(object):
if item:
return item
else:
log.info(u'item not found: {0}', displayable_path(path))
self._log.info(u'item not found: {0}', displayable_path(path))
@staticmethod
def update_item(item, attribute, value=None, increment=None):
def update_item(self, item, attribute, value=None, increment=None):
"""Update the beets item. Set attribute to value or increment the value
of attribute. If the increment argument is used the value is cast to
the corresponding type.
@ -190,7 +189,7 @@ class MPDStats(object):
item[attribute] = value
item.store()
log.debug(u'updated: {0} = {1} [{2}]',
self._log.debug(u'updated: {0} = {1} [{2}]',
attribute,
item[attribute],
displayable_path(item.path))
@ -229,16 +228,16 @@ class MPDStats(object):
"""Updates the play count of a song.
"""
self.update_item(song['beets_item'], 'play_count', increment=1)
log.info(u'played {0}', displayable_path(song['path']))
self._log.info(u'played {0}', displayable_path(song['path']))
def handle_skipped(self, song):
"""Updates the skip count of a song.
"""
self.update_item(song['beets_item'], 'skip_count', increment=1)
log.info(u'skipped {0}', displayable_path(song['path']))
self._log.info(u'skipped {0}', displayable_path(song['path']))
def on_stop(self, status):
log.info(u'stop')
self._log.info(u'stop')
if self.now_playing:
self.handle_song_change(self.now_playing)
@ -246,7 +245,7 @@ class MPDStats(object):
self.now_playing = None
def on_pause(self, status):
log.info(u'pause')
self._log.info(u'pause')
self.now_playing = None
def on_play(self, status):
@ -257,7 +256,7 @@ class MPDStats(object):
return
if is_url(path):
log.info(u'playing stream {0}', displayable_path(path))
self._log.info(u'playing stream {0}', displayable_path(path))
return
played, duration = map(int, status['time'].split(':', 1))
@ -266,7 +265,7 @@ class MPDStats(object):
if self.now_playing and self.now_playing['path'] != path:
self.handle_song_change(self.now_playing)
log.info(u'playing {0}', displayable_path(path))
self._log.info(u'playing {0}', displayable_path(path))
self.now_playing = {
'started': time.time(),
@ -291,7 +290,7 @@ class MPDStats(object):
if handler:
handler(status)
else:
log.debug(u'unhandled status "{0}"', status)
self._log.debug(u'unhandled status "{0}"', status)
events = self.mpd.events()
@ -344,7 +343,7 @@ class MPDStatsPlugin(plugins.BeetsPlugin):
config['mpd']['password'] = opts.password.decode('utf8')
try:
MPDStats(lib).run()
MPDStats(lib, self._log).run()
except KeyboardInterrupt:
pass

View file

@ -14,9 +14,10 @@
"""Send the results of a query to the configured music player as a playlist.
"""
from functools import partial
from beets.plugins import BeetsPlugin
from beets.ui import Subcommand
from beets import logging
from beets import config
from beets import ui
from beets import util
@ -25,10 +26,8 @@ import platform
import shlex
from tempfile import NamedTemporaryFile
log = logging.getLogger(__name__)
def play_music(lib, opts, args):
def play_music(lib, opts, args, log):
"""Execute query, create temporary playlist and execute player
command passing that playlist.
"""
@ -133,5 +132,5 @@ class PlayPlugin(BeetsPlugin):
action='store_true', default=False,
help='query and load albums rather than tracks'
)
play_command.func = play_music
play_command.func = partial(play_music, log=self._log)
return [play_command]