from __future__ import unicode_literals
import re
try:
# Python 3
from urllib.parse import urlparse # noqa
except ImportError:
# Python 2
from urlparse import urlparse # noqa
import spotify
from spotify import ffi, lib, serialized, utils
__all__ = ["Link", "LinkType"]
[docs]class Link(object):
"""A Spotify object link.
Call the :meth:`~Session.get_link` method on your :class:`Session` instance
to get a :class:`Link` object from a Spotify URI. You can also get links
from the ``link`` attribute on most objects, e.g. :attr:`Track.link`.
To get the URI from the link object you can use the :attr:`uri` attribute,
or simply use the link as a string::
>>> session = spotify.Session()
# ...
>>> link = session.get_link(
... 'spotify:track:2Foc5Q5nqNiosCNqttzHof')
>>> link
Link('spotify:track:2Foc5Q5nqNiosCNqttzHof')
>>> link.uri
'spotify:track:2Foc5Q5nqNiosCNqttzHof'
>>> str(link)
'spotify:track:2Foc5Q5nqNiosCNqttzHof'
>>> link.type
<LinkType.TRACK: 1>
>>> track = link.as_track()
>>> track.link
Link('spotify:track:2Foc5Q5nqNiosCNqttzHof')
>>> track.load().name
u'Get Lucky'
You can also get :class:`Link` objects from open.spotify.com and
play.spotify.com URLs::
>>> session.get_link(
... 'http://open.spotify.com/track/4wl1dK5dHGp3Ig51stvxb0')
Link('spotify:track:4wl1dK5dHGp3Ig51stvxb0')
>>> session.get_link(
... 'https://play.spotify.com/track/4wl1dK5dHGp3Ig51stvxb0'
... '?play=true&utm_source=open.spotify.com&utm_medium=open')
Link('spotify:track:4wl1dK5dHGp3Ig51stvxb0')
"""
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)
@staticmethod
def _normalize_uri(uri):
if uri.startswith("spotify:"):
return uri
parsed = urlparse(uri)
if parsed.netloc not in ("open.spotify.com", "play.spotify.com"):
return uri
return "spotify%s" % parsed.path.strip().replace("/", ":")
def __repr__(self):
return "Link(%r)" % self.uri
def __str__(self):
return self.uri
def __eq__(self, other):
if isinstance(other, self.__class__):
return self._sp_link == other._sp_link
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self._sp_link)
@property
def uri(self):
"""The link's Spotify URI."""
return utils.get_with_growing_buffer(lib.sp_link_as_string, self._sp_link)
@property
def url(self):
"""The link's HTTP URL."""
return "https://open.spotify.com%s" % (
self.uri[len("spotify") :].replace(":", "/")
)
@property
def type(self):
"""The link's :class:`LinkType`."""
return LinkType(lib.sp_link_type(self._sp_link))
@serialized
def as_track(self):
"""Make a :class:`Track` from the link."""
sp_track = lib.sp_link_as_track(self._sp_link)
if sp_track == ffi.NULL:
return None
return spotify.Track(self._session, sp_track=sp_track, add_ref=True)
def as_track_offset(self):
"""Get the track offset in milliseconds from the link."""
offset = ffi.new("int *")
sp_track = lib.sp_link_as_track_and_offset(self._sp_link, offset)
if sp_track == ffi.NULL:
return None
return offset[0]
@serialized
def as_album(self):
"""Make an :class:`Album` from the link."""
sp_album = lib.sp_link_as_album(self._sp_link)
if sp_album == ffi.NULL:
return None
return spotify.Album(self._session, sp_album=sp_album, add_ref=True)
@serialized
def as_artist(self):
"""Make an :class:`Artist` from the link."""
sp_artist = lib.sp_link_as_artist(self._sp_link)
if sp_artist == ffi.NULL:
return None
return spotify.Artist(self._session, sp_artist=sp_artist, add_ref=True)
def as_playlist(self):
"""Make a :class:`Playlist` from the link."""
sp_playlist = self._as_sp_playlist()
if sp_playlist is None:
return None
return spotify.Playlist._cached(self._session, sp_playlist, add_ref=False)
def _as_sp_playlist(self):
sp_playlist = None
if self.type == LinkType.PLAYLIST:
sp_playlist = lib.sp_playlist_create(
self._session._sp_session, self._sp_link
)
elif self.type == LinkType.STARRED:
matches = re.match(r"^spotify:user:([^:]+):starred$", self.uri)
if matches:
username = matches.group(1)
sp_playlist = lib.sp_session_starred_for_user_create(
self._session._sp_session, utils.to_bytes(username)
)
if sp_playlist is None or sp_playlist == ffi.NULL:
return None
return sp_playlist
@serialized
def as_user(self):
"""Make an :class:`User` from the link."""
sp_user = lib.sp_link_as_user(self._sp_link)
if sp_user == ffi.NULL:
return None
return spotify.User(self._session, sp_user=sp_user, add_ref=True)
def as_image(self, callback=None):
"""Make an :class:`Image` from the link.
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.
"""
if self.type is not LinkType.IMAGE:
return None
sp_image = lib.sp_image_create_from_link(
self._session._sp_session, self._sp_link
)
if sp_image == ffi.NULL:
return None
return spotify.Image(
self._session, sp_image=sp_image, add_ref=False, callback=callback
)
[docs]@utils.make_enum("SP_LINKTYPE_")
class LinkType(utils.IntEnum):
pass