]> kaliko git repositories - mpd-sima.git/commitdiff
Move fuzzy_find method in the player.
authorkaliko <efrim@azylum.org>
Sat, 12 Oct 2013 12:57:52 +0000 (14:57 +0200)
committerkaliko <efrim@azylum.org>
Sat, 12 Oct 2013 12:57:52 +0000 (14:57 +0200)
Exposes daemon/player in plugins

sima/client.py
sima/lib/player.py
sima/lib/plugin.py
sima/plugins/addhist.py
sima/plugins/lastfm.py
sima/plugins/mpd.py
sima/plugins/randomfallback.py

index 9d6100535f93e880d1792ce4aa6c74847ecdfd69..929f66281fd7489cc2d001a75efa5755e3ffe138 100644 (file)
@@ -3,8 +3,11 @@
 
 This client is built above python-musicpd a fork of python-mpd
 """
+#  pylint: disable=C0111
 
 # standart library import
+
+from difflib import get_close_matches
 from select import select
 
 # third parties componants
@@ -18,6 +21,8 @@ except ImportError as err:
 # local import
 from .lib.player import Player
 from .lib.track import Track
+from .lib.simastr import SimaStr
+from .utils.leven import levenshtein_ratio
 
 
 class PlayerError(Exception):
@@ -26,7 +31,7 @@ class PlayerError(Exception):
 class PlayerCommandError(PlayerError):
     """Command error"""
 
-PlayerUnHandledError = MPDError
+PlayerUnHandledError = MPDError  # pylint: disable=C0103
 
 class PlayerClient(Player):
     """MPC Client
@@ -44,9 +49,9 @@ class PlayerClient(Player):
           find_aa, remove…)
     """
     def __init__(self, host="localhost", port="6600", password=None):
-        self._host = host
-        self._port = port
-        self._password = password
+        super().__init__()
+        self._comm = self._args = None
+        self._mpd = host, port, password
         self._client = MPDClient()
         self._client.iterate = True
 
@@ -77,13 +82,15 @@ class PlayerClient(Player):
         return self._track_format(ans)
 
     def _track_format(self, ans):
+        """
+        unicode_obj = ["idle", "listplaylist", "list", "sticker list",
+                "commands", "notcommands", "tagtypes", "urlhandlers",]
+        """
         # TODO: ain't working for "sticker find" and "sticker list"
         tracks_listing = ["playlistfind", "playlistid", "playlistinfo",
                 "playlistsearch", "plchanges", "listplaylistinfo", "find",
                 "search", "sticker find",]
         track_obj = ['currentsong']
-        unicode_obj = ["idle", "listplaylist", "list", "sticker list",
-                "commands", "notcommands", "tagtypes", "urlhandlers",]
         if self._comm in tracks_listing + track_obj:
             #  pylint: disable=w0142
             if isinstance(ans, list):
@@ -105,6 +112,62 @@ class PlayerClient(Player):
             return self.find('artist', artist, 'title', title)
         return self.find('artist', artist)
 
+    def fuzzy_find(self, art):
+        """
+        Controls presence of artist in music library.
+        Crosschecking artist names with SimaStr objects / difflib / levenshtein
+
+        TODO: proceed crosschecking even when an artist matched !!!
+              Not because we found "The Doors" as "The Doors" that there is no
+              remaining entries as "Doors" :/
+              not straight forward, need probably heavy refactoring.
+        """
+        matching_artists = list()
+        artist = SimaStr(art)
+        all_artists = self.list('artist')
+
+        # Check against the actual string in artist list
+        if artist.orig in all_artists:
+            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)
+        if not match:
+            return []
+        self.log.debug('found close match for "%s": %s' %
+                       (artist, '/'.join(match)))
+        # Does not perform fuzzy matching on short and single word strings
+        # Only lowercased comparison
+        if ' ' not in artist.orig and len(artist) < 8:
+            for fuzz_art in match:
+                # Regular string comparison SimaStr().lower is regular string
+                if artist.lower() == fuzz_art.lower():
+                    matching_artists.append(fuzz_art)
+                    self.log.debug('"%s" matches "%s".' % (fuzz_art, artist))
+            return matching_artists
+        for fuzz_art in match:
+            # Regular string comparison SimaStr().lower is regular string
+            if artist.lower() == fuzz_art.lower():
+                matching_artists.append(fuzz_art)
+                self.log.debug('"%s" matches "%s".' % (fuzz_art, artist))
+                return matching_artists
+            # Proceed with levenshtein and SimaStr
+            leven = levenshtein_ratio(artist.stripped.lower(),
+                    SimaStr(fuzz_art).stripped.lower())
+            # SimaStr string __eq__, not regular string comparison here
+            if artist == fuzz_art:
+                matching_artists.append(fuzz_art)
+                self.log.info('"%s" quite probably matches "%s" (SimaStr)' %
+                              (fuzz_art, artist))
+            elif leven >= 0.82:  # PARAM
+                matching_artists.append(fuzz_art)
+                self.log.debug('FZZZ: "%s" should match "%s" (lr=%1.3f)' %
+                               (fuzz_art, artist, leven))
+            else:
+                self.log.debug('FZZZ: "%s" does not match "%s" (lr=%1.3f)' %
+                               (fuzz_art, artist, leven))
+        return matching_artists
+
     def find_album(self, artist, album):
         """
         Special wrapper around album search:
@@ -157,14 +220,15 @@ class PlayerClient(Player):
         return self.playlistinfo()
 
     def connect(self):
+        host, port, password = self._mpd
         self.disconnect()
         try:
-            self._client.connect(self._host, self._port)
+            self._client.connect(host, port)
 
         # Catch socket errors
         except IOError as err:
             raise PlayerError('Could not connect to "%s:%s": %s' %
-                              (self._host, self._port, err.strerror))
+                              (host, port, err.strerror))
 
         # Catch all other possible errors
         # ConnectionError and ProtocolError are always fatal.  Others may not
@@ -172,23 +236,23 @@ class PlayerClient(Player):
         # they are instead of ignoring them.
         except MPDError as err:
             raise PlayerError('Could not connect to "%s:%s": %s' %
-                              (self._host, self._port, err))
+                              (host, port, err))
 
-        if self._password:
+        if password:
             try:
-                self._client.password(self._password)
+                self._client.password(password)
 
             # Catch errors with the password command (e.g., wrong password)
             except CommandError as err:
                 raise PlayerError("Could not connect to '%s': "
                                   "password command failed: %s" %
-                                  (self._host, err))
+                                  (host, err))
 
             # Catch all other possible errors
             except (MPDError, IOError) as err:
                 raise PlayerError("Could not connect to '%s': "
                                   "error with password command: %s" %
-                                  (self._host, err))
+                                  (host, err))
         # Controls we have sufficient rights
         needed_cmds = ['status', 'stats', 'add', 'find', \
                        'search', 'currentsong', 'ping']
@@ -199,7 +263,7 @@ class PlayerClient(Player):
                 self.disconnect()
                 raise PlayerError('Could connect to "%s", '
                                   'but command "%s" not available' %
-                                  (self._host, nddcmd))
+                                  (host, nddcmd))
 
     def disconnect(self):
         # Try to tell MPD we're closing the connection first
index 9f2910d048548bd3a1544b1c6ef4a85a7242b269..726b0b632c515de7798017245409c876dbad1ebf 100644 (file)
@@ -3,6 +3,9 @@
 # TODO:
 # Add decorator to filter through history?
 
+# standart library import
+import logging
+
 # local import
 #from sima.lib.track import Track
 
@@ -16,8 +19,8 @@ class Player(object):
     """
 
     def __init__(self):
-        self.state = {}
-        self.current = {}
+        super().__init__()
+        self.log = logging.getLogger('sima')
 
     def monitor(self):
         """Monitor player for change
@@ -52,6 +55,17 @@ class Player(object):
         Returns a list of Track objects
         """
 
+    def fuzzy_find(self, artist):
+        """
+        Find artists based on a fuzzy search in the media library
+            >>> bea = player.fuzzy_find('beatles')
+            >>> print(bea)
+            >>> ['The Beatles']
+
+        Returns a list of strings (artist names)
+        """
+        raise NotImplementedError
+
     def disconnect(self):
         """Closing client connection with the Player
         """
@@ -64,4 +78,3 @@ class Player(object):
 
 # VIM MODLINE
 # vim: ai ts=4 sw=4 sts=4 expandtab
-
index d44abafda47cd6eb142233c97d0d29252d47dedd..cea4448346749c582e91944623712978ff5f595b 100644 (file)
@@ -25,6 +25,7 @@ class Plugin():
     def __init__(self, daemon):
         self.log = daemon.log
         self.__daemon = daemon
+        self.player = daemon.player
         self.plugin_conf = None
         self.__get_config()
 
index 70c3f1742b57be003a16aa994dcf6b1d29b70630..e9a36e8ddb53f008be8bee630b60046cd0a18f9e 100644 (file)
@@ -16,7 +16,6 @@ class History(Plugin):
     def __init__(self, daemon):
         Plugin.__init__(self, daemon)
         self.sdb = daemon.sdb
-        self.player = daemon.player
 
     def shutdown(self):
         self.log.info('Cleaning database')
index 7f2397a8eb13f38f81f6c90b08a940b234abec15..0bbddbd33f0e8ae8b767692877a1cdc0fb8e1715 100644 (file)
@@ -7,16 +7,13 @@ Fetching similar artists from last.fm web services
 import random
 
 from collections import deque
-from difflib import get_close_matches
 from hashlib import md5
 
 # third parties componants
 
 # local import
-from ..utils.leven import levenshtein_ratio
 from ..lib.plugin import Plugin
 from ..lib.simafm import SimaFM, XmlFMHTTPError, XmlFMNotFound, XmlFMError
-from ..lib.simastr import SimaStr
 from ..lib.track import Track
 
 
@@ -46,7 +43,6 @@ class Lastfm(Plugin):
         Plugin.__init__(self, daemon)
         self.daemon_conf = daemon.config
         self.sdb = daemon.sdb
-        self.player = daemon.player
         self.history = daemon.short_history
         ##
         self.to_add = list()
@@ -77,6 +73,7 @@ class Lastfm(Plugin):
     def _cleanup_cache(self):
         """Avoid bloated cache
         """
+        # TODO: call cleanup once its dict instance are used somewhere XXX
         for _ , val in self._cache.items():
             if isinstance(val, dict):
                 while len(val) > 150:
@@ -116,6 +113,7 @@ class Lastfm(Plugin):
         Move around items in artists_list in order to play first not recently
         played artists
         """
+        # TODO: move to utils as a decorator
         duration = self.daemon_conf.getint('sima', 'history_duration')
         art_in_hist = list()
         for trk in self.sdb.get_history(duration=duration,
@@ -130,62 +128,6 @@ class Lastfm(Plugin):
                        ' / '.join(art_not_in_hist)))
         return art_not_in_hist
 
-    def _cross_check_artist(self, art):
-        """
-        Controls presence of artists in liste in music library.
-        Crosschecking artist names with SimaStr objects / difflib / levenshtein
-
-        TODO: proceed crosschecking even when an artist matched !!!
-              Not because we found "The Doors" as "The Doors" that there is no
-              remaining entries as "Doors" :/
-              not straight forward, need probably heavy refactoring.
-        """
-        matching_artists = list()
-        artist = SimaStr(art)
-        all_artists = self._cache.get('artists')
-
-        # Check against the actual string in artist list
-        if artist.orig in all_artists:
-            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)
-        if not match:
-            return []
-        self.log.debug('found close match for "%s": %s' %
-                       (artist, '/'.join(match)))
-        # Does not perform fuzzy matching on short and single word strings
-        # Only lowercased comparison
-        if ' ' not in artist.orig and len(artist) < 8:
-            for fuzz_art in match:
-                # Regular string comparison SimaStr().lower is regular string
-                if artist.lower() == fuzz_art.lower():
-                    matching_artists.append(fuzz_art)
-                    self.log.debug('"%s" matches "%s".' % (fuzz_art, artist))
-            return matching_artists
-        for fuzz_art in match:
-            # Regular string comparison SimaStr().lower is regular string
-            if artist.lower() == fuzz_art.lower():
-                matching_artists.append(fuzz_art)
-                self.log.debug('"%s" matches "%s".' % (fuzz_art, artist))
-                return matching_artists
-            # Proceed with levenshtein and SimaStr
-            leven = levenshtein_ratio(artist.stripped.lower(),
-                    SimaStr(fuzz_art).stripped.lower())
-            # SimaStr string __eq__, not regular string comparison here
-            if artist == fuzz_art:
-                matching_artists.append(fuzz_art)
-                self.log.info('"%s" quite probably matches "%s" (SimaStr)' %
-                              (fuzz_art, artist))
-            elif leven >= 0.82:  # PARAM
-                matching_artists.append(fuzz_art)
-                self.log.debug('FZZZ: "%s" should match "%s" (lr=%1.3f)' %
-                               (fuzz_art, artist, leven))
-            else:
-                self.log.debug('FZZZ: "%s" does not match "%s" (lr=%1.3f)' %
-                               (fuzz_art, artist, leven))
-        return matching_artists
-
     @cache
     def get_artists_from_player(self, similarities):
         """
@@ -203,8 +145,8 @@ class Lastfm(Plugin):
             art_pop, match = similarities.pop()
             if match < similarity:
                 break
-            results.extend(self._cross_check_artist(art_pop))
-        results and self.log.debug('Similarity: %d%%' % match)
+            results.extend(self.player.fuzzy_find(art_pop))
+        results and self.log.debug('Similarity: %d%%' % match) # pylint: disable=w0106
         return results
 
     def lfm_similar_artists(self, artist=None):
@@ -322,7 +264,7 @@ class Lastfm(Plugin):
     def callback_need_track(self):
         self._cleanup_cache()
         if not self.player.current:
-            self.log.info('No currently playing track, cannot queue')
+            self.log.info('Not currently playing track, cannot queue')
             return None
         self.queue_mode()
         candidates = self.to_add
index 26a07e5f2635bb4af3295911e9b0e36138f82cef..5ad156ffaf8e2dd946b589ae983db19ed02f3dea 100644 (file)
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 """
+    Deal with MPD options ‑ idle and repeat mode
 """
 
 # standard library import
@@ -31,6 +32,7 @@ class MpdOptions(Plugin):
             self.log.info('MPD "repeat" mode activated.')
             self.daemon.enabled = False
         else:
+            self.log.debug('enabling queuing (leaving single|repeat mode)')
             self.daemon.enabled = True
 
     def shutdown(self):
index 37d57afaab806b4e95ac5f0ae8ee0230cee8097a..4951c80141ad0ef4c55fb79ef9d35ba7c13391f6 100644 (file)
@@ -17,7 +17,6 @@ class RandomFallBack(Plugin):
 
     def __init__(self, daemon):
         Plugin.__init__(self, daemon)
-        self.player = daemon.player
         self.daemon = daemon
         ##
         self.to_add = list()