Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion librespot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


class Version:
version_name = "0.0.9"
version_name = "0.0.10"

@staticmethod
def platform() -> Platform:
Expand Down
79 changes: 39 additions & 40 deletions librespot/audio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ def load_track(
session: Session, track: Metadata.Track, file: Metadata.AudioFile,
resp_or_url: typing.Union[StorageResolve.StorageResolveResponse,
str], preload: bool,
halt_listener: HaltListener) -> PlayableContentFeeder.LoadedStream:
halt_listener: HaltListener) -> LoadedStream:
if type(resp_or_url) is str:
url = resp_or_url
else:
Expand All @@ -345,18 +345,17 @@ def load_track(
normalization_data = NormalizationData.read(input_stream)
if input_stream.skip(0xA7) != 0xA7:
raise IOError("Couldn't skip 0xa7 bytes!")
return PlayableContentFeeder.LoadedStream(
return LoadedStream(
track,
streamer,
normalization_data,
PlayableContentFeeder.Metrics(file.file_id, preload,
-1 if preload else audio_key_time),
file.file_id, preload, audio_key_time
)

@staticmethod
def load_episode_external(
session: Session, episode: Metadata.Episode,
halt_listener: HaltListener) -> PlayableContentFeeder.LoadedStream:
halt_listener: HaltListener) -> LoadedStream:
resp = session.client().head(episode.external_url)

if resp.status_code != 200:
Expand All @@ -368,11 +367,11 @@ def load_episode_external(

streamer = session.cdn().stream_external_episode(
episode, url, halt_listener)
return PlayableContentFeeder.LoadedStream(
return LoadedStream(
episode,
streamer,
None,
PlayableContentFeeder.Metrics(None, False, -1),
None, False, -1
)

@staticmethod
Expand All @@ -383,7 +382,7 @@ def load_episode(
resp_or_url: typing.Union[StorageResolve.StorageResolveResponse, str],
preload: bool,
halt_listener: HaltListener,
) -> PlayableContentFeeder.LoadedStream:
) -> LoadedStream:
if type(resp_or_url) is str:
url = resp_or_url
else:
Expand All @@ -397,12 +396,11 @@ def load_episode(
normalization_data = NormalizationData.read(input_stream)
if input_stream.skip(0xA7) != 0xA7:
raise IOError("Couldn't skip 0xa7 bytes!")
return PlayableContentFeeder.LoadedStream(
return LoadedStream(
episode,
streamer,
normalization_data,
PlayableContentFeeder.Metrics(file.file_id, preload,
-1 if preload else audio_key_time),
file.file_id, preload, audio_key_time
)


Expand Down Expand Up @@ -748,7 +746,9 @@ def load_stream(self, file: Metadata.AudioFile, track: Metadata.Track,
episode: Metadata.Episode, preload: bool,
halt_lister: HaltListener):
if track is None and episode is None:
raise RuntimeError()
raise RuntimeError("No content passed!")
elif file is None:
raise RuntimeError("Content has no audio file!")
response = self.resolve_storage_interactive(file.file_id, preload)
if response.result == StorageResolve.StorageResolveResponse.Result.CDN:
if track is not None:
Expand Down Expand Up @@ -778,6 +778,7 @@ def load_episode(self, episode_id: EpisodeId,
self.logger.fatal(
"Couldn't find any suitable audio file, available: {}".format(
episode.audio))
raise FeederException("Cannot find suitable audio file")
return self.load_stream(file, None, episode, preload, halt_listener)

def load_track(self, track_id_or_track: typing.Union[TrackId,
Expand All @@ -797,7 +798,7 @@ def load_track(self, track_id_or_track: typing.Union[TrackId,
self.logger.fatal(
"Couldn't find any suitable audio file, available: {}".format(
track.file))
raise FeederException()
raise FeederException("Cannot find suitable audio file")
return self.load_stream(file, track, None, preload, halt_listener)

def pick_alternative_if_necessary(
Expand Down Expand Up @@ -848,43 +849,41 @@ def resolve_storage_interactive(
storage_resolve_response.ParseFromString(body)
return storage_resolve_response

class LoadedStream:
episode: Metadata.Episode
track: Metadata.Track
input_stream: GeneralAudioStream
normalization_data: NormalizationData
metrics: PlayableContentFeeder.Metrics

def __init__(self, track_or_episode: typing.Union[Metadata.Track,
Metadata.Episode],
input_stream: GeneralAudioStream,
normalization_data: typing.Union[NormalizationData, None],
metrics: PlayableContentFeeder.Metrics):
if type(track_or_episode) is Metadata.Track:
self.track = track_or_episode
self.episode = None
elif type(track_or_episode) is Metadata.Episode:
self.track = None
self.episode = track_or_episode
else:
raise TypeError()
self.input_stream = input_stream
self.normalization_data = normalization_data
self.metrics = metrics

class LoadedStream:
episode: Metadata.Episode
track: Metadata.Track
input_stream: GeneralAudioStream
normalization_data: NormalizationData
metrics: Metrics

class Metrics:
file_id: str
preloaded_audio_key: bool
audio_key_time: int

def __init__(self, file_id: typing.Union[bytes, None],
preloaded_audio_key: bool, audio_key_time: int):
preloaded_audio_key: bool, audio_key_time: int):
self.file_id = None if file_id is None else util.bytes_to_hex(
file_id)
self.preloaded_audio_key = preloaded_audio_key
self.audio_key_time = audio_key_time
if preloaded_audio_key and audio_key_time != -1:
raise RuntimeError()
self.audio_key_time = -1 if preloaded_audio_key else audio_key_time

def __init__(self, track_or_episode: typing.Union[Metadata.Track, Metadata.Episode],
input_stream: GeneralAudioStream,
normalization_data: typing.Union[NormalizationData, None],
file_id: str, preloaded_audio_key: bool, audio_key_time: int):
if type(track_or_episode) is Metadata.Track:
self.track = track_or_episode
self.episode = None
elif type(track_or_episode) is Metadata.Episode:
self.track = None
self.episode = track_or_episode
else:
raise TypeError()
self.input_stream = input_stream
self.normalization_data = normalization_data
self.metrics = self.Metrics(file_id, preloaded_audio_key, audio_key_time)


class StreamId:
Expand Down
117 changes: 54 additions & 63 deletions librespot/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
from librespot.proto import Keyexchange_pb2 as Keyexchange
from librespot.proto import Metadata_pb2 as Metadata
from librespot.proto import Playlist4External_pb2 as Playlist4External
from librespot.proto.ExtendedMetadata_pb2 import EntityRequest, BatchedEntityRequest, ExtensionQuery, BatchedExtensionResponse
from librespot.proto.ExtensionKind_pb2 import ExtensionKind
from librespot.proto.ExplicitContentPubsub_pb2 import UserAttributesUpdate
from librespot.proto.spotify.login5.v3 import Login5_pb2 as Login5
from librespot.proto.spotify.login5.v3.credentials import Credentials_pb2 as Login5Credentials
Expand Down Expand Up @@ -104,20 +106,20 @@ def build_request(
self.logger.debug("Updated client token: {}".format(
self.__client_token_str))

request = requests.PreparedRequest()
request.method = method
request.data = body
request.headers = CaseInsensitiveDict()
if headers is not None:
request.headers = headers
request.headers["Authorization"] = "Bearer {}".format(
self.__session.tokens().get("playlist-read"))
request.headers["client-token"] = self.__client_token_str
if url is None:
request.url = self.__base_url + suffix
url = self.__base_url + suffix
else:
request.url = url + suffix
return request
url = url + suffix

if headers is None:
headers = CaseInsensitiveDict()
headers["Authorization"] = "Bearer {}".format(
self.__session.tokens().get("playlist-read"))
headers["client-token"] = self.__client_token_str

request = requests.Request(method, url, headers=headers, data=body)

return request.prepare()

def send(
self,
Expand Down Expand Up @@ -190,91 +192,80 @@ def put_connect_state(self, connection_id: str,
self.logger.warning("PUT state returned {}. headers: {}".format(
response.status_code, response.headers))

def get_ext_metadata(self, extension_kind: ExtensionKind, uri: str):
headers = CaseInsensitiveDict({"content-type": "application/x-protobuf"})
req = EntityRequest(entity_uri=uri, query=[ExtensionQuery(extension_kind=extension_kind),])

response = self.send("POST", "/extended-metadata/v0/extended-metadata",
headers, BatchedEntityRequest(entity_request=[req,]).SerializeToString())
ApiClient.StatusCodeException.check_status(response)

body = response.content
if body is None:
raise ConnectionError("Extended Metadata request failed: No response body")

proto = BatchedExtensionResponse()
proto.ParseFromString(body)
entityextd = proto.extended_metadata.pop().extension_data.pop()
if entityextd.header.status_code != 200:
raise ConnectionError("Extended Metadata request failed: Status code {}".format(entityextd.header.status_code))
mdb: bytes = entityextd.extension_data.value
return mdb

def get_metadata_4_track(self, track: TrackId) -> Metadata.Track:
"""

:param track: TrackId:

"""
response = self.sendToUrl("GET", "https://spclient.wg.spotify.com",
"/metadata/4/track/{}".format(track.hex_id()),
None, None)
ApiClient.StatusCodeException.check_status(response)
body = response.content
if body is None:
raise RuntimeError()
proto = Metadata.Track()
proto.ParseFromString(body)
return proto
mdb = self.get_ext_metadata(ExtensionKind.TRACK_V4, track.to_spotify_uri())
md = Metadata.Track()
md.ParseFromString(mdb)
return md

def get_metadata_4_episode(self, episode: EpisodeId) -> Metadata.Episode:
"""

:param episode: EpisodeId:

"""
response = self.sendToUrl("GET", "https://spclient.wg.spotify.com",
"/metadata/4/episode/{}".format(episode.hex_id()),
None, None)
ApiClient.StatusCodeException.check_status(response)
body = response.content
if body is None:
raise IOError()
proto = Metadata.Episode()
proto.ParseFromString(body)
return proto
mdb = self.get_ext_metadata(ExtensionKind.EPISODE_V4, episode.to_spotify_uri())
md = Metadata.Episode()
md.ParseFromString(mdb)
return md

def get_metadata_4_album(self, album: AlbumId) -> Metadata.Album:
"""

:param album: AlbumId:

"""
response = self.sendToUrl("GET", "https://spclient.wg.spotify.com",
"/metadata/4/album/{}".format(album.hex_id()),
None, None)
ApiClient.StatusCodeException.check_status(response)

body = response.content
if body is None:
raise IOError()
proto = Metadata.Album()
proto.ParseFromString(body)
return proto
mdb = self.get_ext_metadata(ExtensionKind.ALBUM_V4, album.to_spotify_uri())
md = Metadata.Album()
md.ParseFromString(mdb)
return md

def get_metadata_4_artist(self, artist: ArtistId) -> Metadata.Artist:
"""

:param artist: ArtistId:

"""
response = self.sendToUrl("GET", "https://spclient.wg.spotify.com",
"/metadata/4/artist/{}".format(artist.hex_id()),
None, None)
ApiClient.StatusCodeException.check_status(response)
body = response.content
if body is None:
raise IOError()
proto = Metadata.Artist()
proto.ParseFromString(body)
return proto
mdb = self.get_ext_metadata(ExtensionKind.ARTIST_V4, artist.to_spotify_uri())
md = Metadata.Artist()
md.ParseFromString(mdb)
return md

def get_metadata_4_show(self, show: ShowId) -> Metadata.Show:
"""

:param show: ShowId:

"""
response = self.sendToUrl("GET", "https://spclient.wg.spotify.com",
"/metadata/4/show/{}".format(show.hex_id()), None,
None)
ApiClient.StatusCodeException.check_status(response)
body = response.content
if body is None:
raise IOError()
proto = Metadata.Show()
proto.ParseFromString(body)
return proto
mdb = self.get_ext_metadata(ExtensionKind.SHOW_V4, show.to_spotify_uri())
md = Metadata.Show()
md.ParseFromString(mdb)
return md

def get_playlist(self,
_id: PlaylistId) -> Playlist4External.SelectedListContent:
Expand Down
35 changes: 35 additions & 0 deletions librespot/proto/EntityExtensionData_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading