Ejemplo n.º 1
0
    def __init__(
            self, session, canonical_username=None, tracks=None, message='',
            callback=None, sp_inbox=None, add_ref=True):

        assert canonical_username and tracks or sp_inbox, \
            'canonical_username and tracks, or sp_inbox, is required'

        self._session = session
        self.loaded_event = threading.Event()

        if sp_inbox is None:
            canonical_username = utils.to_char(canonical_username)

            if isinstance(tracks, spotify.Track):
                tracks = [tracks]

            message = utils.to_char(message)

            handle = ffi.new_handle((self._session, self, callback))
            self._session._callback_handles.add(handle)

            sp_inbox = lib.sp_inbox_post_tracks(
                self._session._sp_session, canonical_username,
                [t._sp_track for t in tracks], len(tracks),
                message, _inboxpost_complete_callback, handle)
            add_ref = True

            if sp_inbox == ffi.NULL:
                raise spotify.Error('Inbox post request failed to initialize')

        if add_ref:
            lib.sp_inbox_add_ref(sp_inbox)
        self._sp_inbox = ffi.gc(sp_inbox, lib.sp_inbox_release)
Ejemplo n.º 2
0
    def login(self, username, password=None, remember_me=False, blob=None):
        """Authenticate to Spotify's servers.

        You can login with one of two combinations:

        - ``username`` and ``password``
        - ``username`` and ``blob``

        To get the ``blob`` string, you must once log in with ``username`` and
        ``password``. You'll then get the ``blob`` string passed to the
        :attr:`~SessionCallbacks.credentials_blob_updated` callback.

        If you set ``remember_me`` to :class:`True`, you can later login to the
        same account without providing any ``username`` or credentials by
        calling :meth:`relogin`.
        """

        username = utils.to_char(username)

        if password is not None:
            password = utils.to_char(password)
            blob = ffi.NULL
        elif blob is not None:
            password = ffi.NULL
            blob = utils.to_char(blob)
        else:
            raise AttributeError('password or blob is required to login')

        spotify.Error.maybe_raise(lib.sp_session_login(
            self._sp_session, username, password, bool(remember_me), blob))
Ejemplo n.º 3
0
    def login(self, username, password=None, remember_me=False, blob=None):
        """Authenticate to Spotify's servers.

        You can login with one of two combinations:

        - ``username`` and ``password``
        - ``username`` and ``blob``

        To get the ``blob`` string, you must once log in with ``username`` and
        ``password``. You'll then get the ``blob`` string passed to the
        :attr:`~SessionCallbacks.credentials_blob_updated` callback.

        If you set ``remember_me`` to :class:`True`, you can later login to the
        same account without providing any ``username`` or credentials by
        calling :meth:`relogin`.
        """

        username = utils.to_char(username)

        if password is not None:
            password = utils.to_char(password)
            blob = ffi.NULL
        elif blob is not None:
            password = ffi.NULL
            blob = utils.to_char(blob)
        else:
            raise AttributeError('password or blob is required to login')

        spotify.Error.maybe_raise(
            lib.sp_session_login(self._sp_session, username, password,
                                 bool(remember_me), blob))
Ejemplo n.º 4
0
    def get_local_track(self,
                        artist=None,
                        title=None,
                        album=None,
                        length=None):
        """
        Get :class:`Track` for a local track.

        Spotify's official clients supports adding your local music files to
        Spotify so they can be played in the Spotify client. These are not
        synced with Spotify's servers or between your devices and there is not
        trace of them in your Spotify user account. The exception is when you
        add one of these local tracks to a playlist or mark them as starred.
        This creates a "local track" which pyspotify also will be able to
        observe.

        "Local tracks" can be recognized in several ways:

        - The track's URI will be of the form
          ``spotify:local:ARTIST:ALBUM:TITLE:LENGTH_IN_SECONDS``. Any of the
          parts in all caps can be left out if there is no information
          available. That is, ``spotify:local::::`` is a valid local track URI.

        - :attr:`Link.type` will be :class:`LinkType.LOCALTRACK` for the
          track's link.

        - :attr:`Track.is_local` will be :class:`True` for the track.

        This method can be used to create local tracks that can be starred or
        added to playlists.

        ``artist`` may be an artist name. ``title`` may be a track name.
        ``album`` may be an album name. ``length`` may be a track length in
        milliseconds.

        Note that when creating a local track you provide the length in
        milliseconds, while the local track URI contains the length in seconds.
        """

        if artist is None:
            artist = ''
        if title is None:
            title = ''
        if album is None:
            album = ''
        if length is None:
            length = -1

        artist = utils.to_char(artist)
        title = utils.to_char(title)
        album = utils.to_char(album)
        sp_track = lib.sp_localtrack_create(artist, title, album, length)

        return spotify.Track(self, sp_track=sp_track, add_ref=False)
Ejemplo n.º 5
0
    def set_social_credentials(self, social_provider, username, password):
        """Set the user's credentials with a social provider.

        Currently this is only relevant for Last.fm. Call
        :meth:`set_scrobbling` to force an authentication attempt with the
        provider. If authentication fails a
        :attr:`~SessionEvent.SCROBBLE_ERROR` event will be emitted on the
        :class:`Session` object.
        """
        spotify.Error.maybe_raise(lib.sp_session_set_social_credentials(
            self._session._sp_session, social_provider,
            utils.to_char(username), utils.to_char(password)))
Ejemplo n.º 6
0
    def get_local_track(
            self, artist=None, title=None, album=None, length=None):
        """
        Get :class:`Track` for a local track.

        Spotify's official clients supports adding your local music files to
        Spotify so they can be played in the Spotify client. These are not
        synced with Spotify's servers or between your devices and there is not
        trace of them in your Spotify user account. The exception is when you
        add one of these local tracks to a playlist or mark them as starred.
        This creates a "local track" which pyspotify also will be able to
        observe.

        "Local tracks" can be recognized in several ways:

        - The track's URI will be of the form
          ``spotify:local:ARTIST:ALBUM:TITLE:LENGTH_IN_SECONDS``. Any of the
          parts in all caps can be left out if there is no information
          available. That is, ``spotify:local::::`` is a valid local track URI.

        - :attr:`Link.type` will be :class:`LinkType.LOCALTRACK` for the
          track's link.

        - :attr:`Track.is_local` will be :class:`True` for the track.

        This method can be used to create local tracks that can be starred or
        added to playlists.

        ``artist`` may be an artist name. ``title`` may be a track name.
        ``album`` may be an album name. ``length`` may be a track length in
        milliseconds.

        Note that when creating a local track you provide the length in
        milliseconds, while the local track URI contains the length in seconds.
        """

        if artist is None:
            artist = ''
        if title is None:
            title = ''
        if album is None:
            album = ''
        if length is None:
            length = -1

        artist = utils.to_char(artist)
        title = utils.to_char(title)
        album = utils.to_char(album)
        sp_track = lib.sp_localtrack_create(artist, title, album, length)

        return spotify.Track(self, sp_track=sp_track, add_ref=False)
Ejemplo n.º 7
0
    def set_social_credentials(self, social_provider, username, password):
        """Set the user's credentials with a social provider.

        Currently this is only relevant for Last.fm. Call
        :meth:`set_scrobbling` to force an authentication attempt with the
        provider. If authentication fails a
        :attr:`~SessionEvent.SCROBBLE_ERROR` event will be emitted on the
        :class:`Session` object.
        """
        spotify.Error.maybe_raise(
            lib.sp_session_set_social_credentials(self._session._sp_session,
                                                  social_provider,
                                                  utils.to_char(username),
                                                  utils.to_char(password)))
Ejemplo n.º 8
0
 def cache_location(self, value):
     # XXX: libspotify segfaults if cache_location is set to NULL, but
     # doesn't seem to care if other strings in sp_session_config is NULL.
     if value is None:
         value = ''
     self._cache_location = utils.to_char(value)
     self._sp_session_config.cache_location = self._cache_location
Ejemplo n.º 9
0
    def __init__(
        self,
        session,
        query='',
        callback=None,
        track_offset=0,
        track_count=20,
        album_offset=0,
        album_count=20,
        artist_offset=0,
        artist_count=20,
        playlist_offset=0,
        playlist_count=20,
        search_type=None,
        sp_search=None,
        add_ref=True,
    ):

        assert query or sp_search, 'query or sp_search is required'

        self._session = session
        self.callback = callback
        self.track_offset = track_offset
        self.track_count = track_count
        self.album_offset = album_offset
        self.album_count = album_count
        self.artist_offset = artist_offset
        self.artist_count = artist_count
        self.playlist_offset = playlist_offset
        self.playlist_count = playlist_count
        if search_type is None:
            search_type = SearchType.STANDARD
        self.search_type = search_type

        self.loaded_event = threading.Event()

        if sp_search is None:
            handle = ffi.new_handle((self._session, self, callback))
            self._session._callback_handles.add(handle)

            sp_search = lib.sp_search_create(
                self._session._sp_session,
                utils.to_char(query),
                track_offset,
                track_count,
                album_offset,
                album_count,
                artist_offset,
                artist_count,
                playlist_offset,
                playlist_count,
                int(search_type),
                _search_complete_callback,
                handle,
            )
            add_ref = False

        if add_ref:
            lib.sp_search_add_ref(sp_search)
        self._sp_search = ffi.gc(sp_search, lib.sp_search_release)
Ejemplo n.º 10
0
    def __init__(
        self,
        session,
        canonical_username=None,
        tracks=None,
        message='',
        callback=None,
        sp_inbox=None,
        add_ref=True,
    ):

        assert (
            canonical_username and tracks or sp_inbox
        ), 'canonical_username and tracks, or sp_inbox, is required'

        self._session = session
        self.loaded_event = threading.Event()

        if sp_inbox is None:
            canonical_username = utils.to_char(canonical_username)

            if isinstance(tracks, spotify.Track):
                tracks = [tracks]

            message = utils.to_char(message)

            handle = ffi.new_handle((self._session, self, callback))
            self._session._callback_handles.add(handle)

            sp_inbox = lib.sp_inbox_post_tracks(
                self._session._sp_session,
                canonical_username,
                [t._sp_track for t in tracks],
                len(tracks),
                message,
                _inboxpost_complete_callback,
                handle,
            )
            add_ref = True

            if sp_inbox == ffi.NULL:
                raise spotify.Error('Inbox post request failed to initialize')

        if add_ref:
            lib.sp_inbox_add_ref(sp_inbox)
        self._sp_inbox = ffi.gc(sp_inbox, lib.sp_inbox_release)
Ejemplo n.º 11
0
    def __init__(self,
                 canonical_username=None,
                 tracks=None,
                 message='',
                 callback=None,
                 sp_inbox=None,
                 add_ref=True):

        assert canonical_username and tracks or sp_inbox, \
            'canonical_username and tracks, or sp_inbox, is required'

        self.complete_event = threading.Event()
        self._callback_handles = set()

        if sp_inbox is None:
            canonical_username = utils.to_char(canonical_username)

            if isinstance(tracks, spotify.Track):
                tracks = [tracks]

            message = utils.to_char(message)

            handle = ffi.new_handle((callback, self))
            # TODO Think through the life cycle of the handle object. Can it
            # happen that we GC the search and handle object, and then later
            # the callback is called?
            self._callback_handles.add(handle)

            sp_inbox = lib.sp_inbox_post_tracks(
                spotify.session_instance._sp_session, canonical_username,
                [t._sp_track for t in tracks], len(tracks), message,
                _inboxpost_complete_callback, handle)
            add_ref = True

            if sp_inbox == ffi.NULL:
                raise spotify.Error('Inbox post request failed to initialize')

        if add_ref:
            lib.sp_inbox_add_ref(sp_inbox)
        self._sp_inbox = ffi.gc(sp_inbox, lib.sp_inbox_release)
Ejemplo n.º 12
0
    def __init__(self,
                 query='',
                 callback=None,
                 track_offset=0,
                 track_count=20,
                 album_offset=0,
                 album_count=20,
                 artist_offset=0,
                 artist_count=20,
                 playlist_offset=0,
                 playlist_count=20,
                 search_type=None,
                 sp_search=None,
                 add_ref=True):

        assert query or sp_search, 'query or sp_search is required'

        self.callback = callback
        self.track_offset = track_offset
        self.track_count = track_count
        self.album_offset = album_offset
        self.album_count = album_count
        self.artist_offset = artist_offset
        self.artist_count = artist_count
        self.playlist_offset = playlist_offset
        self.playlist_count = playlist_count
        if search_type is None:
            search_type = SearchType.STANDARD
        self.search_type = search_type

        self.complete_event = threading.Event()
        self._callback_handles = set()

        if sp_search is None:
            handle = ffi.new_handle((callback, self))
            # TODO Think through the life cycle of the handle object. Can it
            # happen that we GC the search and handle object, and then later
            # the callback is called?
            self._callback_handles.add(handle)

            sp_search = lib.sp_search_create(
                spotify.session_instance._sp_session, utils.to_char(query),
                track_offset, track_count, album_offset, album_count,
                artist_offset, artist_count, playlist_offset, playlist_count,
                int(search_type), _search_complete_callback, handle)
            add_ref = False

        if add_ref:
            lib.sp_search_add_ref(sp_search)
        self._sp_search = ffi.gc(sp_search, lib.sp_search_release)
Ejemplo n.º 13
0
    def add_folder(self, name, index=None):
        """Add a playlist folder with ``name`` at the given ``index``.

        The playlist folder name must not be space-only or longer than 255
        chars.

        If the ``index`` isn't specified, the folder is added at the end of the
        container.
        """
        self._validate_name(name)
        if index is None:
            index = self.__len__()
        spotify.Error.maybe_raise(lib.sp_playlistcontainer_add_folder(
            self._sp_playlistcontainer, index, utils.to_char(name)))
Ejemplo n.º 14
0
    def add_folder(self, name, index=None):
        """Add a playlist folder with ``name`` at the given ``index``.

        The playlist folder name must not be space-only or longer than 255
        chars.

        If the ``index`` isn't specified, the folder is added at the end of the
        container.
        """
        self._validate_name(name)
        if index is None:
            index = self.__len__()
        spotify.Error.maybe_raise(
            lib.sp_playlistcontainer_add_folder(self._sp_playlistcontainer,
                                                index, utils.to_char(name)))
Ejemplo n.º 15
0
    def __init__(self, session, uri=None, sp_link=None, add_ref=True):
        assert uri or sp_link, 'uri or sp_link is required'

        self._session = session

        if uri is not None:
            sp_link = lib.sp_link_create_from_string(
                utils.to_char(Link._normalize_uri(uri)))
            add_ref = False
            if sp_link == ffi.NULL:
                raise ValueError('Failed to get link from Spotify URI: %r' %
                                 uri)

        if add_ref:
            lib.sp_link_add_ref(sp_link)
        self._sp_link = ffi.gc(sp_link, lib.sp_link_release)
Ejemplo n.º 16
0
    def __init__(self, uri=None, sp_link=None, add_ref=True):
        assert uri or sp_link, 'uri or sp_link is required'

        if spotify.session_instance is None:
            raise RuntimeError('Session must be initialized to create links')

        if uri is not None:
            sp_link = lib.sp_link_create_from_string(utils.to_char(uri))
            add_ref = False
            if sp_link == ffi.NULL:
                raise ValueError('Failed to get link from Spotify URI: %r' %
                                 uri)

        if add_ref:
            lib.sp_link_add_ref(sp_link)
        self._sp_link = ffi.gc(sp_link, lib.sp_link_release)
Ejemplo n.º 17
0
    def __init__(self, session, uri=None, sp_link=None, add_ref=True):
        assert uri or sp_link, 'uri or sp_link is required'

        self._session = session

        if uri is not None:
            sp_link = lib.sp_link_create_from_string(
                utils.to_char(Link._normalize_uri(uri)))
            add_ref = False
            if sp_link == ffi.NULL:
                raise ValueError(
                    'Failed to get link from Spotify URI: %r' % uri)

        if add_ref:
            lib.sp_link_add_ref(sp_link)
        self._sp_link = ffi.gc(sp_link, lib.sp_link_release)
Ejemplo n.º 18
0
    def __init__(
            self, session, query='', callback=None,
            track_offset=0, track_count=20,
            album_offset=0, album_count=20,
            artist_offset=0, artist_count=20,
            playlist_offset=0, playlist_count=20,
            search_type=None,
            sp_search=None, add_ref=True):

        assert query or sp_search, 'query or sp_search is required'

        self._session = session
        self.callback = callback
        self.track_offset = track_offset
        self.track_count = track_count
        self.album_offset = album_offset
        self.album_count = album_count
        self.artist_offset = artist_offset
        self.artist_count = artist_count
        self.playlist_offset = playlist_offset
        self.playlist_count = playlist_count
        if search_type is None:
            search_type = SearchType.STANDARD
        self.search_type = search_type

        self.loaded_event = threading.Event()

        if sp_search is None:
            handle = ffi.new_handle((self._session, self, callback))
            self._session._callback_handles.add(handle)

            sp_search = lib.sp_search_create(
                self._session._sp_session, utils.to_char(query),
                track_offset, track_count,
                album_offset, album_count,
                artist_offset, artist_count,
                playlist_offset, playlist_count,
                int(search_type), _search_complete_callback, handle)
            add_ref = False

        if add_ref:
            lib.sp_search_add_ref(sp_search)
        self._sp_search = ffi.gc(sp_search, lib.sp_search_release)
Ejemplo n.º 19
0
    def add_new_playlist(self, name, index=None):
        """Add an empty playlist with ``name`` at the given ``index``.

        The playlist name must not be space-only or longer than 255 chars.

        If the ``index`` isn't specified, the new playlist is added at the end
        of the container.

        Returns the new playlist.
        """
        self._validate_name(name)
        sp_playlist = lib.sp_playlistcontainer_add_new_playlist(
            self._sp_playlistcontainer, utils.to_char(name))
        if sp_playlist == ffi.NULL:
            raise spotify.Error('Playlist creation failed')
        playlist = spotify.Playlist._cached(
            self._session, sp_playlist, add_ref=True)
        if index is not None:
            self.move_playlist(self.__len__() - 1, index)
        return playlist
Ejemplo n.º 20
0
    def add_new_playlist(self, name, index=None):
        """Add an empty playlist with ``name`` at the given ``index``.

        The playlist name must not be space-only or longer than 255 chars.

        If the ``index`` isn't specified, the new playlist is added at the end
        of the container.

        Returns the new playlist.
        """
        self._validate_name(name)
        sp_playlist = lib.sp_playlistcontainer_add_new_playlist(
            self._sp_playlistcontainer, utils.to_char(name))
        if sp_playlist == ffi.NULL:
            raise spotify.Error('Playlist creation failed')
        playlist = spotify.Playlist._cached(self._session,
                                            sp_playlist,
                                            add_ref=True)
        if index is not None:
            self.move_playlist(self.__len__() - 1, index)
        return playlist
Ejemplo n.º 21
0
 def cache_location(self, value):
     # NOTE libspotify segfaults if cache_location is set to NULL, thus we
     # convert None to empty string.
     self._cache_location = utils.to_char('' if value is None else value)
     self._sp_session_config.cache_location = self._cache_location
Ejemplo n.º 22
0
    def test_anything_else_fails(self):
        with self.assertRaises(ValueError):
            utils.to_char(None)

        with self.assertRaises(ValueError):
            utils.to_char(123)
Ejemplo n.º 23
0
    def test_unicode_becomes_char(self):
        result = utils.to_char('æøå')

        self.assertIsInstance(result, spotify.ffi.CData)
        self.assertEqual(spotify.ffi.string(result).decode('utf-8'), 'æøå')
Ejemplo n.º 24
0
    def test_bytes_becomes_char(self):
        result = utils.to_char(b'abc')

        self.assertIsInstance(result, spotify.ffi.CData)
        self.assertEqual(spotify.ffi.string(result), b'abc')
Ejemplo n.º 25
0
 def user_agent(self, value):
     self._user_agent = utils.to_char('' if value is None else value)
     self._sp_session_config.user_agent = self._user_agent
Ejemplo n.º 26
0
 def rename(self, new_name):
     """Rename the playlist."""
     spotify.Error.maybe_raise(
         lib.sp_playlist_rename(self._sp_playlist, utils.to_char(new_name)))
Ejemplo n.º 27
0
 def proxy_password(self, value):
     # NOTE libspotify reuses cached values from previous sessions if this
     # is set to NULL, thus we convert None to empty string.
     self._proxy_password = utils.to_char('' if value is None else value)
     self._sp_session_config.proxy_password = self._proxy_password
Ejemplo n.º 28
0
 def rename(self, new_name):
     """Rename the playlist."""
     spotify.Error.maybe_raise(
         lib.sp_playlist_rename(self._sp_playlist, utils.to_char(new_name)))
Ejemplo n.º 29
0
 def settings_location(self, value):
     self._settings_location = utils.to_char('' if value is None else value)
     self._sp_session_config.settings_location = self._settings_location