X-Git-Url: https://git.kaliko.me/?a=blobdiff_plain;f=sima%2Fplugins%2Finternal%2Flastfm.py;h=9ca859f6a724629ba73be2d39c1e8b27a99e1e27;hb=279a3160534cad04c64c0d0b17f437c08ebb2703;hp=b28d34e679482da4e2bccd37cd688b61310fdbba;hpb=7854938788e0af521edd7199e40db796d3256351;p=mpd-sima.git diff --git a/sima/plugins/internal/lastfm.py b/sima/plugins/internal/lastfm.py index b28d34e..9ca859f 100644 --- a/sima/plugins/internal/lastfm.py +++ b/sima/plugins/internal/lastfm.py @@ -3,19 +3,19 @@ Fetching similar artists from last.fm web services """ -# standart library import +# standard library import import random from collections import deque -from itertools import dropwhile from hashlib import md5 -# third parties componants +# third parties components # local import from ...lib.plugin import Plugin -from ...lib.simafm import SimaFM, XmlFMHTTPError, XmlFMNotFound, XmlFMError +from ...lib.simafm import SimaFM, WSError from ...lib.track import Track +from ...lib.meta import Artist def cache(func): @@ -23,49 +23,20 @@ def cache(func): def wrapper(*args, **kwargs): #pylint: disable=W0212,C0111 cls = args[0] - similarities = [art + str(match) for art, match in args[1]] + similarities = [art for art, _ in args[1]] hashedlst = md5(''.join(similarities).encode('utf-8')).hexdigest() if hashedlst in cls._cache.get('asearch'): cls.log.debug('cached request') results = cls._cache.get('asearch').get(hashedlst) else: results = func(*args, **kwargs) + cls.log.debug('caching request') cls._cache.get('asearch').update({hashedlst:list(results)}) random.shuffle(results) return results return wrapper -def blacklist(artist=False, album=False, track=False): - #pylint: disable=C0111,W0212 - field = (artist, album, track) - def decorated(func): - def wrapper(*args, **kwargs): - cls = args[0] - boolgen = (bl for bl in field) - bl_fun = (cls._Plugin__daemon.sdb.get_bl_artist, - cls._Plugin__daemon.sdb.get_bl_album, - cls._Plugin__daemon.sdb.get_bl_track,) - #bl_getter = next(fn for fn, bl in zip(bl_fun, boolgen) if bl is True) - bl_getter = next(dropwhile(lambda _: not next(boolgen), bl_fun)) - cls.log.debug('using {0} as bl filter'.format(bl_getter.__name__)) - if artist: - results = func(*args, **kwargs) - for elem in results: - if bl_getter(elem, add_not=True): - cls.log.info('Blacklisted: {0}'.format(elem)) - results.remove(elem) - return results - if track: - for elem in args[1]: - if bl_getter(elem, add_not=True): - cls.log.info('Blacklisted: {0}'.format(elem)) - args[1].remove(elem) - return func(*args, **kwargs) - return wrapper - return decorated - - class Lastfm(Plugin): """last.fm similar artists """ @@ -90,16 +61,15 @@ class Lastfm(Plugin): """ Both flushes and instanciates _cache """ + name = self.__class__.__name__ if isinstance(self._cache, dict): - self.log.info('Lastfm: Flushing cache!') + self.log.info('{0}: Flushing cache!'.format(name)) else: - self.log.info('Lastfm: Initialising cache!') + self.log.info('{0}: Initialising cache!'.format(name)) self._cache = { - 'artists': None, 'asearch': dict(), 'tsearch': dict(), } - self._cache['artists'] = frozenset(self.player.list('artist')) def _cleanup_cache(self): """Avoid bloated cache @@ -142,6 +112,13 @@ class Lastfm(Plugin): if self.sdb.get_bl_album(trk, add_not=True): self.log.info('Blacklisted album: {0}: '.format(trk)) continue + # Should use albumartist heuristic as well + if self.plugin_conf.getboolean('single_album'): + if (trk.album == self.player.current.album or + trk.album in [tr.album for tr in self.to_add]): + self.log.debug('Found unplayed track ' + + 'but from an album already queued: %s' % (trk)) + continue candidate.append(trk) if not candidate: self.log.debug('Unable to find title to add' + @@ -169,17 +146,16 @@ class Lastfm(Plugin): ' / '.join(art_not_in_hist))) return art_not_in_hist - @blacklist(artist=True) @cache def get_artists_from_player(self, similarities): """ Look in player library for availability of similar artists in similarities """ - dynamic = int(self.plugin_conf.get('dynamic')) + dynamic = self.plugin_conf.getint('dynamic') if dynamic <= 0: dynamic = 100 - similarity = int(self.plugin_conf.get('similarity')) + similarity = self.plugin_conf.getint('similarity') results = list() similarities.reverse() while (len(results) < dynamic @@ -187,7 +163,7 @@ class Lastfm(Plugin): art_pop, match = similarities.pop() if match < similarity: break - results.extend(self.player.fuzzy_find(art_pop)) + results.extend(self.player.fuzzy_find_artist(art_pop)) results and self.log.debug('Similarity: %d%%' % match) # pylint: disable=w0106 return results @@ -196,24 +172,24 @@ class Lastfm(Plugin): Retrieve similar artists on last.fm server. """ if artist is None: - current = self.player.current + curr = self.player.current.__dict__ + name = curr.get('artist') + mbid = curr.get('musicbrainz_artistid', None) + current = Artist(name=name, mbid=mbid) else: current = artist simafm = SimaFM() # initialize artists deque list to construct from DB as_art = deque() - as_artists = simafm.get_similar(artist=current.artist) - self.log.debug('Requesting last.fm for "{0.artist}"'.format(current)) + as_artists = simafm.get_similar(artist=current) + self.log.debug('Requesting last.fm for "{0}"'.format(current)) try: - [as_art.append((a, m)) for a, m in as_artists] - except XmlFMHTTPError as err: - self.log.warning('last.fm http error: %s' % err) - except XmlFMNotFound as err: - self.log.warning("last.fm: %s" % err) - except XmlFMError as err: - self.log.warning('last.fm module error: %s' % err) + # TODO: let's propagate Artist type + [as_art.append((str(a), m)) for a, m in as_artists] + except WSError as err: + self.log.warning('Last.fm: {0}'.format(err)) if as_art: - self.log.debug('Fetched %d artist(s) from last.fm' % len(as_art)) + self.log.debug('Fetched {0} artist(s)'.format(len(as_art))) return as_art def get_recursive_similar_artist(self): @@ -223,7 +199,7 @@ class Lastfm(Plugin): depth = 0 current = self.player.current extra_arts = list() - while depth < int(self.plugin_conf.get('depth')): + while depth < self.plugin_conf.getint('depth'): if len(history) == 0: break trk = history.popleft() @@ -237,6 +213,8 @@ class Lastfm(Plugin): for artist in extra_arts: self.log.debug('Looking for artist similar to "{0.artist}" as well'.format(artist)) similar = self.lfm_similar_artists(artist=artist) + if not similar: + return ret_extra similar = sorted(similar, key=lambda sim: sim[1], reverse=True) ret_extra.extend(self.get_artists_from_player(similar)) if current.artist in ret_extra: @@ -254,47 +232,25 @@ class Lastfm(Plugin): return [] similar = sorted(similar, key=lambda sim: sim[1], reverse=True) self.log.info('First five similar artist(s): {}...'.format( - ' / '.join([a for a, m in similar[0:5]]))) + ' / '.join([a for a, _ in similar[0:5]]))) self.log.info('Looking availability in music library') ret = self.get_artists_from_player(similar) ret_extra = None if len(self.history) >= 2: - ret_extra = self.get_recursive_similar_artist() + if self.plugin_conf.getint('depth') > 1: + ret_extra = self.get_recursive_similar_artist() + if ret_extra: + ret = list(set(ret) | set(ret_extra)) if not ret: self.log.warning('Got nothing from music library.') self.log.warning('Try running in debug mode to guess why...') return [] - if ret_extra: - ret = list(set(ret) | set(ret_extra)) self.log.info('Got {} artists in library'.format(len(ret))) self.log.info(' / '.join(ret)) # Move around similars items to get in unplayed|not recently played # artist first. return self._get_artists_list_reorg(ret) - def _detects_var_artists_album(self, album, artist): - """Detects either an album is a "Various Artists" or a - single artist release.""" - art_first_track = None - for track in self.player.find_album(artist, album): - if not art_first_track: # set artist for the first track - art_first_track = track.artist - alb_art = track.albumartist - # Special heuristic used when AlbumArtist is available - if (alb_art): - if artist == alb_art: - # When album artist field is similar to the artist we're - # looking an album for, the album is considered good to - # queue - return False - else: - self.log.debug(track) - self.log.debug('album art says "%s", looking for "%s",' - ' not queueing this album' % - (alb_art, artist)) - return True - return False - def _get_album_history(self, artist=None): """Retrieve album history""" duration = self.daemon_conf.getint('sima', 'history_duration') @@ -308,11 +264,17 @@ class Lastfm(Plugin): """ self.to_add = list() nb_album_add = 0 - target_album_to_add = int(self.plugin_conf.get('album_to_add')) + target_album_to_add = self.plugin_conf.getint('album_to_add') for artist in artists: self.log.info('Looking for an album to add for "%s"...' % artist) - albums = set(self.player.find_albums(artist)) + albums = self.player.find_albums(artist) + # str conversion while Album type is not propagated + albums = [ str(album) for album in albums] + if albums: + self.log.debug('Albums candidate: {0:s}'.format(' / '.join(albums))) + else: continue # albums yet in history for this artist + albums = set(albums) albums_yet_in_hist = albums & self._get_album_history(artist=artist) albums_not_in_hist = list(albums - albums_yet_in_hist) # Get to next artist if there are no unplayed albums @@ -322,13 +284,7 @@ class Lastfm(Plugin): album_to_queue = str() random.shuffle(albums_not_in_hist) for album in albums_not_in_hist: - tracks = self.player.find('album', album) - if self._detects_var_artists_album(album, artist): - continue - if tracks and self.sdb.get_bl_album(tracks[0], add_not=True): - self.log.info('Blacklisted album: "%s"' % album) - self.log.debug('using track: "%s"' % tracks[0]) - continue + tracks = self.player.find_album(artist, album) # Look if one track of the album is already queued # Good heuristic, at least enough to guess if the whole album is # already queued. @@ -351,7 +307,7 @@ class Lastfm(Plugin): """Get some tracks for track queue mode """ artists = self.get_local_similar_artists() - nbtracks_target = int(self.plugin_conf.get('track_to_add')) + nbtracks_target = self.plugin_conf.getint('track_to_add') for artist in artists: self.log.debug('Trying to find titles to add for "{}"'.format( artist)) @@ -365,7 +321,7 @@ class Lastfm(Plugin): 'history getting too large?') return None for track in self.to_add: - self.log.info('last.fm candidate: {0!s}'.format(track)) + self.log.info('last.fm candidates: {0!s}'.format(track)) def _album(self): """Get albums for album queue mode @@ -382,11 +338,17 @@ class Lastfm(Plugin): def callback_need_track(self): self._cleanup_cache() if not self.player.current: - self.log.info('Not currently playing track, cannot queue') + self.log.info('No current track, cannot queue') + return None + if not self.player.current.artist: + self.log.warning('No artist set for the current track') + self.log.debug(repr(self.player.current)) return None self.queue_mode() candidates = self.to_add self.to_add = list() + if self.plugin_conf.get('queue_mode') != 'album': + random.shuffle(candidates) return candidates def callback_player_database(self):