def get_search_suggestions( self, musiq: Musiq, query: str, playlist: bool ) -> List[Tuple[str, str]]: """Returns a list of suggested items for the given query. Returns playlists if :param playlist: is True, songs otherwise.""" result = self.web_client.get( "search", params={ "q": query, "limit": "20", "market": "from_token", "type": "playlist" if playlist else "track", }, ) if playlist: items = result["playlists"]["items"] else: items = result["tracks"]["items"] suggestions = [] for item in items: external_url = item["external_urls"]["spotify"] title = item["name"] if playlist: displayname = title else: artist = item["artists"][0]["name"] # apply filter from the settings if song_utils.is_forbidden(musiq, artist) or song_utils.is_forbidden( musiq, title ): continue displayname = song_utils.displayname(artist, title) suggestions.append((displayname, external_url)) # remove duplicates and filter by keywords chosen_displaynames = set() unique_suggestions = [] for suggestion in suggestions: if suggestion[0] in chosen_displaynames: continue unique_suggestions.append(suggestion) chosen_displaynames.add(suggestion[0]) return unique_suggestions
def get_suggestions(self, request): terms = request.GET['term'].split() remaining_songs = ArchivedQuery.objects.select_related('song') \ .values('song__id', 'song__title', 'song__url', 'song__artist', 'song__counter', 'query') for term in terms: remaining_songs = remaining_songs.filter( Q(song__title__icontains=term) | Q(song__artist__icontains=term) | Q(query__icontains=term)) remaining_songs = remaining_songs \ .values('song__id', 'song__title', 'song__url', 'song__artist', 'song__counter') \ .distinct() \ .order_by('-song__counter') \ [:20] results = [] for song in remaining_songs: if song_utils.path_from_url(song['song__url']) is not None: cached = True else: cached = False # don't suggest online songs when we don't have internet if not self.musiq.base.settings.has_internet: if not cached: continue result_dict = { 'key': song['song__id'], 'value': song_utils.displayname(song['song__artist'], song['song__title']), 'counter': song['song__counter'], 'type': 'cached' if cached else 'online', } results.append(result_dict) return HttpResponse(json.dumps(results))
def analyse(self, request): startdate = request.POST.get('startdate') starttime = request.POST.get('starttime') enddate = request.POST.get('enddate') endtime = request.POST.get('endtime') if startdate is None or startdate == '' \ or starttime is None or starttime == '' \ or enddate is None or enddate == '' \ or endtime is None or endtime == '': return HttpResponseBadRequest('All fields are required') start = dateparse.parse_datetime(startdate + 'T' + starttime) end = dateparse.parse_datetime(enddate + 'T' + endtime) if start is None or end is None: return HttpResponseBadRequest('invalid start-/endtime given') if start >= end: return HttpResponseBadRequest('start has to be before end') start = timezone.make_aware(start) end = timezone.make_aware(end) played = PlayLog.objects.all().filter(created__gte=start).filter(created__lt=end) requested = RequestLog.objects.all().filter(created__gte=start).filter(created__lt=end) played_count = played.values('song__url', 'song__artist', 'song__title').values('song__url', 'song__artist', 'song__title', count=models.Count('song__url')).order_by('-count') played_votes = PlayLog.objects.all().filter(created__gte=start).filter(created__lt=end).order_by('-votes') devices = requested.values('address').values('address', count=models.Count('address')) response = {} response['songs_played'] = len(played) response['most_played_song'] = song_utils.displayname( played_count[0]['song__artist'], played_count[0]['song__title']) + ' (' + str(played_count[0]['count']) + ')' response['highest_voted_song'] = played_votes[0].song.displayname() + ' (' + str(played_votes[0].votes) + ')' response['most_active_device'] = devices[0]['address'] + ' (' + str(devices[0]['count']) + ')' requested_by_ip = requested.filter(address=devices[0]['address']) for i in range(6): if i >= len(requested_by_ip): break response['most_active_device'] += '\n' if i == 5: response['most_active_device'] += '...' else: response['most_active_device'] += requested_by_ip[i].song.displayname() binsize = 3600 number_of_bins = math.ceil((end - start).total_seconds() / binsize) request_bins = [0 for _ in range(number_of_bins)] for r in requested: seconds = (r.created - start).total_seconds() index = int(seconds / binsize) request_bins[index] += 1 current_time = start current_index = 0 response['request_activity'] = '' while current_time < end: response['request_activity'] += current_time.strftime('%H:%M') response['request_activity'] += ':\t' + str(request_bins[current_index]) response['request_activity'] += '\n' current_time += timedelta(seconds=binsize) current_index += 1 localtz = tz.gettz(settings.TIME_ZONE) playlist = '' for log in played: localtime = log.created.astimezone(localtz) playlist += '[{:02d}:{:02d}] {}\n'.format(localtime.hour, localtime.minute, log.song.displayname()) response['playlist'] = playlist return JsonResponse(response)
def get_suggestions(self, request: WSGIRequest) -> JsonResponse: """Returns suggestions for a given query. Combines online and offline suggestions.""" terms = request.GET["term"].split() suggest_playlist = request.GET["playlist"] == "true" results: List[Dict[str, Union[str, int]]] = [] if self.musiq.base.settings.has_internet: if self.musiq.base.settings.spotify_enabled: spotify_suggestions = Spotify().get_search_suggestions( " ".join(terms), suggest_playlist) spotify_suggestions = spotify_suggestions[:2] for suggestion, external_url in spotify_suggestions: results.append({ "key": external_url, "value": suggestion, "type": "spotify-online", }) if self.musiq.base.settings.soundcloud_enabled: spotify_suggestions = Soundcloud().get_search_suggestions( " ".join(terms)) soundcloud_suggestions = spotify_suggestions[:2] for suggestion in soundcloud_suggestions: results.append({ "key": -1, "value": suggestion, "type": "soundcloud-online" }) if self.musiq.base.settings.youtube_enabled: youtube_suggestions = Youtube().get_search_suggestions( " ".join(terms)) # limit to the first three online suggestions youtube_suggestions = youtube_suggestions[:2] for suggestion in youtube_suggestions: results.append({ "key": -1, "value": suggestion, "type": "youtube-online" }) # The following query is roughly equivalent to the following SQL code: # SELECT DISTINCT id, title, artist, counter # FROM core_archivedsong s LEFT JOIN core_archivedquery q ON q.song # WHERE forall term in terms: term in q.query or term in s.artist or term in s.title # ORDER BY -counter if suggest_playlist: remaining_playlists = ArchivedPlaylist.objects.prefetch_related( "queries") # exclude radios from suggestions remaining_playlists = remaining_playlists.exclude( list_id__startswith="RD").exclude(list_id__contains="&list=RD") for term in terms: remaining_playlists = remaining_playlists.filter( Q(title__icontains=term) | Q(queries__query__icontains=term)) playlist_suggestions = (remaining_playlists.values( "id", "title", "counter").distinct().order_by("-counter")[:20]) for playlist in playlist_suggestions: archived_playlist = ArchivedPlaylist.objects.get( id=playlist["id"]) result_dict: Dict[str, Union[str, int]] = { "key": playlist["id"], "value": playlist["title"], "counter": playlist["counter"], "type": song_utils.determine_playlist_type(archived_playlist), } results.append(result_dict) else: remaining_songs = ArchivedSong.objects.prefetch_related("queries") for term in terms: remaining_songs = remaining_songs.filter( Q(title__icontains=term) | Q(artist__icontains=term) | Q(queries__query__icontains=term)) song_suggestions = (remaining_songs.values( "id", "title", "url", "artist", "counter").distinct().order_by("-counter")[:20]) for song in song_suggestions: provider = SongProvider.create(self.musiq, external_url=song["url"]) cached = provider.check_cached() # don't suggest local songs if they are not cached (=not at expected location) if not cached and provider.type == "local": continue # don't suggest online songs when we don't have internet if not self.musiq.base.settings.has_internet and not cached: continue # don't suggest youtube songs if it was disabled if (not self.musiq.base.settings.youtube_enabled and provider.type == "youtube"): continue # don't suggest spotify songs if we are not logged in if (not self.musiq.base.settings.spotify_enabled and provider.type == "spotify"): continue # don't suggest soundcloud songs if we are not logged in if (not self.musiq.base.settings.soundcloud_enabled and provider.type == "soundcloud"): continue result_dict = { "key": song["id"], "value": song_utils.displayname(song["artist"], song["title"]), "counter": song["counter"], "type": provider.type, } results.append(result_dict) return JsonResponse(results, safe=False)
def analyse(self, request: WSGIRequest) -> HttpResponse: """Perform an analysis of the database in the given timeframe.""" startdate = request.POST.get("startdate") starttime = request.POST.get("starttime") enddate = request.POST.get("enddate") endtime = request.POST.get("endtime") if not startdate or not starttime or not enddate or not endtime: return HttpResponseBadRequest("All fields are required") start = dateparse.parse_datetime(startdate + "T" + starttime) end = dateparse.parse_datetime(enddate + "T" + endtime) if start is None or end is None: return HttpResponseBadRequest("invalid start-/endtime given") if start >= end: return HttpResponseBadRequest("start has to be before end") start = timezone.make_aware(start) end = timezone.make_aware(end) played = ( PlayLog.objects.all().filter(created__gte=start).filter(created__lt=end) ) requested = ( RequestLog.objects.all().filter(created__gte=start).filter(created__lt=end) ) played_count = ( played.values("song__url", "song__artist", "song__title") .values( "song__url", "song__artist", "song__title", count=models.Count("song__url"), ) .order_by("-count") ) played_votes = ( PlayLog.objects.all() .filter(created__gte=start) .filter(created__lt=end) .order_by("-votes") ) devices = requested.values("address").values( "address", count=models.Count("address") ) response = { "songs_played": len(played), "most_played_song": ( song_utils.displayname( played_count[0]["song__artist"], played_count[0]["song__title"] ) + f" ({played_count[0]['count']})" ), "highest_voted_song": ( played_votes[0].song_displayname() + f" ({played_votes[0].votes})" ), "most_active_device": (devices[0]["address"] + f" ({devices[0]['count']})"), } requested_by_ip = requested.filter(address=devices[0]["address"]) for i in range(6): if i >= len(requested_by_ip): break response["most_active_device"] += "\n" if i == 5: response["most_active_device"] += "..." else: response["most_active_device"] += requested_by_ip[i].item_displayname() binsize = 3600 number_of_bins = math.ceil((end - start).total_seconds() / binsize) request_bins = [0 for _ in range(number_of_bins)] for request_log in requested: seconds = (request_log.created - start).total_seconds() index = int(seconds / binsize) request_bins[index] += 1 current_time = start current_index = 0 response["request_activity"] = "" while current_time < end: response["request_activity"] += current_time.strftime("%H:%M") response["request_activity"] += ":\t" + str(request_bins[current_index]) response["request_activity"] += "\n" current_time += timedelta(seconds=binsize) current_index += 1 localtz = tz.gettz(settings.TIME_ZONE) playlist = "" for play_log in played: localtime = play_log.created.astimezone(localtz) playlist += "[{:02d}:{:02d}] {}\n".format( localtime.hour, localtime.minute, play_log.song_displayname() ) response["playlist"] = playlist return JsonResponse(response)
def analyse(request: WSGIRequest) -> HttpResponse: """Perform an analysis of the database in the given timeframe.""" try: start, end = _parse_datetimes(request) except ValueError as error: return HttpResponseBadRequest(error.args[0]) played = PlayLog.objects.all().filter(created__gte=start).filter(created__lt=end) if not played.exists(): return HttpResponseBadRequest("No songs played in the given time span") request_logs = ( RequestLog.objects.all().filter(created__gte=start).filter(created__lt=end) ) played_count = ( played.values("song__url", "song__artist", "song__title") .annotate(count=models.Count("song__url")) .values("song__url", "song__artist", "song__title", "count") .order_by("-count") ) played_votes = ( PlayLog.objects.all() .filter(created__gte=start) .filter(created__lt=end) .order_by("-votes") ) devices = ( request_logs.values("session_key") .annotate(count=models.Count("session_key")) .values("session_key", "count") .order_by("-count") ) response = { "songsPlayed": len(played), "mostPlayedSong": ( song_utils.displayname( played_count[0]["song__artist"], played_count[0]["song__title"] ) + f" ({played_count[0]['count']})" ), "highestVotedSong": ( played_votes[0].song_displayname() + f" ({played_votes[0].votes})" ), } response["mostActiveDevice"] = _most_active_device( devices[0]["session_key"], devices[0]["count"], request_logs.filter(session_key=devices[0]["session_key"]), ) response["requestActivity"] = _request_activity(start, end, request_logs) localtz = tz.gettz(conf.TIME_ZONE) playlist = "" for play_log in played: localtime = play_log.created.astimezone(localtz) playlist += f"[{localtime.hour:02d}:{localtime.minute:02d}] {play_log.song_displayname()}\n" response["playlist"] = playlist return JsonResponse(response)
def displayname(self) -> str: """Formats the song using the utility method.""" return song_utils.displayname(self.artist, self.title)
def get_suggestions(self, request: WSGIRequest) -> JsonResponse: """Returns suggestions for a given query. Combines online and offline suggestions.""" query = request.GET["term"] suggest_playlist = request.GET["playlist"] == "true" results: List[Dict[str, Union[str, int]]] = [] if (self.musiq.base.settings.online_suggestions and self.musiq.base.settings.has_internet): number_of_suggestions = 2 if [ self.musiq.base.settings.spotify_enabled, self.musiq.base.settings.soundcloud_enabled, self.musiq.base.settings.youtube_enabled, ].count(True) > 1: # If there is more than one active service, # only offer one online suggestion per service number_of_suggestions = 1 if self.musiq.base.settings.spotify_enabled: spotify_suggestions = Spotify().get_search_suggestions( query, suggest_playlist) spotify_suggestions = spotify_suggestions[: number_of_suggestions] for suggestion, external_url in spotify_suggestions: results.append({ "key": external_url, "value": suggestion, "type": "spotify-online", }) if self.musiq.base.settings.soundcloud_enabled: soundcloud_suggestions = Soundcloud().get_search_suggestions( query) soundcloud_suggestions = soundcloud_suggestions[: number_of_suggestions] for suggestion in soundcloud_suggestions: results.append({ "key": -1, "value": suggestion, "type": "soundcloud-online" }) if self.musiq.base.settings.youtube_enabled: youtube_suggestions = Youtube().get_search_suggestions(query) # limit to the first three online suggestions youtube_suggestions = youtube_suggestions[: number_of_suggestions] for suggestion in youtube_suggestions: results.append({ "key": -1, "value": suggestion, "type": "youtube-online" }) if suggest_playlist: search_results = watson.search(query, models=(ArchivedPlaylist, ))[:20] for playlist in search_results: playlist_info = playlist.meta archived_playlist = ArchivedPlaylist.objects.get( id=playlist_info["id"]) result_dict: Dict[str, Union[str, int]] = { "key": playlist_info["id"], "value": playlist_info["title"], "counter": playlist_info["counter"], "type": song_utils.determine_playlist_type(archived_playlist), } results.append(result_dict) else: search_results = watson.search(query, models=(ArchivedSong, ))[:20] for search_result in search_results: song_info = search_result.meta provider = SongProvider.create(self.musiq, external_url=song_info["url"]) cached = provider.check_cached() # don't suggest local songs if they are not cached (=not at expected location) if not cached and provider.type == "local": continue # don't suggest online songs when we don't have internet if not self.musiq.base.settings.has_internet and not cached: continue # don't suggest youtube songs if it was disabled if (not self.musiq.base.settings.youtube_enabled and provider.type == "youtube"): continue # don't suggest spotify songs if we are not logged in if (not self.musiq.base.settings.spotify_enabled and provider.type == "spotify"): continue # don't suggest soundcloud songs if we are not logged in if (not self.musiq.base.settings.soundcloud_enabled and provider.type == "soundcloud"): continue result_dict = { "key": song_info["id"], "value": song_utils.displayname(song_info["artist"], song_info["title"]), "counter": song_info["counter"], "type": provider.type, } results.append(result_dict) return JsonResponse(results, safe=False)
def get_suggestions(self, request: WSGIRequest) -> JsonResponse: """Returns suggestions for a given query. Combines online and offline suggestions.""" query = request.GET["term"] suggest_playlist = request.GET["playlist"] == "true" results = self._online_suggestions(query, suggest_playlist) basic_settings = self.musiq.base.settings.basic if suggest_playlist: search_results = watson.search(query, models=( ArchivedPlaylist, ))[:basic_settings.number_of_suggestions] for playlist in search_results: playlist_info = playlist.meta archived_playlist = ArchivedPlaylist.objects.get( id=playlist_info["id"]) result_dict: Dict[str, Union[str, int]] = { "key": playlist_info["id"], "value": playlist_info["title"], "counter": playlist.object.counter, "type": song_utils.determine_playlist_type(archived_playlist), } results.append(result_dict) else: search_results = watson.search( query, models=(ArchivedSong, ))[:basic_settings.number_of_suggestions] for search_result in search_results: song_info = search_result.meta provider = SongProvider.create(self.musiq, external_url=song_info["url"]) cached = provider.check_cached() # don't suggest local songs if they are not cached (=not at expected location) if not cached and provider.type == "local": continue # don't suggest online songs when we don't have internet if not self.musiq.base.settings.basic.has_internet and not cached: continue # don't suggest youtube songs if it was disabled if (not self.musiq.base.settings.platforms.youtube_enabled and provider.type == "youtube"): continue # don't suggest spotify songs if we are not logged in if (not self.musiq.base.settings.platforms.spotify_enabled and provider.type == "spotify"): continue # don't suggest soundcloud songs if we are not logged in if (not self.musiq.base.settings.platforms.soundcloud_enabled and provider.type == "soundcloud"): continue result_dict = { "key": song_info["id"], "value": song_utils.displayname(song_info["artist"], song_info["title"]), "counter": search_result.object.counter, "type": provider.type, } results.append(result_dict) return JsonResponse(results, safe=False)
def displayname(self): return song_utils.displayname(self.artist, self.title)
def get_suggestions(self, request): terms = request.GET['term'].split() suggest_playlist = request.GET['playlist'] == 'true' results = [] if suggest_playlist: remaining_playlists = ArchivedPlaylist.objects.prefetch_related( 'queries') # exclude radios from suggestions remaining_playlists = remaining_playlists.exclude( list_id__startswith='RD').exclude(list_id__contains='&list=RD') for term in terms: remaining_playlists = remaining_playlists.filter( Q(title__icontains=term) | Q(queries__query__icontains=term)) remaining_playlists = remaining_playlists \ .values('id', 'title', 'counter') \ .distinct() \ .order_by('-counter') \ [:20] for playlist in remaining_playlists: cached = False archived_playlist = ArchivedPlaylist.objects.get( id=playlist['id']) result_dict = { 'key': playlist['id'], 'value': playlist['title'], 'counter': playlist['counter'], 'type': song_utils.determine_playlist_type(archived_playlist), } results.append(result_dict) else: remaining_songs = ArchivedSong.objects.prefetch_related('queries') for term in terms: remaining_songs = remaining_songs.filter( Q(title__icontains=term) | Q(artist__icontains=term) | Q(queries__query__icontains=term)) remaining_songs = remaining_songs \ .values('id', 'title', 'url', 'artist', 'counter') \ .distinct() \ .order_by('-counter') \ [:20] for song in remaining_songs: provider = SongProvider.create(self.musiq, external_url=song['url']) cached = provider.check_cached() # don't suggest local songs if they are not cached (=not at expected location) if not cached and provider.type == 'local': continue # don't suggest online songs when we don't have internet if not self.musiq.base.settings.has_internet and not cached: continue # don't suggest spotify songs if we are not logged in if not self.musiq.base.settings.spotify_enabled and provider.type == 'spotify': continue result_dict = { 'key': song['id'], 'value': song_utils.displayname(song['artist'], song['title']), 'counter': song['counter'], 'type': provider.type } results.append(result_dict) return HttpResponse(json.dumps(results)) """ query for the suggestions
def get_suggestions(self, request): terms = request.GET['term'].split() suggest_playlist = request.GET['playlist'] == 'true' results = [] if suggest_playlist: remaining_playlists = ArchivedPlaylist.objects.prefetch_related( 'queries') # exclude radios from suggestions remaining_playlists = remaining_playlists.exclude( list_id__startswith='RD').exclude(list_id__contains='&list=RD') for term in terms: remaining_playlists = remaining_playlists.filter( Q(title__icontains=term) | Q(queries__query__icontains=term)) remaining_playlists = remaining_playlists \ .values('id', 'title', 'counter') \ .distinct() \ .order_by('-counter') \ [:20] for playlist in remaining_playlists: cached = False result_dict = { 'key': playlist['id'], 'value': playlist['title'], 'counter': playlist['counter'], 'type': 'cached' if cached else 'online', } results.append(result_dict) else: remaining_songs = ArchivedSong.objects.prefetch_related('queries') for term in terms: remaining_songs = remaining_songs.filter( Q(title__icontains=term) | Q(artist__icontains=term) | Q(queries__query__icontains=term)) remaining_songs = remaining_songs \ .values('id', 'title', 'url', 'artist', 'counter') \ .distinct() \ .order_by('-counter') \ [:20] for song in remaining_songs: if song_utils.path_from_url(song['url']) is not None: cached = True else: cached = False # don't suggest online songs when we don't have internet if not self.musiq.base.settings.has_internet: if not cached: continue result_dict = { 'key': song['id'], 'value': song_utils.displayname(song['artist'], song['title']), 'counter': song['counter'], 'type': 'cached' if cached else 'online', } results.append(result_dict) return HttpResponse(json.dumps(results))
def _offline_song_suggestions(query: str) -> List[SuggestionResult]: results: List[SuggestionResult] = [] terms = query.split() song_results: Iterable[Mapping[str, Any]] if settings.DEBUG: # Testing the whole table whether it contains any term is quite costly. # Used for sqlite3 which does not have a similarity function. matching_songs = ArchivedSong.objects.prefetch_related("queries") for term in terms: matching_songs = matching_songs.filter( Q(title__icontains=term) | Q(artist__icontains=term) | Q(queries__query__icontains=term)) song_results = ( matching_songs.order_by("-counter") # annotate with same values as in the postgres case to have a consistent interface .annotate( u_id=F("id"), u_url=F("url"), u_artist=F("artist"), u_title=F("title"), u_duration=F("duration"), u_counter=F("counter"), u_cached=F("cached"), ).values(*u_values_list).distinct() [:storage.get("number_of_suggestions")]) # Perhaps this could be combined with the similarity search # to improve usability with the right weighting. # matching_songs = matching_songs.annotate( # artist_similarity=Coalesce(TrigramWordSimilarity(query, "artist"), 0), # title_similarity=Coalesce(TrigramWordSimilarity(query, "title"), 0), # query_similarity=Coalesce(TrigramWordSimilarity(query, "queries__query"), 0), # max_similarity=Greatest( # "artist_similarity", "title_similarity", "query_similarity" # ), # ) # To combine, use union instead of | (or) in order to access the annotated values # similar_songs = union(matching_songs) else: song_results = _postgres_song_results(query) has_internet = redis.get("has_internet") for song in song_results: if song_utils.is_forbidden( song["u_artist"]) or song_utils.is_forbidden(song["u_title"]): continue platform = song_utils.determine_url_type(song["u_url"]) # don't suggest online songs when we don't have internet if not has_internet and not song["u_cached"]: continue if platform == "local": # don't suggest local songs if they are not cached (=not at expected location) if not song["u_cached"]: continue else: # don't suggest songs if the respective platform is disabled assert platform in ["youtube", "spotify", "soundcloud", "jamendo"] if not storage.get(cast(PlatformEnabled, f"{platform}_enabled")): continue result_dict: SuggestionResult = { "key": song["u_id"], "value": song_utils.displayname(song["u_artist"], song["u_title"]), "counter": song["u_counter"], "type": platform, "durationFormatted": song_utils.format_seconds(song["u_duration"]), } results.append(result_dict) # mark suggestions whose displayname is identical seen_values: Dict[str, int] = {} for index, result in enumerate(results): if result["value"] in seen_values: result["confusable"] = True results[seen_values[result["value"]]]["confusable"] = True seen_values[result["value"]] = index return results