X-Git-Url: http://git.kaliko.me/?a=blobdiff_plain;f=sima%2Flib%2Fplugin.py;h=9a721bbd4630edd2fadbe5a4ecf2f32e91e8ee32;hb=f76a5c2fce5c9a515efb93991c53ce8e0edaf197;hp=3839f8754505591550f632cee6a31ab27db1b59d;hpb=35012360f9d528bca294f6bbe2ca3ad843d09630;p=mpd-sima.git diff --git a/sima/lib/plugin.py b/sima/lib/plugin.py index 3839f87..9a721bb 100644 --- a/sima/lib/plugin.py +++ b/sima/lib/plugin.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2013-2015, 2020 kaliko +# Copyright (c) 2013-2015, 2020-2021 kaliko # # This file is part of sima # @@ -23,8 +23,7 @@ Plugin object to derive from import random -from .track import Track -from .meta import Album, Artist +from .meta import Artist, MetaContainer class Plugin: @@ -42,17 +41,18 @@ class Plugin: def info(cls): """self documenting class method """ - doc = 'Undocumented plugin! Fill "{}" docstring'.format(cls.__name__) + doc = f'Undocumented plugin! Fill "{cls.__name__}" docstring' if cls.__doc__: doc = cls.__doc__.strip(' \n').splitlines()[0] return {'name': cls.__name__, - 'doc': doc,} + 'doc': doc} 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 +61,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 +70,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): @@ -81,148 +81,134 @@ class Plugin: Called when the daemon().run() is called and right after the player has connected successfully. """ - pass def callback_player(self): """ Called on player changes, stopped, paused, skipped """ - pass def callback_player_database(self): """ Called on player music library changes """ - pass def callback_playlist(self): """ Called on playlist changes Not returning data """ - pass def callback_next_song(self): """ Could be use to scrobble, maintain an history… Not returning data, """ - pass def callback_need_track(self): """ Returns a list of Track objects to add """ - pass - - def callback_need_track_fb(self): - """ - Called when callback_need_track failled to find tracks to queue - Returns a list of Track objects to add - """ - pass def shutdown(self): """Called on application shutdown""" - 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. - """ - duration = self.daemon.config.getint('sima', 'history_duration') - name = None - if artist: - name = artist.name - from_db = self.daemon.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 + def get_history(self): + """Returns a Track list of already played artists.""" + duration = self.main_conf.getint('sima', 'history_duration') + return self.sdb.fetch_history(duration=duration) def get_album_history(self, artist): """Retrieve album history""" - hist = [] - tracks_from_db = self.get_history(artist=artist) - for trk in tracks_from_db: - if trk.album and trk.album in hist: - continue - hist.append(Album(name=trk.album, artist=Artist(trk.artist))) - return hist + duration = self.main_conf.getint('sima', 'history_duration') + return self.sdb.fetch_albums_history(needle=artist, duration=duration) def get_reorg_artists_list(self, alist): """ - Move around items in artists_list in order to play first not recently - played artists + Move around items in alist in order to have first not recently + played (or about to be played) artists. - :param list(str) alist: + :param {Artist} alist: Artist objects list/container """ - hist = list() - duration = self.daemon.config.getint('sima', 'history_duration') - for art in self.daemon.sdb.get_artists_history(alist, duration=duration): + queued_artist = MetaContainer([Artist(_.artist) for _ in + self.player.queue if _.artist]) + not_queued_artist = alist - queued_artist + duration = self.main_conf.getint('sima', 'history_duration') + hist = [] + for art in self.sdb.fetch_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] + if art not in queued_artist: + hist.insert(0, art) + else: + hist.append(art) + # Find not recently played (not in history) & not in queue + reorg = [art for art in not_queued_artist if art not in hist] reorg.extend(hist) return reorg # /Query History # 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 """ - self.log.info('Searching an album for "%s"...' % artist) + self.log.info('Searching an album for "%s"...', artist) albums = self.player.search_albums(artist) if not albums: - return [] - self.log.debug('Albums candidate: %s', albums) + return None + self.log.debug('Albums to choose from: %s', albums) albums_hist = self.get_album_history(artist) - self.log.debug('Albums history: %s', albums_hist) + self.log.trace('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: - self.log.info('No unplayed album found for "%s"' % artist) + self.log.info('No unplayed album found for "%s"', artist) if unplayed: - return [] + return None random.shuffle(albums_not_in_hist) albums_not_in_hist.extend(albums_hist) + self.log.trace('Album candidates: %s', albums_not_in_hist) album_to_queue = [] 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}: + if album in {t.Album.name for t in self.player.queue}: self.log.debug('"%s" already queued, skipping!', album) - return [] + continue # In random play mode use complete playlist to filter + # Yes indeed, some users play in random with album mode :| if self.player.playmode.get('random'): - if album in {t.album for t in self.player.playlist}: + if album in {t.Album.name for t in self.player.playlist}: self.log.debug('"%s" already in playlist, skipping!', album) - return [] + continue album_to_queue = album + break if not album_to_queue: self.log.info('No album found for "%s"', artist) - return [] - self.log.info('%s album candidate: %s - %s', self.__class__.__name__, - artist, album_to_queue) + return None + self.log.info('%s plugin chose album: %s - %s', + self.__class__.__name__, artist, album_to_queue) return album_to_queue - def filter_track(self, tracks, unplayed=False): + def filter_track(self, tracks, chosen=None, unplayed=False): """ Extract one unplayed track from a Track object list. * not in history * not already in the queue + + :param list(Track) tracks: List of tracks to chose from + :param list(Track) chosen: List of tracks previously chosen + :param bool unplayed: chose only unplayed (honoring history duration setting) + :return: A Track + :rtype: Track """ artist = tracks[0].Artist # In random play mode use complete playlist to filter @@ -230,22 +216,24 @@ class AdvancedLookUp: deny_list = self.player.playlist else: deny_list = self.player.queue - not_in_hist = list(set(tracks) - set(self.get_history(artist=artist))) + not_in_hist = list(set(tracks) - set(self.sdb.fetch_history(artist=artist))) if not not_in_hist: self.log.debug('All tracks already played for "%s"', artist) 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 + albums = [tr.Album for tr in deny_list] + albums += [tr.Album for tr in chosen] + if (trk.Album == self.player.current.Album or + trk.Album in albums): + 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)