class Image(Descriptor): __protobuf__ = metadata_pb2.Image file_uri = PropertyProxy( 'file_id', func=lambda file_id: Uri.from_gid('image', file_id)) size = PropertyProxy() width = PropertyProxy height = PropertyProxy @staticmethod def __parsers__(): return [MercuryJSON, XML, Tunigo] @property def file_url(self): if not self.file_uri or type(self.file_uri) is not Uri: return None return 'https://%s/%s/%s' % (RESOURCE_HOST, SIZES.get(self.size, self.width), self.file_uri.to_id(size=40)) @classmethod def from_id(cls, id): return cls(None, { 'file_id': Uri.from_id('image', id).to_gid(size=40), 'size': 3 })
class Artist(Descriptor): __protobuf__ = metadata_pb2.Artist gid = PropertyProxy uri = PropertyProxy('gid', func=lambda gid: Uri.from_gid('artist', gid)) name = PropertyProxy popularity = PropertyProxy top_tracks = PropertyProxy('top_track', 'TopTracks') albums = PropertyProxy('album_group', 'AlbumGroup') singles = PropertyProxy('single_group', 'AlbumGroup') compilations = PropertyProxy('compilation_group', 'AlbumGroup') appears_on = PropertyProxy('appears_on_group', 'AlbumGroup') genres = PropertyProxy('genre') external_ids = PropertyProxy('external_id', 'ExternalId') portraits = PropertyProxy('portrait', 'Image') biographies = PropertyProxy('biography') activity_periods = PropertyProxy('activity_period') restrictions = PropertyProxy('restriction') related = PropertyProxy('related') is_portrait_album_cover = PropertyProxy('is_portrait_album_cover') portrait_group = PropertyProxy('portrait_group') @staticmethod def __parsers__(): return [MercuryJSON, XML, Tunigo]
def track_uri(self, callback=None): """Requests the track stream URI. :param callback: Callback to trigger on a successful response :type callback: function :return: decorate wrapper if no callback is provided, otherwise returns the `Request` object. :rtype: function or `spotify.core.request.Request` """ def on_track_uri(response): def on_track_key(key): if callback: callback(response, EncryptedStream(self.sp, key)) self.sp.trackKeyQueue.put(on_track_key) files = {} for file in self.files: files[file.format] = file log.debug("[%s] File format found: %s", self.uri, file.format) request = self.build("sp/track_uri2", self.uri.to_id(), Uri.from_gid(None, files.get(7).file_id)) return self.request_wrapper(request, on_track_uri)
def get_object_key(self, content_type, internal): group, type = self.get_schema_key(content_type) if group is None or type is None: return None, None uri = Uri.from_gid(type, internal.gid) return "hm://%s/%s" % (group, uri.type), uri.to_id()
def get_object_key(self, content_type, internal): group, type = self.get_schema_key(content_type) if group is None or type is None: return None, None uri = Uri.from_gid(type, internal.gid) return 'hm://%s/%s' % (group, uri.type), uri.to_id()
class Album(Descriptor): __protobuf__ = metadata_pb2.Album gid = PropertyProxy uri = PropertyProxy('gid', func=lambda gid: Uri.from_gid('album', gid)) name = PropertyProxy artists = PropertyProxy('artist', 'Artist') type = PropertyProxy label = PropertyProxy date = PropertyProxy(func=PropertyProxy.parse_date) popularity = PropertyProxy genres = PropertyProxy('genre') covers = PropertyProxy('cover', 'Image') external_ids = PropertyProxy('external_id', 'ExternalId') discs = PropertyProxy('disc', 'Disc') # review - [] copyrights = PropertyProxy('copyright', 'Copyright') restrictions = PropertyProxy('restriction', 'Restriction') # related - [] # sale_period - [] cover_group = PropertyProxy('cover_group', 'ImageGroup') @staticmethod def __parsers__(): return [MercuryJSON, XML, Tunigo] def is_available(self): message = '' for restriction in self.restrictions: success, message = restriction.check() if success: return True log.debug('Album "%s" not available (%s)', self.uri, message) return False @property def tracks(self): # Iterate through each disc and return a flat track list for disc in self.discs: for track in disc.tracks: yield track
class Track(Descriptor): __protobuf__ = metadata_pb2.Track gid = PropertyProxy uri = PropertyProxy('gid', func=lambda gid: Uri.from_gid('track', gid)) name = PropertyProxy album = PropertyProxy('album', 'Album') artists = PropertyProxy('artist', 'Artist') number = PropertyProxy disc_number = PropertyProxy duration = PropertyProxy popularity = PropertyProxy explicit = PropertyProxy external_ids = PropertyProxy('external_id', 'ExternalId') restrictions = PropertyProxy('restriction', 'Restriction') files = PropertyProxy('file', 'AudioFile') alternatives = PropertyProxy('alternative', 'Track') # sale_period - [] preview = PropertyProxy @staticmethod def __parsers__(): return [XML] def is_available(self): catalogues = {} available = False for restriction in self.restrictions: re_available, re_allowed = restriction.check() if re_allowed and restriction.catalogues: for catalogue in restriction.catalogues: catalogues[catalogue] = True if restriction.type is None or restriction.type == 'streaming': available |= re_available if catalogues.get(self.sp.catalogue): return True return False def find_alternative(self): if not self.alternatives: log.debug('No alternatives available for "%s"', self.uri) return False alternative = None # Try find an available alternative for alternative in self.alternatives: if alternative.is_available(): break else: alternative = None if alternative is None: log.debug('Unable to find alternative for "%s"', self.uri) return False # Update our object with new attributes self.protobuf_update(alternative, 'uri', 'gid', 'restrictions', 'files') return True def track_uri(self, callback=None): """Requests the track stream URI. :param callback: Callback to trigger on a successful response :type callback: function :return: decorate wrapper if no callback is provided, otherwise returns the `Request` object. :rtype: function or `spotify.core.request.Request` """ request = self.build('sp/track_uri', 'mp3160', self.uri.to_id()) return self.request_wrapper(request, callback) def track_event(self, lid, event, time): """Send the "sp/track_event" event. :param lid: Stream lid (from "sp/track_uri") :param event: Event :param time: Current track playing position (in milliseconds) """ return self.send('sp/track_event', lid, event, time) def track_progress(self, lid, position, source='unknown', reason='unknown', latency=150, context='unknown', referrer=None): """ :type lid: str :type position: int :type source: str :type reason: str :type latency: int :type context: str :type referrer: dict {'referrer', 'version', 'vendor'} :return: """ referrer = set_defaults(referrer, { 'referrer': 'unknown', 'version': '0.1.0', 'vendor': 'com.spotify' }) return self.send( 'sp/track_progress', lid, # Start source, reason, # Timings position, latency, # Context context, str(self.uri), # Referrer referrer['referrer'], referrer['version'], referrer['vendor'], ) def track_end(self, lid, position, seeks=None, latency=150, context='unknown', source=None, reason=None, referrer=None): """ :type lid: str :type position: int :type seeks: dict {'num_forward', 'num_backward', 'ms_forward', 'ms_backward'} :type latency: int :type context: str :type source: dict {'start', 'end'} :type reason: dict {'start', 'end'} :type referrer: dict {'referrer', 'version', 'vendor'} :return: """ seeks = set_defaults(seeks, { 'num_forward': 0, 'num_backward': 0, 'ms_forward': 0, 'ms_backward': 0 }) source = set_defaults(source, {'start': 'unknown', 'end': 'unknown'}) reason = set_defaults(reason, {'start': 'unknown', 'end': 'unknown'}) referrer = set_defaults(referrer, { 'referrer': 'unknown', 'version': '0.1.0', 'vendor': 'com.spotify' }) return self.send( 'sp/track_end', lid, # Timings position, # ms_played position, # ms_played_union # Seek count seeks['num_forward'], seeks['num_backward'], seeks['ms_forward'], seeks['ms_backward'], latency, # Context str(self.uri), context, # Source source['start'], source['end'], # Reason reason['start'], reason['end'], # Referrer referrer['referrer'], referrer['version'], referrer['vendor'], position # max_continuous )