class RequestPlugin(SongsMenuPlugin): PLUGIN_ID = "Request" PLUGIN_NAME = _("Autoqueue Request") # noqa PLUGIN_DESC = _( # noqa "Request songs that autoqueue will then work its way toward.") PLUGIN_ICON = "gtk-find-and-replace" PLUGIN_VERSION = "0.1" def __init__(self, *args): super(RequestPlugin, self).__init__(*args) self.requests = Requests() def plugin_songs(self, songs): """Add the work to the coroutine pool.""" for song in songs: self.requests.add(song('~filename'))
def __init__(self, player): self._cache_dir = None self.blocking = Blocking() self.configuration = Configuration() self.context = None self.cache = Cache() bus = dbus.SessionBus() sim = bus.get_object('org.autoqueue', '/org/autoqueue/Similarity', follow_name_owner_changes=True) self.similarity = dbus.Interface( sim, dbus_interface='org.autoqueue.SimilarityInterface') self.has_gaia = self.similarity.has_gaia() self.player = player self.player.set_variables_from_config(self.configuration) self.cache.set_nearby_artist(self.configuration) self.requests = Requests()
def __init__(self, player): self._cache_dir = None self.blocking = Blocking() self.configuration = Configuration() self.context = None self.cache = Cache() bus = dbus.SessionBus() sim = bus.get_object( 'org.autoqueue', '/org/autoqueue/Similarity', follow_name_owner_changes=True) self.similarity = dbus.Interface( sim, dbus_interface='org.autoqueue.SimilarityInterface') self.has_gaia = self.similarity.has_gaia() self.player = player self.player.set_variables_from_config(self.configuration) self.cache.set_nearby_artist(self.configuration) self.requests = Requests()
def __init__(self, *args): super(RequestPlugin, self).__init__(*args) self.requests = Requests()
class AutoQueueBase(object): """Generic base class for autoqueue plugins.""" def __init__(self, player): self._cache_dir = None self.blocking = Blocking() self.configuration = Configuration() self.context = None self.cache = Cache() bus = dbus.SessionBus() sim = bus.get_object( 'org.autoqueue', '/org/autoqueue/Similarity', follow_name_owner_changes=True) self.similarity = dbus.Interface( sim, dbus_interface='org.autoqueue.SimilarityInterface') self.has_gaia = self.similarity.has_gaia() self.player = player self.player.set_variables_from_config(self.configuration) self.cache.set_nearby_artist(self.configuration) self.requests = Requests() @property def use_gaia(self): return self.configuration.use_gaia and self.has_gaia def error_handler(self, *args, **kwargs): """Log errors when calling D-Bus methods in a async way.""" print('Error handler received: %r, %r' % (args, kwargs)) def allowed(self, song): """Check whether a song is allowed to be queued.""" filename = song.get_filename() for qsong in self.get_last_songs(): if qsong.get_filename() == filename: return False if self.requests.has(filename): return True date_search = re.compile("([0-9]{4}-)?%02d-%02d" % ( self.eoq.month, self.eoq.day)) for tag in song.get_stripped_tags(): if date_search.match(tag): return True for artist in song.get_artists(): if artist in self.blocking.get_blocked_artists( self.get_last_songs()): return False return True def on_song_ended(self, song, skipped): """Should be called by the plugin when a song ends or is skipped.""" if song is None: return if skipped: return for artist_name in song.get_artists(): self.blocking.block_artist(artist_name) def on_song_started(self, song): """Should be called by the plugin when a new song starts. If the right conditions apply, we start looking for new songs to queue. """ if song is None: return self.cache.song = song if self.cache.running: return if self.configuration.desired_queue_length == 0 or \ self.queue_needs_songs(): self.queue_song() self.blocking.unblock_artists() self.pop_request(song) def pop_request(self, song): filename = song.get_filename() if self.requests.has(filename): self.cache.current_request = None self.requests.pop(filename) def on_removed(self, songs): if not self.use_gaia: return for song in songs: self.remove_missing_track(song.get_filename()) def queue_needs_songs(self): """Determine whether the queue needs more songs added.""" queue_length = self.player.get_queue_length() return queue_length < self.configuration.desired_queue_length @property def eoq(self): return datetime.now() + timedelta(0, self.player.get_queue_length()) def construct_filenames_search(self, filenames): return self.player.construct_files_search(filenames) def construct_search(self, artist=None, title=None, tags=None, filename=None): """Construct a search based on several criteria.""" if filename: return self.player.construct_file_search(filename) if title: return self.player.construct_track_search(artist, title) if artist: return self.player.construct_artist_search(artist) if tags: return self.player.construct_tag_search(tags) def queue_song(self): """Queue a single track.""" self.cache.running = True self.cache.last_songs = self.get_last_songs() song = self.cache.last_song = self.cache.last_songs.pop() self.analyze_and_callback( song.get_filename(), reply_handler=self.analyzed, empty_handler=self.gaia_reply_handler) def analyzed(self): song = self.cache.last_song filename = ensure_string(song.get_filename()) if not filename: return if self.use_gaia: print('Get similar tracks for: %s' % filename) request = self.requests.get_first() if request: request = ensure_string(request) if request: self.similarity.get_ordered_gaia_tracks_by_request( filename, self.configuration.number, request, reply_handler=self.gaia_reply_handler, error_handler=self.error_handler, timeout=TIMEOUT) return self.similarity.get_ordered_gaia_tracks( filename, self.configuration.number, reply_handler=self.gaia_reply_handler, error_handler=self.error_handler, timeout=TIMEOUT) else: self.gaia_reply_handler([]) def gaia_reply_handler(self, results): """Handler for (gaia) similar tracks returned from dbus.""" self.player.execute_async(self._gaia_reply_handler, results=results) def continue_queueing(self): if not self.queue_needs_songs(): self.done() else: self.queue_song() def _gaia_reply_handler(self, results=None): """Exexute processing asynchronous.""" self.cache.found = False if results: for _ in self.process_filename_results([{'score': match, 'filename': filename} for match, filename in results]): yield if self.cache.found: self.continue_queueing() return self.get_similar_tracks() def get_similar_tracks(self): if not self.configuration.use_lastfm: self.similar_artists_handler([]) return last_song = self.cache.last_song artist_name = last_song.get_artist() title = last_song.get_title() if artist_name and title: print('Get similar tracks for: %s - %s' % (artist_name, title)) self.similarity.get_ordered_similar_tracks( artist_name, title, reply_handler=self.similar_tracks_handler, error_handler=self.error_handler, timeout=TIMEOUT) else: self.similar_tracks_handler([]) def done(self): """Analyze the last song and stop.""" song = self.get_last_songs()[-1] self.analyze_and_callback(song.get_filename()) self.cache.running = False def similar_tracks_handler(self, results): """Handler for similar tracks returned from dbus.""" self.player.execute_async( self._similar_tracks_handler, results=results) def _similar_tracks_handler(self, results=None): """Exexute processing asynchronous.""" self.cache.found = False for _ in self.process_results([{'score': match, 'artist': artist, 'title': title} for match, artist, title in results], invert_scores=True): yield if self.cache.found: self.continue_queueing() return self.get_similar_artists() def get_similar_artists(self): artists = [ a.encode('utf-8') for a in self.cache.last_song.get_artists()] print('Get similar artists for %s' % artists) self.similarity.get_ordered_similar_artists( artists, reply_handler=self.similar_artists_handler, error_handler=self.error_handler, timeout=TIMEOUT) def similar_artists_handler(self, results): """Handler for similar artists returned from dbus.""" self.player.execute_async( self._similar_artists_handler, results=results) def _similar_artists_handler(self, results=None): """Exexute processing asynchronous.""" self.cache.found = False if results: for _ in self.process_results([{'score': match, 'artist': artist} for match, artist in results], invert_scores=True): yield if self.cache.found: self.continue_queueing() return if self.configuration.use_groupings: for _ in self.process_results( self.get_ordered_similar_by_tag(self.cache.last_song), invert_scores=True): yield if self.cache.found: self.continue_queueing() return if not self.cache.last_songs: self.cache.running = False return song = self.cache.last_song = self.cache.last_songs.pop() self.analyze_and_callback( song.get_filename(), reply_handler=self.analyzed, empty_handler=self.gaia_reply_handler) def analyze_and_callback(self, filename, reply_handler=no_op, empty_handler=no_op): filename = ensure_string(filename) if not filename: return if self.use_gaia: print('Analyzing: %s' % filename) self.similarity.analyze_track( filename, reply_handler=reply_handler, error_handler=self.error_handler, timeout=TIMEOUT) else: empty_handler([]) @staticmethod def satisfies(song, criteria): """Check whether the song satisfies any of the criteria.""" filename = criteria.get('filename') if filename: return filename == song.get_filename() title = criteria.get('title') artist = criteria.get('artist') if title: return ( song.get_title().lower() == title.lower() and song.get_artist().lower() == artist.lower()) if artist: artist_lower = artist.lower() for song_artist in song.get_artists(): if song_artist.lower() == artist_lower: return True return False tags = criteria.get('tags') song_tags = song.get_tags() for tag in tags: if (tag in song_tags or 'artist:%s' % (tag,) in song_tags or 'album:%s' % (tag,) in song_tags): return True return False def search_filenames(self, results): filenames = [r['filename'] for r in results] search = self.construct_filenames_search(filenames) self.perform_search(search, results) def search_database(self, results): """Search for songs in results.""" for result in results: search = self.construct_search( artist=result.get('artist'), title=result.get('title'), filename=result.get('filename'), tags=result.get('tags')) self.perform_search(search, [result]) yield def get_current_request(self): if not self.cache.current_request: filename = self.requests.get_first() if not filename: return search = self.construct_filenames_search([filename]) songs = self.player.search(search) if not songs: return self.cache.current_request = songs[0] return self.cache.current_request def perform_search(self, search, results): songs = set( self.player.search( search, restrictions=self.configuration.restrictions)) found = set() for result in results: for song in songs - found: if self.satisfies(song, result): result['song'] = song found.add(song) break else: if not self.configuration.restrictions: filename = result.get('filename') if filename: self.remove_missing_track(filename) def remove_missing_track(self, filename): print('Remove similarity for %s' % filename) if not isinstance(filename, str): filename = str(filename, 'utf-8') self.similarity.remove_track_by_filename( filename, reply_handler=no_op, error_handler=self.error_handler, timeout=TIMEOUT) def adjust_scores(self, results, invert_scores): """Adjust scores based on similarity with previous song and context.""" if self.configuration.contextualize: self.get_current_request() self.context = Context( context_date=self.eoq, configuration=self.configuration, cache=self.cache) maximum_score = max(result['score'] for result in results) + 1 for result in results[:]: if 'song' not in result: results.remove(result) continue if invert_scores: result['score'] = maximum_score - result['score'] self.context.adjust_score(result) yield def process_results(self, results, invert_scores=False): """Process results and queue best one(s).""" if not results: return for _ in self.search_database(results): yield for _ in self.adjust_scores(results, invert_scores): yield if not results: return self.pick_result(results) def pick_result(self, results): for number, result in enumerate(sorted(results, key=lambda x: x['score'])): song = result.get('song') if not song: print("'song' not found in %s" % result) continue self.log_lookup(number, result) frequency = song.get_play_frequency() if frequency is NotImplemented: frequency = 1 rating = song.get_rating() if rating is NotImplemented: rating = THRESHOLD for reason in result.get('reasons', []): print(" %s" % (reason,)) print("score: %.5f, play frequency %.5f" % (rating, frequency)) comparison = rating if self.configuration.favor_new: comparison -= frequency if (frequency > 0 or not self.configuration.favor_new) and \ random.random() > comparison: continue if self.maybe_enqueue_album(song): self.cache.found = True return if self.allowed(song): self.enqueue_song(song) self.cache.found = True return def process_filename_results(self, results): if not results: return self.search_filenames(results) for _ in self.adjust_scores(results, invert_scores=False): yield if not results: return self.pick_result(results) @staticmethod def log_lookup(number, result): look_for = str(result.get('artist', '')) if look_for: title = str(result.get('title', '')) if title: look_for += ' - ' + title elif 'filename' in result: look_for = str(result['filename']) elif 'tags' in result: look_for = result['tags'] else: print(repr(result)) look_for = str(result) print('%03d: %06d %s' % (number + 1, result.get('score', 0), look_for)) def maybe_enqueue_album(self, song): """Determine if a whole album should be queued, and do so.""" if (self.configuration.whole_albums and song.get_tracknumber() == 1 and random.random() > .5): album = song.get_album() album_artist = song.get_album_artist() album_id = song.get_musicbrainz_albumid() if album and album.lower() not in BANNED_ALBUMS: return self.enqueue_album(album, album_artist, album_id) return False def enqueue_song(self, song): self.cache.add_to_previous_terms(song) self.player.enqueue(song) def enqueue_album(self, album, album_artist, album_id): """Try to enqueue whole album.""" search = self.player.construct_album_search( album=album, album_artist=album_artist, album_id=album_id) songs = sorted([ (song.get_discnumber(), song.get_tracknumber(), song) for song in self.player.search(search)]) if songs and all(self.allowed(song[2]) for song in songs): for _, _, song in songs: self.enqueue_song(song) return True return False def get_last_songs(self): """Return the currently playing song plus the songs in the queue.""" queue = self.player.get_songs_in_queue() or [] return [self.cache.song] + queue def get_ordered_similar_by_tag(self, last_song): """Get similar tracks by tag.""" tag_set = set(last_song.get_non_geo_tags()) if not tag_set: return [] search = self.construct_search(tags=list(tag_set)) songs = sorted( [(tag_score(song, tag_set), song) for song in self.player.search(search)], reverse=True) return [ {'score': score, 'filename': song.get_filename()} for score, song in songs]
class AutoQueueBase(object): """Generic base class for autoqueue plugins.""" def __init__(self, player): self._cache_dir = None self.blocking = Blocking() self.configuration = Configuration() self.context = None self.cache = Cache() bus = dbus.SessionBus() sim = bus.get_object('org.autoqueue', '/org/autoqueue/Similarity', follow_name_owner_changes=True) self.similarity = dbus.Interface( sim, dbus_interface='org.autoqueue.SimilarityInterface') self.has_gaia = self.similarity.has_gaia() self.player = player self.player.set_variables_from_config(self.configuration) self.cache.set_nearby_artist(self.configuration) self.requests = Requests() @property def use_gaia(self): return self.configuration.use_gaia and self.has_gaia def error_handler(self, *args, **kwargs): """Log errors when calling D-Bus methods in a async way.""" print('Error handler received: %r, %r' % (args, kwargs)) def allowed(self, song): """Check whether a song is allowed to be queued.""" filename = song.get_filename() for qsong in self.get_last_songs(): if qsong.get_filename() == filename: return False if self.requests.has(filename): return True date_search = re.compile("([0-9]{4}-)?%02d-%02d" % (self.eoq.month, self.eoq.day)) for tag in song.get_stripped_tags(): if date_search.match(tag): return True for artist in song.get_artists(): if artist in self.blocking.get_blocked_artists( self.get_last_songs()): return False return True def on_song_ended(self, song, skipped): """Should be called by the plugin when a song ends or is skipped.""" if song is None: return if skipped: return for artist_name in song.get_artists(): self.blocking.block_artist(artist_name) def on_song_started(self, song): """Should be called by the plugin when a new song starts. If the right conditions apply, we start looking for new songs to queue. """ if song is None: return self.cache.song = song if self.cache.running: return if self.configuration.desired_queue_length == 0 or \ self.queue_needs_songs(): self.queue_song() self.blocking.unblock_artists() self.pop_request(song) def pop_request(self, song): filename = song.get_filename() if self.requests.has(filename): self.cache.current_request = None self.requests.pop(filename) def on_removed(self, songs): if not self.use_gaia: return for song in songs: self.remove_missing_track(song.get_filename()) def queue_needs_songs(self): """Determine whether the queue needs more songs added.""" queue_length = self.player.get_queue_length() return queue_length < self.configuration.desired_queue_length @property def eoq(self): return datetime.now() + timedelta(0, self.player.get_queue_length()) def construct_filenames_search(self, filenames): return self.player.construct_files_search(filenames) def construct_search(self, artist=None, title=None, tags=None, filename=None): """Construct a search based on several criteria.""" if filename: return self.player.construct_file_search(filename) if title: return self.player.construct_track_search(artist, title) if artist: return self.player.construct_artist_search(artist) if tags: return self.player.construct_tag_search(tags) def queue_song(self): """Queue a single track.""" self.cache.running = True self.cache.last_songs = self.get_last_songs() song = self.cache.last_song = self.cache.last_songs.pop() self.analyze_and_callback(song.get_filename(), reply_handler=self.analyzed, empty_handler=self.gaia_reply_handler) def analyzed(self): song = self.cache.last_song filename = ensure_string(song.get_filename()) if not filename: return if self.use_gaia: print('Get similar tracks for: %s' % filename) request = self.requests.get_first() if request: request = ensure_string(request) if request: self.similarity.get_ordered_gaia_tracks_by_request( filename, self.configuration.number, request, reply_handler=self.gaia_reply_handler, error_handler=self.error_handler, timeout=TIMEOUT) return self.similarity.get_ordered_gaia_tracks( filename, self.configuration.number, reply_handler=self.gaia_reply_handler, error_handler=self.error_handler, timeout=TIMEOUT) else: self.gaia_reply_handler([]) def gaia_reply_handler(self, results): """Handler for (gaia) similar tracks returned from dbus.""" self.player.execute_async(self._gaia_reply_handler, results=results) def continue_queueing(self): if not self.queue_needs_songs(): self.done() else: self.queue_song() def _gaia_reply_handler(self, results=None): """Exexute processing asynchronous.""" self.cache.found = False if results: for _ in self.process_filename_results([{ 'score': match, 'filename': filename } for match, filename in results]): yield if self.cache.found: self.continue_queueing() return self.get_similar_tracks() def get_similar_tracks(self): if not self.configuration.use_lastfm: self.similar_artists_handler([]) return last_song = self.cache.last_song artist_name = last_song.get_artist() title = last_song.get_title() if artist_name and title: print('Get similar tracks for: %s - %s' % (artist_name, title)) self.similarity.get_ordered_similar_tracks( artist_name, title, reply_handler=self.similar_tracks_handler, error_handler=self.error_handler, timeout=TIMEOUT) else: self.similar_tracks_handler([]) def done(self): """Analyze the last song and stop.""" song = self.get_last_songs()[-1] self.analyze_and_callback(song.get_filename()) self.cache.running = False def similar_tracks_handler(self, results): """Handler for similar tracks returned from dbus.""" self.player.execute_async(self._similar_tracks_handler, results=results) def _similar_tracks_handler(self, results=None): """Exexute processing asynchronous.""" self.cache.found = False for _ in self.process_results([{ 'score': match, 'artist': artist, 'title': title } for match, artist, title in results], invert_scores=True): yield if self.cache.found: self.continue_queueing() return self.get_similar_artists() def get_similar_artists(self): artists = [ a.encode('utf-8') for a in self.cache.last_song.get_artists() ] print('Get similar artists for %s' % artists) self.similarity.get_ordered_similar_artists( artists, reply_handler=self.similar_artists_handler, error_handler=self.error_handler, timeout=TIMEOUT) def similar_artists_handler(self, results): """Handler for similar artists returned from dbus.""" self.player.execute_async(self._similar_artists_handler, results=results) def _similar_artists_handler(self, results=None): """Exexute processing asynchronous.""" self.cache.found = False if results: for _ in self.process_results([{ 'score': match, 'artist': artist } for match, artist in results], invert_scores=True): yield if self.cache.found: self.continue_queueing() return if self.configuration.use_groupings: for _ in self.process_results(self.get_ordered_similar_by_tag( self.cache.last_song), invert_scores=True): yield if self.cache.found: self.continue_queueing() return if not self.cache.last_songs: self.cache.running = False return song = self.cache.last_song = self.cache.last_songs.pop() self.analyze_and_callback(song.get_filename(), reply_handler=self.analyzed, empty_handler=self.gaia_reply_handler) def analyze_and_callback(self, filename, reply_handler=no_op, empty_handler=no_op): filename = ensure_string(filename) if not filename: return if self.use_gaia: print('Analyzing: %s' % filename) self.similarity.analyze_track(filename, reply_handler=reply_handler, error_handler=self.error_handler, timeout=TIMEOUT) else: empty_handler([]) @staticmethod def satisfies(song, criteria): """Check whether the song satisfies any of the criteria.""" filename = criteria.get('filename') if filename: return filename == song.get_filename() title = criteria.get('title') artist = criteria.get('artist') if title: return (song.get_title().lower() == title.lower() and song.get_artist().lower() == artist.lower()) if artist: artist_lower = artist.lower() for song_artist in song.get_artists(): if song_artist.lower() == artist_lower: return True return False tags = criteria.get('tags') song_tags = song.get_tags() for tag in tags: if (tag in song_tags or 'artist:%s' % (tag, ) in song_tags or 'album:%s' % (tag, ) in song_tags): return True return False def search_filenames(self, results): filenames = [r['filename'] for r in results] search = self.construct_filenames_search(filenames) self.perform_search(search, results) def search_database(self, results): """Search for songs in results.""" for result in results: search = self.construct_search(artist=result.get('artist'), title=result.get('title'), filename=result.get('filename'), tags=result.get('tags')) self.perform_search(search, [result]) yield def get_current_request(self): if not self.cache.current_request: filename = self.requests.get_first() if not filename: return search = self.construct_filenames_search([filename]) songs = self.player.search(search) if not songs: return self.cache.current_request = songs[0] return self.cache.current_request def perform_search(self, search, results): songs = set( self.player.search(search, restrictions=self.configuration.restrictions)) found = set() for result in results: for song in songs - found: if self.satisfies(song, result): result['song'] = song found.add(song) break else: if not self.configuration.restrictions: filename = result.get('filename') if filename: self.remove_missing_track(filename) def remove_missing_track(self, filename): print('Remove similarity for %s' % filename) if not isinstance(filename, str): filename = str(filename, 'utf-8') self.similarity.remove_track_by_filename( filename, reply_handler=no_op, error_handler=self.error_handler, timeout=TIMEOUT) def adjust_scores(self, results, invert_scores): """Adjust scores based on similarity with previous song and context.""" if self.configuration.contextualize: self.get_current_request() self.context = Context(context_date=self.eoq, configuration=self.configuration, cache=self.cache) maximum_score = max(result['score'] for result in results) + 1 for result in results[:]: if 'song' not in result: results.remove(result) continue if invert_scores: result['score'] = maximum_score - result['score'] self.context.adjust_score(result) yield def process_results(self, results, invert_scores=False): """Process results and queue best one(s).""" if not results: return for _ in self.search_database(results): yield for _ in self.adjust_scores(results, invert_scores): yield if not results: return self.pick_result(results) def pick_result(self, results): for number, result in enumerate( sorted(results, key=lambda x: x['score'])): song = result.get('song') if not song: print("'song' not found in %s" % result) continue self.log_lookup(number, result) frequency = song.get_play_frequency() if frequency is NotImplemented: frequency = 1 rating = song.get_rating() if rating is NotImplemented: rating = THRESHOLD for reason in result.get('reasons', []): print(" %s" % (reason, )) print("score: %.5f, play frequency %.5f" % (rating, frequency)) comparison = rating if self.configuration.favor_new: comparison -= frequency if (frequency > 0 or not self.configuration.favor_new) and \ random.random() > comparison: continue if self.maybe_enqueue_album(song): self.cache.found = True return if self.allowed(song): self.enqueue_song(song) self.cache.found = True return def process_filename_results(self, results): if not results: return self.search_filenames(results) for _ in self.adjust_scores(results, invert_scores=False): yield if not results: return self.pick_result(results) @staticmethod def log_lookup(number, result): look_for = str(result.get('artist', '')) if look_for: title = str(result.get('title', '')) if title: look_for += ' - ' + title elif 'filename' in result: look_for = str(result['filename']) elif 'tags' in result: look_for = result['tags'] else: print(repr(result)) look_for = str(result) print('%03d: %06d %s' % (number + 1, result.get('score', 0), look_for)) def maybe_enqueue_album(self, song): """Determine if a whole album should be queued, and do so.""" if (self.configuration.whole_albums and song.get_tracknumber() == 1 and random.random() > .5): album = song.get_album() album_artist = song.get_album_artist() album_id = song.get_musicbrainz_albumid() if album and album.lower() not in BANNED_ALBUMS: return self.enqueue_album(album, album_artist, album_id) return False def enqueue_song(self, song): self.cache.add_to_previous_terms(song) self.player.enqueue(song) def enqueue_album(self, album, album_artist, album_id): """Try to enqueue whole album.""" search = self.player.construct_album_search(album=album, album_artist=album_artist, album_id=album_id) songs = sorted([(song.get_discnumber(), song.get_tracknumber(), song) for song in self.player.search(search)]) if songs and all(self.allowed(song[2]) for song in songs): for _, _, song in songs: self.enqueue_song(song) return True return False def get_last_songs(self): """Return the currently playing song plus the songs in the queue.""" queue = self.player.get_songs_in_queue() or [] return [self.cache.song] + queue def get_ordered_similar_by_tag(self, last_song): """Get similar tracks by tag.""" tag_set = set(last_song.get_non_geo_tags()) if not tag_set: return [] search = self.construct_search(tags=list(tag_set)) songs = sorted([(tag_score(song, tag_set), song) for song in self.player.search(search)], reverse=True) return [{ 'score': score, 'filename': song.get_filename() } for score, song in songs]