]> kaliko git repositories - mpd-sima.git/commitdiff
Some refactoring around player
authorkaliko <efrim@azylum.org>
Sun, 15 Dec 2013 10:41:29 +0000 (11:41 +0100)
committerkaliko <efrim@azylum.org>
Sun, 15 Dec 2013 10:41:29 +0000 (11:41 +0100)
Move out of lastfm plugin what was player logic (album search
heuristic mainly)

launch
sima/client.py
sima/lib/player.py
sima/plugins/contrib/placeholder.py
sima/plugins/internal/crop.py
sima/plugins/internal/lastfm.py
sima/plugins/internal/randomfallback.py

diff --git a/launch b/launch
index c7ff8e69825fbe50e2ede515b18ed7e22e429988..39fac649ba25ebdba7e8fc3e5b1a311ffd39a941 100755 (executable)
--- a/launch
+++ b/launch
@@ -3,7 +3,7 @@
 """Sima
 """
 
 """Sima
 """
 
-# standart library import
+# standard library import
 import logging
 import sys
 
 import logging
 import sys
 
index 80c16e530875a2b6fd68fcff717568bbb339e96d..9724357c3c521acc87689a6143e91910903f33d9 100644 (file)
@@ -5,12 +5,11 @@ This client is built above python-musicpd a fork of python-mpd
 """
 #  pylint: disable=C0111
 
 """
 #  pylint: disable=C0111
 
-# standart library import
-
+# standard library import
 from difflib import get_close_matches
 from select import select
 
 from difflib import get_close_matches
 from select import select
 
-# third parties componants
+# third parties components
 try:
     from musicpd import (MPDClient, MPDError, CommandError)
 except ImportError as err:
 try:
     from musicpd import (MPDClient, MPDError, CommandError)
 except ImportError as err:
@@ -32,6 +31,7 @@ class PlayerCommandError(PlayerError):
 
 PlayerUnHandledError = MPDError  # pylint: disable=C0103
 
 
 PlayerUnHandledError = MPDError  # pylint: disable=C0103
 
+
 class PlayerClient(Player):
     """MPC Client
     From python-musicpd:
 class PlayerClient(Player):
     """MPC Client
     From python-musicpd:
@@ -53,17 +53,22 @@ class PlayerClient(Player):
         self._mpd = host, port, password
         self._client = MPDClient()
         self._client.iterate = True
         self._mpd = host, port, password
         self._client = MPDClient()
         self._client.iterate = True
+        self._cache = None
 
     def __getattr__(self, attr):
         command = attr
         wrapper = self._execute
         return lambda *args: wrapper(command, args)
 
 
     def __getattr__(self, attr):
         command = attr
         wrapper = self._execute
         return lambda *args: wrapper(command, args)
 
+    def __del__(self):
+        """Avoid hanging sockets"""
+        self.disconnect()
+
     def _execute(self, command, args):
         self._write_command(command, args)
         return self._client_wrapper()
 
     def _execute(self, command, args):
         self._write_command(command, args)
         return self._client_wrapper()
 
-    def _write_command(self, command, args=[]):
+    def _write_command(self, command, args=None):
         self._comm = command
         self._args = list()
         for arg in args:
         self._comm = command
         self._args = list()
         for arg in args:
@@ -105,13 +110,26 @@ class PlayerClient(Player):
             return False
         return (self.current.id != old_curr.id)  # pylint: disable=no-member
 
             return False
         return (self.current.id != old_curr.id)  # pylint: disable=no-member
 
+    def _flush_cache(self):
+        """
+        Both flushes and instantiates _cache
+        """
+        if isinstance(self._cache, dict):
+            self.log.info('Player: Flushing cache!')
+        else:
+            self.log.info('Player: Initialising cache!')
+        self._cache = {
+                'artists': None,
+                }
+        self._cache['artists'] = frozenset(self._client.list('artist'))
+
     def find_track(self, artist, title=None):
         #return getattr(self, 'find')('artist', artist, 'title', title)
         if title:
             return self.find('artist', artist, 'title', title)
         return self.find('artist', artist)
 
     def find_track(self, artist, title=None):
         #return getattr(self, 'find')('artist', artist, 'title', title)
         if title:
             return self.find('artist', artist, 'title', title)
         return self.find('artist', artist)
 
-    def fuzzy_find(self, art):
+    def fuzzy_find_artist(self, art):
         """
         Controls presence of artist in music library.
         Crosschecking artist names with SimaStr objects / difflib / levenshtein
         """
         Controls presence of artist in music library.
         Crosschecking artist names with SimaStr objects / difflib / levenshtein
@@ -123,14 +141,13 @@ class PlayerClient(Player):
         """
         matching_artists = list()
         artist = SimaStr(art)
         """
         matching_artists = list()
         artist = SimaStr(art)
-        all_artists = self.list('artist')
 
         # Check against the actual string in artist list
 
         # Check against the actual string in artist list
-        if artist.orig in all_artists:
+        if artist.orig in self.artists:
             self.log.debug('found exact match for "%s"' % artist)
             return [artist]
         # Then proceed with fuzzy matching if got nothing
             self.log.debug('found exact match for "%s"' % artist)
             return [artist]
         # Then proceed with fuzzy matching if got nothing
-        match = get_close_matches(artist.orig, all_artists, 50, 0.73)
+        match = get_close_matches(artist.orig, self.artists, 50, 0.73)
         if not match:
             return []
         self.log.debug('found close match for "%s": %s' %
         if not match:
             return []
         self.log.debug('found close match for "%s": %s' %
@@ -172,13 +189,20 @@ class PlayerClient(Player):
 
     def find_albums(self, artist):
         """
 
     def find_albums(self, artist):
         """
-        Special wrapper around album search:
-        Album lookup is made through AlbumArtist/Album instead of Artist/Album
+        Fetch all albums for "AlbumArtist" == artist
+        Filter albums returned for "artist" == artist since MPD returns any
+               album containing at least a single track for artist
         """
         """
-        alb_art_search = self.list('album', 'albumartist', artist,)
-        if alb_art_search:
-            return alb_art_search
-        return self.list('album', 'artist', artist)
+        albums = set()
+        albums.update(self.list('album', 'albumartist', artist))
+        for album in self.list('album', 'artist', artist):
+            arts = set([trk.artist for trk in self.find('album', album)])
+            if len(arts) < 2:
+                albums.add(album)
+            else:
+                self.log.debug('"{0}" probably not an album of "{1}"'.format(
+                             album, artist) + '({0})'.format('/'.join(arts)))
+        return albums
 
     def monitor(self):
         curr = self.current
 
     def monitor(self):
         curr = self.current
@@ -188,6 +212,8 @@ class PlayerClient(Player):
             ret = self._client.fetch_idle()
             if self.__skipped_track(curr):
                 ret.append('skipped')
             ret = self._client.fetch_idle()
             if self.__skipped_track(curr):
                 ret.append('skipped')
+            if 'database' in ret:
+                self._flush_cache()
             return ret
         except (MPDError, IOError) as err:
             raise PlayerError("Couldn't init idle: %s" % err)
             return ret
         except (MPDError, IOError) as err:
             raise PlayerError("Couldn't init idle: %s" % err)
@@ -200,6 +226,10 @@ class PlayerClient(Player):
         object"""
         self._client.add(track.file)
 
         object"""
         self._client.add(track.file)
 
+    @property
+    def artists(self):
+        return self._cache.get('artists')
+
     @property
     def state(self):
         return str(self._client.status().get('state'))
     @property
     def state(self):
         return str(self._client.status().get('state'))
@@ -266,6 +296,7 @@ class PlayerClient(Player):
                 raise PlayerError('Could connect to "%s", '
                                   'but command "%s" not available' %
                                   (host, nddcmd))
                 raise PlayerError('Could connect to "%s", '
                                   'but command "%s" not available' %
                                   (host, nddcmd))
+        self._flush_cache()
 
     def disconnect(self):
         # Try to tell MPD we're closing the connection first
 
     def disconnect(self):
         # Try to tell MPD we're closing the connection first
index ea84a648632a2ae76b36811a651517e69dc5d62d..751e464ba7a3c551aabf3ff0dd2c974028fb3e82 100644 (file)
@@ -3,7 +3,7 @@
 # TODO:
 # Add decorator to filter through history?
 
 # TODO:
 # Add decorator to filter through history?
 
-# standart library import
+# standard library import
 import logging
 
 # local import
 import logging
 
 # local import
@@ -16,6 +16,14 @@ class Player(object):
 
     When querying player music library for tracks, Player instance *must* return
     Track objects (usually a list of them)
 
     When querying player music library for tracks, Player instance *must* return
     Track objects (usually a list of them)
+
+    Player instance should expose the following immutable attributes:
+        * artists
+        * state
+        * current
+        * queue
+        * playlist
+        *
     """
 
     def __init__(self):
     """
 
     def __init__(self):
@@ -65,10 +73,10 @@ class Player(object):
         """
         raise NotImplementedError
 
         """
         raise NotImplementedError
 
-    def fuzzy_find(self, artist):
+    def fuzzy_find_artist(self, artist):
         """
         Find artists based on a fuzzy search in the media library
         """
         Find artists based on a fuzzy search in the media library
-            >>> bea = player.fuzzy_find('beatles')
+            >>> bea = player.fuzzy_find_artist('beatles')
             >>> print(bea)
             >>> ['The Beatles']
 
             >>> print(bea)
             >>> ['The Beatles']
 
index 665668d66faa30b2c1631b8b4fe41e0fd48d8d09..60e34b50f4c054326e643d9465fe59c98095e96b 100644 (file)
@@ -2,10 +2,10 @@
 """Crops playlist
 """
 
 """Crops playlist
 """
 
-# standart library import
+# standard library import
 #from select import select
 
 #from select import select
 
-# third parties componants
+# third parties components
 
 # local import
 from sima.lib.plugin import Plugin
 
 # local import
 from sima.lib.plugin import Plugin
index d4df0c3211abc7acc1b728265f074adba7c2a99d..a38b45df2128e7906cc953204c24d7f180fb999a 100644 (file)
@@ -2,10 +2,10 @@
 """Crops playlist
 """
 
 """Crops playlist
 """
 
-# standart library import
+# standard library import
 #from select import select
 
 #from select import select
 
-# third parties componants
+# third parties components
 
 # local import
 from ...lib.plugin import Plugin
 
 # local import
 from ...lib.plugin import Plugin
index 03cea6cd4284247ac2180b9f831a56dea5022ca6..1b9ae41798cd39c46efc940f16eb77d99340a2f6 100644 (file)
@@ -3,14 +3,14 @@
 Fetching similar artists from last.fm web services
 """
 
 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
 
 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
 
 # local import
 from ...lib.plugin import Plugin
@@ -96,11 +96,9 @@ class Lastfm(Plugin):
         else:
             self.log.info('Lastfm: Initialising cache!')
         self._cache = {
         else:
             self.log.info('Lastfm: Initialising cache!')
         self._cache = {
-                'artists': None,
                 'asearch': dict(),
                 'tsearch': dict(),
                 }
                 'asearch': dict(),
                 'tsearch': dict(),
                 }
-        self._cache['artists'] = frozenset(self.player.list('artist'))
 
     def _cleanup_cache(self):
         """Avoid bloated cache
 
     def _cleanup_cache(self):
         """Avoid bloated cache
@@ -184,10 +182,10 @@ class Lastfm(Plugin):
         Look in player library for availability of similar artists in
         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
         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
         results = list()
         similarities.reverse()
         while (len(results) < dynamic
@@ -195,7 +193,7 @@ class Lastfm(Plugin):
             art_pop, match = similarities.pop()
             if match < similarity:
                 break
             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
 
         results and self.log.debug('Similarity: %d%%' % match) # pylint: disable=w0106
         return results
 
@@ -231,7 +229,7 @@ class Lastfm(Plugin):
         depth = 0
         current = self.player.current
         extra_arts = list()
         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()
             if len(history) == 0:
                 break
             trk = history.popleft()
@@ -282,29 +280,6 @@ class Lastfm(Plugin):
         # artist first.
         return self._get_artists_list_reorg(ret)
 
         # 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')
     def _get_album_history(self, artist=None):
         """Retrieve album history"""
         duration = self.daemon_conf.getint('sima', 'history_duration')
@@ -318,10 +293,10 @@ class Lastfm(Plugin):
         """
         self.to_add = list()
         nb_album_add = 0
         """
         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)
         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)
             # albums yet in history for this artist
             albums_yet_in_hist = albums & self._get_album_history(artist=artist)
             albums_not_in_hist = list(albums - albums_yet_in_hist)
             # albums yet in history for this artist
             albums_yet_in_hist = albums & self._get_album_history(artist=artist)
             albums_not_in_hist = list(albums - albums_yet_in_hist)
@@ -332,9 +307,7 @@ class Lastfm(Plugin):
             album_to_queue = str()
             random.shuffle(albums_not_in_hist)
             for album in albums_not_in_hist:
             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
+                tracks = self.player.find_album(artist, album)
                 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])
                 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])
@@ -361,7 +334,7 @@ class Lastfm(Plugin):
         """Get some tracks for track queue mode
         """
         artists = self.get_local_similar_artists()
         """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))
         for artist in artists:
             self.log.debug('Trying to find titles to add for "{}"'.format(
                            artist))
index f2eaac4dc9eb412819c64b16a8c5068793697f2a..07201c028a16378cc976018a66ca898364ad9177 100644 (file)
@@ -3,10 +3,10 @@
 Fetching similar artists from last.fm web services
 """
 
 Fetching similar artists from last.fm web services
 """
 
-# standart library import
+# standard library import
 import random
 
 import random
 
-# third parties componants
+# third parties components
 
 # local import
 from ...lib.plugin import Plugin
 
 # local import
 from ...lib.plugin import Plugin