From d0232dae194becb33696266083df400a54afbd27 Mon Sep 17 00:00:00 2001 From: kaliko Date: Fri, 18 Dec 2020 10:11:01 +0100 Subject: [PATCH] Major refactoring of Plugin class Add an AdvancedPlugin class with advanced Player/db query. It permits to share album search for album queue mode and other utilities previously available in LastFM (ie. webserv) only. --- sima/lib/plugin.py | 51 ++++++++-------- sima/lib/webserv.py | 108 ++++------------------------------ sima/plugins/internal/tags.py | 7 +-- 3 files changed, 40 insertions(+), 126 deletions(-) diff --git a/sima/lib/plugin.py b/sima/lib/plugin.py index 3839f87..83c28b5 100644 --- a/sima/lib/plugin.py +++ b/sima/lib/plugin.py @@ -50,9 +50,10 @@ class Plugin: def __init__(self, daemon): self.log = daemon.log - self.__daemon = daemon self.player = daemon.player self.plugin_conf = None + self.main_conf = daemon.config + self.sdb = daemon.sdb self.__get_config() def __str__(self): @@ -61,7 +62,7 @@ class Plugin: def __get_config(self): """Get plugin's specific configuration from global applications's config """ - conf = self.__daemon.config + conf = self.main_conf for sec in conf.sections(): # Discovering plugin conf if sec == self.__class__.__name__.lower(): self.plugin_conf = conf[sec] @@ -70,7 +71,7 @@ class Plugin: if not self.plugin_conf: self.plugin_conf = {'priority': '80'} #if self.plugin_conf: - # self.log.debug('Got config for %s: %s', self, self.plugin_conf) + # self.log.debug('Got config for %s: %s', self, self.plugin_conf) @property def priority(self): @@ -127,25 +128,22 @@ class Plugin: pass -class AdvancedLookUp: +class AdvancedPlugin(Plugin): """Object to derive from for plugins Exposes advanced music library look up with use of play history """ - def __init__(self, daemon): - self.log = daemon.log - self.daemon = daemon - self.player = daemon.player - # Query History def get_history(self, artist=False): - """Constructs list of already played artists. + """Constructs Track list of already played artists. + + :param Artist artist: Artist object to look history for """ - duration = self.daemon.config.getint('sima', 'history_duration') + duration = self.main_conf.getint('sima', 'history_duration') name = None if artist: name = artist.name - from_db = self.daemon.sdb.get_history(duration=duration, artist=name) + from_db = self.sdb.get_history(duration=duration, artist=name) hist = [Track(artist=tr[0], album=tr[1], title=tr[2], file=tr[3]) for tr in from_db] return hist @@ -168,8 +166,8 @@ class AdvancedLookUp: :param list(str) alist: """ hist = list() - duration = self.daemon.config.getint('sima', 'history_duration') - for art in self.daemon.sdb.get_artists_history(alist, duration=duration): + duration = self.main_conf.getint('sima', 'history_duration') + for art in self.sdb.get_artists_history(alist, duration=duration): if art not in hist: hist.insert(0, art) reorg = [art for art in alist if art not in hist] @@ -179,7 +177,8 @@ class AdvancedLookUp: # Find not recently played/unplayed def album_candidate(self, artist, unplayed=True): - """ + """Search an album for artist + :param Artist artist: Artist to fetch an album for :param bool unplayed: Fetch only unplayed album """ @@ -189,7 +188,7 @@ class AdvancedLookUp: return [] self.log.debug('Albums candidate: %s', albums) albums_hist = self.get_album_history(artist) - self.log.debug('Albums history: %s', albums_hist) + self.log.debug('Albums history: %s', [a.name for a in albums_hist]) albums_not_in_hist = [a for a in albums if a.name not in albums_hist] # Get to next artist if there are no unplayed albums if not albums_not_in_hist: @@ -236,16 +235,16 @@ class AdvancedLookUp: if unplayed: return None random.shuffle(not_in_hist) - candidates = [_ for _ in not_in_hist if _ not in deny_list] - # for trk in [_ for _ in not_in_hist if _ not in deny_list]: - # # Should use albumartist heuristic as well - # if self.plugin_conf.getboolean('single_album'): # pylint: disable=no-member - # if (trk.album == self.player.current.album or - # trk.album in [tr.album for tr in black_list]): - # self.log.debug('Found unplayed track ' + - # 'but from an album already queued: %s', trk) - # continue - # candidates.append(trk) + candidates = [] + for trk in [_ for _ in not_in_hist if _ not in deny_list]: + # Should use albumartist heuristic as well + if self.plugin_conf.getboolean('single_album', False): # pylint: disable=no-member + if (trk.album == self.player.current.album or + trk.album in [tr.album for tr in deny_list]): + self.log.debug('Found unplayed track ' + + 'but from an album already queued: %s', trk) + continue + candidates.append(trk) if not candidates: return None return random.choice(candidates) diff --git a/sima/lib/webserv.py b/sima/lib/webserv.py index c6a906f..c8ab86e 100644 --- a/sima/lib/webserv.py +++ b/sima/lib/webserv.py @@ -31,8 +31,7 @@ from hashlib import md5 # third parties components # local import -from .plugin import Plugin -from .track import Track +from .plugin import AdvancedPlugin from .meta import Artist, MetaContainer from ..utils.utils import WSError, WSNotFound, WSTimeout @@ -55,15 +54,13 @@ def cache(func): return wrapper -class WebService(Plugin): +class WebService(AdvancedPlugin): """similar artists webservice """ # pylint: disable=bad-builtin def __init__(self, daemon): - Plugin.__init__(self, daemon) - self.daemon_conf = daemon.config - self.sdb = daemon.sdb + super().__init__(daemon) self.history = daemon.short_history ## self.to_add = list() @@ -96,63 +93,6 @@ class WebService(Plugin): while len(val) > 150: val.popitem() - def get_history(self, artist): - """Constructs list of Track for already played titles for an artist. - """ - duration = self.daemon_conf.getint('sima', 'history_duration') - tracks_from_db = self.sdb.get_history(duration=duration, artist=artist) - # Construct Track() objects list from database history - played_tracks = [Track(artist=tr[-1], album=tr[1], title=tr[2], - file=tr[3]) for tr in tracks_from_db] - return played_tracks - - def filter_track(self, tracks): - """ - Extract one unplayed track from a Track object list. - * not in history - * not already in the queue - * not blacklisted - Then add to candidates in self.to_add - """ - artist = tracks[0].artist - # In random play mode use complete playlist to filter - if self.player.playmode.get('random'): - black_list = self.player.playlist + self.to_add - else: - black_list = self.player.queue + self.to_add - not_in_hist = list(set(tracks) - set(self.get_history(artist=artist))) - if self.plugin_conf.get('queue_mode') != 'top' and not not_in_hist: - self.log.debug('All tracks already played for "%s"', artist) - random.shuffle(not_in_hist) - candidate = [] - for trk in [_ for _ in not_in_hist if _ not in black_list]: - # Should use albumartist heuristic as well - if self.plugin_conf.getboolean('single_album'): # pylint: disable=no-member - if (trk.album == self.player.current.album or - trk.album in [tr.album for tr in black_list]): - self.log.debug('Found unplayed track ' + - 'but from an album already queued: %s', trk) - continue - candidate.append(trk) - if not candidate: - return False - self.to_add.append(random.choice(candidate)) - return True - - def _get_artists_list_reorg(self, alist): - """ - Move around items in artists_list in order to play first not recently - played artists - """ - hist = list() - duration = self.daemon_conf.getint('sima', 'history_duration') - for art in self.sdb.get_artists_history(alist, duration=duration): - if art not in hist: - hist.insert(0, art) - reorg = [art for art in alist if art not in hist] - reorg.extend(hist) - return reorg - @cache def get_artists_from_player(self, similarities): """ @@ -273,7 +213,7 @@ class WebService(Plugin): ret_extra = self.get_recursive_similar_artist() if ret_extra: # get them reorg to pick up best element - ret_extra = self._get_artists_list_reorg(ret_extra) + ret_extra = self.get_reorg_artists_list(ret_extra) # tries to pickup less artist from extra art if len(ret) < 4: ret_extra = MetaContainer(ret_extra) @@ -304,7 +244,7 @@ class WebService(Plugin): # Move around similars items to get in unplayed|not recently played # artist first. self.log.info('Got %d artists in library', len(ret)) - candidates = self._get_artists_list_reorg(list(ret)) + candidates = self.get_reorg_artists_list(list(ret)) if candidates: self.log.info(' / '.join(map(str, candidates))) return candidates @@ -325,35 +265,7 @@ class WebService(Plugin): nb_album_add = 0 target_album_to_add = self.plugin_conf.getint('album_to_add') # pylint: disable=no-member for artist in artists: - self.log.info('Looking for an album to add for "%s"...' % artist) - albums = self.player.search_albums(artist) - if not albums: - continue - self.log.debug('Albums candidate: %s', albums) - albums_hist = self._get_album_history(artist) - albums_not_in_hist = [a for a in albums if a.name not in albums_hist] - # Get to next artist if there are no unplayed albums - if not albums_not_in_hist: - self.log.info('No unplayed album found for "%s"' % artist) - continue - album_to_queue = [] - random.shuffle(albums_not_in_hist) - for album in albums_not_in_hist: - # Controls the album found is not already queued - if album in {t.album for t in self.player.queue}: - self.log.debug('"%s" already queued, skipping!', album) - continue - # In random play mode use complete playlist to filter - if self.player.playmode.get('random'): - if album in {t.album for t in self.player.playlist}: - self.log.debug('"%s" already in playlist, skipping!', album) - continue - album_to_queue = album - if not album_to_queue: - self.log.info('No album found for "%s"', artist) - continue - self.log.info('%s album candidate: %s - %s', self.ws.name, - artist, album_to_queue) + album = self.album_candidate(artist) nb_album_add += 1 candidates = self.player.find_tracks(album) if self.plugin_conf.getboolean('shuffle_album'): @@ -387,7 +299,9 @@ class WebService(Plugin): found = self.player.search_track(artist, trk.title) if found: random.shuffle(found) - if self.filter_track(found): + top_trk = self.filter_track(found) + if top_trk: + self.to_add.append(top_trk) break def _track(self): @@ -403,7 +317,9 @@ class WebService(Plugin): self.log.debug('Found nothing to queue for %s', artist) continue # find tracks not in history for artist - self.filter_track(found) + track_candidate = self.filter_track(found) + if track_candidate: + self.to_add.append(track_candidate) if len(self.to_add) == nbtracks_target: break if not self.to_add: diff --git a/sima/plugins/internal/tags.py b/sima/plugins/internal/tags.py index 1536c5c..0af81ef 100644 --- a/sima/plugins/internal/tags.py +++ b/sima/plugins/internal/tags.py @@ -28,8 +28,8 @@ import random from musicpd import CommandError # local import -from ...lib.plugin import Plugin, AdvancedLookUp -from ...lib.meta import Artist, Album +from ...lib.plugin import AdvancedPlugin +from ...lib.meta import Artist from ...utils.utils import PluginException @@ -53,7 +53,7 @@ def forge_filter(cfg): return mpd_filter -class Tags(Plugin, AdvancedLookUp): +class Tags(AdvancedPlugin): """Add track based on tags content """ supported_tags = {'comment', 'date', 'genre', 'label', 'originaldate'} @@ -61,7 +61,6 @@ class Tags(Plugin, AdvancedLookUp): def __init__(self, daemon): super().__init__(daemon) - self.daemon = daemon self._control_conf() self.mpd_filter = forge_filter(self.plugin_conf) self._setup_tagsneeded() -- 2.39.2