def extract_id(self) -> Optional[str]: if self.key is not None: try: archived_song = ArchivedSong.objects.get(id=self.key) return self.__class__.get_id_from_external_url(archived_song.url) except ArchivedSong.DoesNotExist: return None if self.query is not None: url_type = song_utils.determine_url_type(self.query) if url_type == "youtube": from core.musiq.youtube import YoutubeSongProvider return YoutubeSongProvider.get_id_from_external_url(self.query) if url_type == "spotify": from core.musiq.spotify import SpotifySongProvider return SpotifySongProvider.get_id_from_external_url(self.query) if url_type == "soundcloud": from core.musiq.soundcloud import SoundcloudSongProvider return SoundcloudSongProvider.get_id_from_external_url(self.query) # interpret the query as an external url and try to look it up in the database try: archived_song = ArchivedSong.objects.get(url=self.query) return self.__class__.get_id_from_external_url(archived_song.url) except ArchivedSong.DoesNotExist: return None logging.error("Can not extract id because neither key nor query are known") return None
def __init__(self, musiq: "Musiq", query: Optional[str], key: Optional[int]) -> None: super().__init__(musiq, query, key) self.ok_message = "song queued" if query: url_type = song_utils.determine_url_type(query) if url_type != self.type and url_type != "unknown": raise WrongUrlError( f"Tried to create a {self.type} provider with: {query}")
def __init__(self, query: Optional[str], key: Optional[int]) -> None: super().__init__(query, key) self.id = self.extract_id() self.ok_message = "song queued" self.queued_song: Optional[QueuedSong] = None self.metadata: "Metadata" = {} if query: url_type = song_utils.determine_url_type(query) if url_type not in (self.type, "unknown"): raise WrongUrlError( f"Tried to create a {self.type} provider with: {query}")
def create( query: Optional[str] = None, key: Optional[int] = None, external_url: Optional[str] = None, ) -> "SongProvider": """Factory method to create a song provider. Either (query and key) or external url need to be specified. Detects the type of provider needed and returns one of corresponding type.""" if key is not None: if query is None: logging.error( "archived song requested but no query given (key %s)", key) raise ValueError() try: archived_song = ArchivedSong.objects.get(id=key) except ArchivedSong.DoesNotExist as error: logging.error("archived song requested for nonexistent key %s", key) raise ValueError() from error external_url = archived_song.url if external_url is None: raise ValueError( "external_url was provided and could not be inferred from remaining attributes." ) provider_class: Optional[Type[SongProvider]] = None url_type = song_utils.determine_url_type(external_url) if url_type == "local": from core.musiq.local import LocalSongProvider provider_class = LocalSongProvider elif storage.get("youtube_enabled") and url_type == "youtube": from core.musiq.youtube import YoutubeSongProvider provider_class = YoutubeSongProvider elif storage.get("spotify_enabled") and url_type == "spotify": from core.musiq.spotify import SpotifySongProvider provider_class = SpotifySongProvider elif storage.get("soundcloud_enabled") and url_type == "soundcloud": from core.musiq.soundcloud import SoundcloudSongProvider provider_class = SoundcloudSongProvider elif storage.get("jamendo_enabled") and url_type == "jamendo": from core.musiq.jamendo import JamendoSongProvider provider_class = JamendoSongProvider if not provider_class: raise NotImplementedError( f"No provider for given song: {external_url}") if not query and external_url: query = external_url provider = provider_class(query, key) return provider
def extract_id(self) -> Optional[str]: """Tries to extract the id from the given query. Returns the id if possible, otherwise None""" if self.key is not None: try: archived_song = ArchivedSong.objects.get(id=self.key) return self.__class__.get_id_from_external_url( archived_song.url) except ArchivedSong.DoesNotExist: return None if self.query is not None: url_type = song_utils.determine_url_type(self.query) provider_class: Optional[Type[SongProvider]] = None if url_type == "local": from core.musiq.local import LocalSongProvider provider_class = LocalSongProvider if storage.get("youtube_enabled") and url_type == "youtube": from core.musiq.youtube import YoutubeSongProvider provider_class = YoutubeSongProvider if storage.get("spotify_enabled") and url_type == "spotify": from core.musiq.spotify import SpotifySongProvider provider_class = SpotifySongProvider if storage.get("soundcloud_enabled") and url_type == "soundcloud": from core.musiq.soundcloud import SoundcloudSongProvider provider_class = SoundcloudSongProvider if storage.get("jamendo_enabled") and url_type == "jamendo": from core.musiq.jamendo import JamendoSongProvider provider_class = JamendoSongProvider if provider_class is not None: return provider_class.get_id_from_external_url(self.query) try: archived_song = ArchivedSong.objects.get(url=self.query) return self.__class__.get_id_from_external_url( archived_song.url) except ArchivedSong.DoesNotExist: return None logging.error( "Can not extract id because neither key nor query are known") return None
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