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)
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))
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))
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)
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)))
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)
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)))
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
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)
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)
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)
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)
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)))
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)))
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)
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)
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)
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)
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
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
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
def test_anything_else_fails(self): with self.assertRaises(ValueError): utils.to_char(None) with self.assertRaises(ValueError): utils.to_char(123)
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'), 'æøå')
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')
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
def rename(self, new_name): """Rename the playlist.""" spotify.Error.maybe_raise( lib.sp_playlist_rename(self._sp_playlist, utils.to_char(new_name)))
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
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