Esempio n. 1
0
    def handle(self, *args, **options):
        from core.models import ArchivedSong
        from core.musiq.song_provider import SongProvider

        for song in ArchivedSong.objects.all():
            try:
                provider = SongProvider.create(external_url=song.url)
            except NotImplementedError:
                # For this song a provider is necessary that is not available
                # e.g. the song was played before, but the provider was disabled
                continue
            cached = provider.check_cached()
            if cached:
                # sync the metadata in the database with the file system
                # _get_path is defined for localdrive and youtube,
                # the only two providers that may be cached
                from core.musiq.local import LocalSongProvider
                from core.musiq.youtube import YoutubeSongProvider

                assert isinstance(provider,
                                  (YoutubeSongProvider, LocalSongProvider))
                metadata = song_utils.get_metadata(provider.get_path())
                song.artist = metadata["artist"]
                song.title = metadata["title"]
                song.duration = metadata["duration"]
                song.cached = True
            else:
                # keep old data but store that the song is not cached
                song.cached = False
            song.save()
Esempio n. 2
0
def request_radio(request: WSGIRequest) -> HttpResponse:
    """Endpoint to request radio for the current song."""
    try:
        current_song = CurrentSong.objects.get()
    except CurrentSong.DoesNotExist:
        return HttpResponseBadRequest("Need a song to play the radio")
    provider = SongProvider.create(external_url=current_song.external_url)
    return provider.request_radio(request.session.session_key)
Esempio n. 3
0
    def __init__(self, base):
        self.base = base

        self.logger = logging.getLogger('raveberry')

        self.song_provider = SongProvider(self)

        self.queue = QueuedSong.objects
        self.placeholders = []

        self.player = Player(self)
        self.player.start()
Esempio n. 4
0
    def enqueue(self) -> None:
        for index, external_url in enumerate(self.urls):
            if index == self.musiq.base.settings.basic.max_playlist_items:
                break
            # request every url in the playlist as their own url
            song_provider = SongProvider.create(self.musiq, external_url=external_url)

            try:
                song_provider.request("", archive=False, manually_requested=False)
            except ProviderError:
                continue

            if settings.DEBUG:
                # the sqlite database has problems if songs are pushed very fast
                # while a new song is taken from the queue. Add a delay to mitigate.
                time.sleep(1)
Esempio n. 5
0
    def request_radio(self, request: WSGIRequest) -> HttpResponse:
        """Endpoint to request radio for the current song."""
        # only get ip on user requests
        if self.base.settings.basic.logging_enabled:
            request_ip, _ = ipware.get_client_ip(request)
            if request_ip is None:
                request_ip = ""
        else:
            request_ip = ""

        try:
            current_song = CurrentSong.objects.get()
        except CurrentSong.DoesNotExist:
            return HttpResponseBadRequest("Need a song to play the radio")
        provider = SongProvider.create(self,
                                       external_url=current_song.external_url)
        return provider.request_radio(request_ip)
Esempio n. 6
0
def get_providers(
    query: str,
    key: Optional[int] = None,
    playlist: bool = False,
    preferred_platform: Optional[str] = None,
) -> List[MusicProvider]:
    """Returns a list of all available providers for the given query, ordered by priority.
    If a preferred platform is given, that provider will be first."""

    if key is not None:
        # an archived entry was requested.
        # The key determines the Provider
        provider: MusicProvider
        if playlist:
            provider = PlaylistProvider.create(query, key)
        else:
            provider = SongProvider.create(query, key)
        if provider is None:
            raise ProviderError("No provider found for requested key")
        return [provider]

    providers: List[MusicProvider] = []
    for platform in enabled_platforms_by_priority():
        module = importlib.import_module(f"core.musiq.{platform}")
        if playlist:
            provider_class = getattr(module,
                                     f"{platform.title()}PlaylistProvider")
        else:
            provider_class = getattr(module, f"{platform.title()}SongProvider")
        try:
            provider = provider_class(query, key)
            if platform == preferred_platform:
                providers.insert(0, provider)
            else:
                providers.append(provider)
        except WrongUrlError:
            pass

    if not providers:
        raise ProviderError("No backend configured to handle your request.")

    return providers
Esempio n. 7
0
    def enqueue(self) -> None:
        for index, external_url in enumerate(self.urls):
            if index == storage.get("max_playlist_items"):
                break
            # request every url in the playlist as their own url
            try:
                song_provider = SongProvider.create(external_url=external_url)
                song_provider.request("",
                                      archive=False,
                                      manually_requested=False)
            except (ProviderError, NotImplementedError) as error:
                logging.warning(
                    "Error while enqueuing url %s to playlist %s: %s",
                    external_url,
                    self.title,
                    self.id,
                )
                logging.exception(error)
                continue

            if settings.DEBUG:
                # the sqlite database has problems if songs are pushed very fast
                # while a new song is taken from the queue. Add a delay to mitigate.
                time.sleep(1)
Esempio n. 8
0
    def handle_autoplay(self, url: Optional[str] = None) -> None:
        """Checks whether to add a song by autoplay and does so if necessary.
        :param url: if given, this url is used to find the next autoplayed song.
        Otherwise, the current song is used."""
        if self.musiq.controller.autoplay and models.QueuedSong.objects.count(
        ) == 0:
            if url is None:
                # if no url was specified, use the one of the current song
                try:
                    current_song = models.CurrentSong.objects.get()
                    url = current_song.external_url
                except (
                        models.CurrentSong.DoesNotExist,
                        models.CurrentSong.MultipleObjectsReturned,
                ):
                    return

            provider = SongProvider.create(self.musiq, external_url=url)
            try:
                suggestion = provider.get_suggestion()
                # The player loop is not restarted after error automatically.
                # As this function can raise several exceptions (it might do networking)
                # we catch every exception to make sure the loop keeps running
            except Exception as e:  # pylint: disable=broad-except
                logging.exception("error during suggestions for %s: %s", url,
                                  e)
            else:
                self.musiq.do_request_music(
                    "",
                    suggestion,
                    None,
                    False,
                    provider.type,
                    archive=False,
                    manually_requested=False,
                )
Esempio n. 9
0
    def _loop(self) -> None:
        """The main loop of the player.
        Takes a song from the queue and plays it until it is finished."""
        while True:

            catch_up = None
            if models.CurrentSong.objects.exists():
                # recover interrupted song from database
                current_song = models.CurrentSong.objects.get()

                # continue with the current song (approximately) where we last left
                song_provider = SongProvider.create(
                    self.musiq, external_url=current_song.external_url)
                duration = int(song_provider.get_metadata()["duration"])
                catch_up = round(
                    (timezone.now() - current_song.created).total_seconds() *
                    1000)
                if catch_up > duration * 1000:
                    catch_up = -1
            else:
                self.queue_semaphore.acquire()
                if not self.running:
                    break

                if self.backup_playing.is_set():
                    # stop backup stream
                    self.backup_playing.clear()
                    with self.mopidy_command(important=True) as allowed:
                        if allowed:
                            self.player.playback.next()

                # select the next song depending on settings
                song: Optional[models.QueuedSong]
                if self.musiq.base.settings.basic.voting_system:
                    with transaction.atomic():
                        song = self.queue.confirmed().order_by(
                            "-votes", "index")[0]
                        song_id = song.id
                        self.queue.remove(song.id)
                elif self.musiq.controller.shuffle:
                    confirmed = self.queue.confirmed()
                    index = random.randint(0, confirmed.count() - 1)
                    song_id = confirmed[index].id
                    song = self.queue.remove(song_id)
                else:
                    # move the first song in the queue into the current song
                    song_id, song = self.queue.dequeue()

                if song is None:
                    # either the semaphore didn't match up with the actual count
                    # of songs in the queue or a race condition occured
                    logging.warning("dequeued on empty list")
                    continue

                current_song = models.CurrentSong.objects.create(
                    queue_key=song_id,
                    manually_requested=song.manually_requested,
                    votes=song.votes,
                    internal_url=song.internal_url,
                    external_url=song.external_url,
                    artist=song.artist,
                    title=song.title,
                    duration=song.duration,
                )

                self.handle_autoplay()

                try:
                    archived_song = models.ArchivedSong.objects.get(
                        url=current_song.external_url)
                    votes: Optional[int]
                    if self.musiq.base.settings.basic.voting_system:
                        votes = current_song.votes
                    else:
                        votes = None
                    if self.musiq.base.settings.basic.logging_enabled:
                        models.PlayLog.objects.create(
                            song=archived_song,
                            manually_requested=current_song.manually_requested,
                            votes=votes,
                        )
                except (
                        models.ArchivedSong.DoesNotExist,
                        models.ArchivedSong.MultipleObjectsReturned,
                ):
                    pass

            self.musiq.update_state()

            playing = Event()

            @self.player.on_event("playback_state_changed")
            def _on_playback_state_changed(_event):
                playing.set()

            with self.mopidy_command(important=True):
                # after a restart consume may be set to False again, so make sure it is on
                self.player.tracklist.clear()
                self.player.tracklist.set_consume(True)
                self.player.tracklist.add(uris=[current_song.internal_url])
                self.player.playback.play()
                # mopidy can only seek when the song is playing
                playing.wait(timeout=1)
                if catch_up is not None and catch_up >= 0:
                    self.player.playback.seek(catch_up)

            self.musiq.update_state()

            if catch_up is None or catch_up >= 0:
                if not self._wait_until_song_end():
                    # there was a ConnectionError during waiting for the song to end
                    # we do not delete the current song but recover its state by restarting the loop
                    continue

            current_song.delete()

            if self.musiq.controller.repeat:
                song_provider = SongProvider.create(
                    self.musiq, external_url=current_song.external_url)
                self.queue.enqueue(song_provider.get_metadata(), False)
                self.queue_semaphore.release()

            if (self.musiq.base.user_manager.partymode_enabled()
                    and random.random() <
                    self.musiq.base.settings.basic.alarm_probability):
                self.alarm_playing.set()
                self.musiq.base.lights.alarm_started()

                self.musiq.update_state()

                with self.mopidy_command(important=True):
                    self.player.tracklist.add(uris=[
                        "file://" + os.path.join(settings.BASE_DIR,
                                                 "config/sounds/alarm.m4a")
                    ])
                    self.player.playback.play()
                playing.clear()
                playing.wait(timeout=1)
                self._wait_until_song_end()

                self.musiq.base.lights.alarm_stopped()
                self.musiq.update_state()
                self.alarm_playing.clear()

            if not self.queue.exists(
            ) and self.musiq.base.settings.sound.backup_stream:
                self.backup_playing.set()
                # play backup stream
                self.player.tracklist.add(
                    uris=[self.musiq.base.settings.sound.backup_stream])
                self.player.playback.play()

            self.musiq.update_state()
Esempio n. 10
0
    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"

        if self.musiq.base.settings.basic.new_music_only and not suggest_playlist:
            return JsonResponse([], safe=False)

        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

                if song_utils.is_forbidden(
                        self.musiq,
                        song_info["artist"]) or song_utils.is_forbidden(
                            self.musiq, song_info["title"]):
                    continue

                try:
                    provider = SongProvider.create(
                        self.musiq, external_url=song_info["url"])
                except NotImplementedError:
                    # For this song a provider is necessary that is not available
                    # e.g. the song was played before, but the provider was disabled
                    continue
                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)