def __init__(self, session: Session, stream_id: StreamId,
                     audio_format: SuperAudioFormat, cdn_url,
                     cache: CacheManager, audio_decrypt: AudioDecrypt,
                     halt_listener: HaltListener):
            self._session = session
            self._streamId = stream_id
            self._audioFormat = audio_format
            self._audioDecrypt = audio_decrypt
            self._cdnUrl = cdn_url
            self._haltListener = halt_listener

            resp = self.request(range_start=0,
                                range_end=ChannelManager.CHUNK_SIZE - 1)
            content_range = resp._headers.get("Content-Range")
            if content_range is None:
                raise IOError("Missing Content-Range header!")

            split = Utils.split(content_range, "/")
            self._size = int(split[1])
            self._chunks = int(
                math.ceil(self._size / ChannelManager.CHUNK_SIZE))

            first_chunk = resp._buffer

            self._available = [False for _ in range(self._chunks)]
            self._requested = [False for _ in range(self._chunks)]
            self._buffer = [bytearray() for _ in range(self._chunks)]
            self._internalStream = CdnManager.Streamer.InternalStream(
                self, False)

            self._requested[0] = True
            self.write_chunk(first_chunk, 0, False)
 def _put_connect_state(self, req: Connect.PutStateRequest):
     self._session.api().put_connect_state(self._connectionId, req)
     self._LOGGER.info("Put state. ts: {}, connId: {}, reason: {}".format(
         req.client_side_timestamp,
         Utils.truncate_middle(self._connectionId, 10),
         req.put_state_reason,
     ))
 def from_uri(uri: str) -> ArtistId:
     matcher = ArtistId._PATTERN.search(uri)
     if matcher is not None:
         artist_id = matcher.group(1)
         return ArtistId(
             Utils.bytes_to_hex(ArtistId._BASE62.decode(artist_id, 16)))
     raise TypeError("Not a Spotify artist ID: {}".format(uri))
Example #4
0
 def from_uri(uri: str) -> ShowId:
     matcher = ShowId._PATTERN.search(uri)
     if matcher is not None:
         show_id = matcher.group(1)
         return ShowId(
             Utils.bytes_to_hex(ShowId._BASE62.decode(show_id, 16)))
     raise TypeError("Not a Spotify show ID: {}".format(uri))
Example #5
0
 def from_uri(uri: str) -> TrackId:
     search = TrackId._PATTERN.search(uri)
     if search is not None:
         track_id = search.group(1)
         return TrackId(
             Utils.bytes_to_hex(PlayableId.BASE62.decode(track_id, 16)))
     raise RuntimeError("Not a Spotify track ID: {}".format(uri))
    def get_audio_url(self, file_id: bytes):
        resp = self._session.api().send(
            "GET", "/storage-resolve/files/audio/interactive/{}".format(
                Utils.bytes_to_hex(file_id)), None, None)

        if resp.status_code != 200:
            raise IOError(resp.status_code)

        body = resp.content
        if body is None:
            raise IOError("Response body is empty!")

        proto = StorageResolve.StorageResolveResponse()
        proto.ParseFromString(body)
        if proto.result == StorageResolve.StorageResolveResponse.Result.CDN:
            url = random.choice(proto.cdnurl)
            self._LOGGER.debug("Fetched CDN url for {}: {}".format(
                Utils.bytes_to_hex(file_id), url))
            return url
        raise CdnManager.CdnException(
            "Could not retrieve CDN url! result: {}".format(proto.result))
    def get_head(self, file_id: bytes):
        resp = self._session.client() \
            .get(self._session.get_user_attribute("head-files-url", "https://heads-fa.spotify.com/head/{file_id}")
                 .replace("{file_id}", Utils.bytes_to_hex(file_id)))

        if resp.status_code != 200:
            raise IOError("{}".format(resp.status_code))

        body = resp.content
        if body is None:
            raise IOError("Response body is empty!")

        return body
Example #8
0
 def to_spotify_uri(self) -> str:
     return "spotify:show:{}".format(
         ShowId._BASE62.encode(Utils.hex_to_bytes(self._hexId)))
Example #9
0
 def from_base62(base62: str) -> ShowId:
     return ShowId(Utils.bytes_to_hex(ShowId._BASE62.decode(base62, 16)))
Example #10
0
 def to_spotify_uri(self) -> str:
     return "spotify:artist:{}".format(
         ArtistId._BASE62.encode(Utils.hex_to_bytes(self._hexId)))
Example #11
0
 def from_base62(base62: str) -> ArtistId:
     return ArtistId(Utils.bytes_to_hex(ArtistId._BASE62.decode(base62,
                                                                16)))
    def dispatch(self, packet: Packet) -> None:
        payload = BytesInputStream(packet.payload)
        seq_length = payload.read_short()
        if seq_length == 2:
            seq = payload.read_short()
        elif seq_length == 4:
            seq = payload.read_int()
        elif seq_length == 8:
            seq = payload.read_long()
        else:
            raise RuntimeError("Unknown seq length: {}".format(seq_length))

        flags = payload.read_byte()
        parts = payload.read_short()

        partial = self._partials.get(seq)
        if partial is None or flags == 0:
            partial = []
            self._partials[seq] = partial

        self._LOGGER.debug(
            "Handling packet, cmd: 0x{}, seq: {}, flags: {}, parts: {}".format(
                Utils.bytes_to_hex(packet.cmd), seq, flags, parts))

        for i in range(parts):
            size = payload.read_short()
            buffer = payload.read(size)
            partial.append(buffer)
            self._partials[seq] = partial

        if flags != b"\x01":
            return

        self._partials.pop(seq)

        header = Mercury.Header()
        header.ParseFromString(partial[0])

        resp = MercuryClient.Response(header, partial)

        if packet.is_cmd(Packet.Type.mercury_event):
            dispatched = False
            with self._subscriptionsLock:
                for sub in self._subscriptions:
                    if sub.matches(header.uri):
                        sub.dispatch(resp)
                        dispatched = True

            if not dispatched:
                self._LOGGER.debug(
                    "Couldn't dispatch Mercury event seq: {}, uri: {}, code: {}, payload: {}"
                    .format(seq, header.uri, header.status_code, resp.payload))
        elif packet.is_cmd(Packet.Type.mercury_req) or \
                packet.is_cmd(Packet.Type.mercury_sub) or \
                packet.is_cmd(Packet.Type.mercury_sub):
            callback = self._callbacks.get(seq)
            self._callbacks.pop(seq)
            if callback is not None:
                callback.response(resp)
            else:
                self._LOGGER.warning(
                    "Skipped Mercury response, seq: {}, uri: {}, code: {}".
                    format(seq, resp.uri, resp.status_code))

            with self._removeCallbackLock:
                self._removeCallbackLock.notify_all()
        else:
            self._LOGGER.warning(
                "Couldn't handle packet, seq: {}, uri: {}, code: {}".format(
                    seq, header.uri, header.status_code))
Example #13
0
 def get_gid(self) -> bytes:
     return Utils.hex_to_bytes(self._hexId)
Example #14
0
 def from_base62(base62: str) -> TrackId:
     return TrackId(Utils.bytes_to_hex(PlayableId.BASE62.decode(base62,
                                                                16)))
Example #15
0
 def to_mercury_uri(self) -> str:
     return "spotify:album:{}".format(
         AlbumId._BASE62.encode(Utils.hex_to_bytes(self._hexId)))