Source code for spotify.image

from __future__ import unicode_literals

import base64
import logging
import threading

import spotify
from spotify import ffi, lib, serialized, utils

__all__ = ["Image", "ImageFormat", "ImageSize"]

logger = logging.getLogger(__name__)


[docs]class Image(object): """A Spotify image. You can get images from :meth:`Album.cover`, :meth:`Artist.portrait`, :meth:`Playlist.image`, or you can create an :class:`Image` yourself from a Spotify URI:: >>> session = spotify.Session() # ... >>> image = session.get_image( ... 'spotify:image:a0bdcbe11b5cd126968e519b5ed1050b0e8183d0') >>> image.load().data_uri[:50] u'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEBLAEsAAD' If ``callback`` isn't :class:`None`, it is expected to be a callable that accepts a single argument, an :class:`Image` instance, when the image is done loading. """ def __init__(self, session, uri=None, sp_image=None, add_ref=True, callback=None): assert uri or sp_image, "uri or sp_image is required" self._session = session if uri is not None: image = spotify.Link(self._session, uri=uri).as_image() if image is None: raise ValueError("Failed to get image from Spotify URI: %r" % uri) sp_image = image._sp_image add_ref = True if add_ref: lib.sp_image_add_ref(sp_image) self._sp_image = ffi.gc(sp_image, lib.sp_image_release) self.loaded_event = threading.Event() handle = ffi.new_handle((self._session, self, callback)) self._session._callback_handles.add(handle) spotify.Error.maybe_raise( lib.sp_image_add_load_callback(self._sp_image, _image_load_callback, handle) ) def __repr__(self): return "Image(%r)" % self.link.uri def __eq__(self, other): if isinstance(other, self.__class__): return self._sp_image == other._sp_image else: return False def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash(self._sp_image) loaded_event = None """:class:`threading.Event` that is set when the image is loaded.""" @property def is_loaded(self): """Whether the image's data is loaded.""" return bool(lib.sp_image_is_loaded(self._sp_image)) @property def error(self): """An :class:`ErrorType` associated with the image. Check to see if there was problems loading the image. """ return spotify.ErrorType(lib.sp_image_error(self._sp_image)) def load(self, timeout=None): """Block until the image's data is loaded. After ``timeout`` seconds with no results :exc:`~spotify.Timeout` is raised. If ``timeout`` is :class:`None` the default timeout is used. The method returns ``self`` to allow for chaining of calls. """ return utils.load(self._session, self, timeout=timeout) @property def format(self): """The :class:`ImageFormat` of the image. Will always return :class:`None` if the image isn't loaded. """ if not self.is_loaded: return None return ImageFormat(lib.sp_image_format(self._sp_image)) @property @serialized def data(self): """The raw image data as a bytestring. Will always return :class:`None` if the image isn't loaded. """ if not self.is_loaded: return None data_size_ptr = ffi.new("size_t *") data = lib.sp_image_data(self._sp_image, data_size_ptr) buffer_ = ffi.buffer(data, data_size_ptr[0]) data_bytes = buffer_[:] assert len(data_bytes) == data_size_ptr[0], "%r == %r" % ( len(data_bytes), data_size_ptr[0], ) return data_bytes @property def data_uri(self): """The raw image data as a data: URI. Will always return :class:`None` if the image isn't loaded. """ if not self.is_loaded: return None if self.format is not ImageFormat.JPEG: raise ValueError("Unknown image format: %r" % self.format) return "data:image/jpeg;base64,%s" % ( base64.b64encode(self.data).decode("ascii") ) @property def link(self): """A :class:`Link` to the image.""" return spotify.Link( self._session, sp_link=lib.sp_link_create_from_image(self._sp_image), add_ref=False, )
@ffi.callback("void(sp_image *, void *)") @serialized def _image_load_callback(sp_image, handle): logger.debug("image_load_callback called") if handle == ffi.NULL: logger.warning("pyspotify image_load_callback called without userdata") return (session, image, callback) = ffi.from_handle(handle) session._callback_handles.remove(handle) image.loaded_event.set() if callback is not None: callback(image) # Load callbacks are by nature only called once per image, so we clean up # and remove the load callback the first time it is called. lib.sp_image_remove_load_callback(sp_image, _image_load_callback, handle)
[docs]@utils.make_enum("SP_IMAGE_FORMAT_") class ImageFormat(utils.IntEnum): pass
[docs]@utils.make_enum("SP_IMAGE_SIZE_") class ImageSize(utils.IntEnum): pass