Restyled by yapf

This commit is contained in:
Restyled.io
2021-05-21 03:41:17 +00:00
parent 7ac065fb4b
commit c534f4bd10
5 changed files with 463 additions and 487 deletions

View File

@@ -33,18 +33,15 @@ def client():
return return
if (args[0] == "p" or args[0] == "play") and len(args) == 2: if (args[0] == "p" or args[0] == "play") and len(args) == 2:
track_uri_search = re.search( track_uri_search = re.search(
r"^spotify:track:(?P<TrackID>[0-9a-zA-Z]{22})$", args[1] r"^spotify:track:(?P<TrackID>[0-9a-zA-Z]{22})$", args[1])
)
track_url_search = re.search( track_url_search = re.search(
r"^(https?://)?open.spotify.com/track/(?P<TrackID>[0-9a-zA-Z]{22})(\?si=.+?)?$", r"^(https?://)?open.spotify.com/track/(?P<TrackID>[0-9a-zA-Z]{22})(\?si=.+?)?$",
args[1], args[1],
) )
if track_uri_search is not None or track_url_search is not None: if track_uri_search is not None or track_url_search is not None:
track_id_str = ( track_id_str = (track_uri_search
track_uri_search if track_uri_search is not None else
if track_uri_search is not None track_url_search).group("TrackID")
else track_url_search
).group("TrackID")
play(track_id_str) play(track_id_str)
wait() wait()
if args[0] == "q" or args[0] == "quality": if args[0] == "q" or args[0] == "quality":
@@ -64,20 +61,22 @@ def client():
token = session.tokens().get("user-read-email") token = session.tokens().get("user-read-email")
resp = requests.get( resp = requests.get(
"https://api.spotify.com/v1/search", "https://api.spotify.com/v1/search",
{"limit": "5", "offset": "0", "q": cmd[2:], "type": "track"}, {
"limit": "5",
"offset": "0",
"q": cmd[2:],
"type": "track"
},
headers={"Authorization": "Bearer %s" % token}, headers={"Authorization": "Bearer %s" % token},
) )
i = 1 i = 1
tracks = resp.json()["tracks"]["items"] tracks = resp.json()["tracks"]["items"]
for track in tracks: for track in tracks:
print( print("%d, %s | %s" % (
"%d, %s | %s" i,
% ( track["name"],
i, ",".join([artist["name"] for artist in track["artists"]]),
track["name"], ))
",".join([artist["name"] for artist in track["artists"]]),
)
)
i += 1 i += 1
position = -1 position = -1
while True: while True:
@@ -115,8 +114,7 @@ def login():
def play(track_id_str: str): def play(track_id_str: str):
track_id = TrackId.from_base62(track_id_str) track_id = TrackId.from_base62(track_id_str)
stream = session.content_feeder().load( stream = session.content_feeder().load(
track_id, VorbisOnlyAudioQuality(AudioQuality.VERY_HIGH), False, None track_id, VorbisOnlyAudioQuality(AudioQuality.VERY_HIGH), False, None)
)
ffplay = subprocess.Popen( ffplay = subprocess.Popen(
["ffplay", "-"], ["ffplay", "-"],
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
@@ -132,13 +130,11 @@ def play(track_id_str: str):
def splash(): def splash():
print( print("=================================\n"
"=================================\n" "| Librespot-Python Player |\n"
"| Librespot-Python Player |\n" "| |\n"
"| |\n" "| by kokarare1212 |\n"
"| by kokarare1212 |\n" "=================================\n\n\n")
"=================================\n\n\n"
)
def main(): def main():

View File

@@ -22,7 +22,9 @@ class AbsChunkedInputStream(InputStream, HaltListener):
_decoded_length: int = 0 _decoded_length: int = 0
def __init__(self, retry_on_chunk_error: bool): def __init__(self, retry_on_chunk_error: bool):
self.retries: typing.Final[typing.List[int]] = [0 for _ in range(self.chunks())] self.retries: typing.Final[typing.List[int]] = [
0 for _ in range(self.chunks())
]
self.retry_on_chunk_error = retry_on_chunk_error self.retry_on_chunk_error = retry_on_chunk_error
def is_closed(self) -> bool: def is_closed(self) -> bool:
@@ -107,13 +109,10 @@ class AbsChunkedInputStream(InputStream, HaltListener):
self.request_chunk_from_stream(chunk) self.request_chunk_from_stream(chunk)
self.requested_chunks()[chunk] = True self.requested_chunks()[chunk] = True
for i in range( for i in range(chunk + 1,
chunk + 1, min(self.chunks() - 1, chunk + self.preload_ahead) + 1 min(self.chunks() - 1, chunk + self.preload_ahead) + 1):
): if (self.requested_chunks()[i]
if ( and self.retries[i] < self.preload_chunk_retries):
self.requested_chunks()[i]
and self.retries[i] < self.preload_chunk_retries
):
self.request_chunk_from_stream(i) self.request_chunk_from_stream(i)
self.requested_chunks()[chunk] = True self.requested_chunks()[chunk] = True
@@ -147,7 +146,10 @@ class AbsChunkedInputStream(InputStream, HaltListener):
self.check_availability(chunk, True, True) self.check_availability(chunk, True, True)
def read(self, b: bytearray = None, offset: int = None, length: int = None) -> int: def read(self,
b: bytearray = None,
offset: int = None,
length: int = None) -> int:
if b is None and offset is None and length is None: if b is None and offset is None and length is None:
return self.internal_read() return self.internal_read()
if not (b is not None and offset is not None and length is not None): if not (b is not None and offset is not None and length is not None):
@@ -157,9 +159,8 @@ class AbsChunkedInputStream(InputStream, HaltListener):
raise IOError("Stream is closed!") raise IOError("Stream is closed!")
if offset < 0 or length < 0 or length > len(b) - offset: if offset < 0 or length < 0 or length > len(b) - offset:
raise IndexError( raise IndexError("offset: {}, length: {}, buffer: {}".format(
"offset: {}, length: {}, buffer: {}".format(offset, length, len(b)) offset, length, len(b)))
)
elif length == 0: elif length == 0:
return 0 return 0
@@ -174,7 +175,8 @@ class AbsChunkedInputStream(InputStream, HaltListener):
self.check_availability(chunk, True, False) self.check_availability(chunk, True, False)
copy = min(len(self.buffer()[chunk]) - chunk_off, length - i) copy = min(len(self.buffer()[chunk]) - chunk_off, length - i)
b[offset + 0 : copy] = self.buffer()[chunk][chunk_off : chunk_off + copy] b[offset + 0:copy] = self.buffer()[chunk][chunk_off:chunk_off +
copy]
i += copy i += copy
self._pos += copy self._pos += copy
@@ -222,5 +224,4 @@ class AbsChunkedInputStream(InputStream, HaltListener):
@staticmethod @staticmethod
def from_stream_error(stream_error: int): def from_stream_error(stream_error: int):
return AbsChunkedInputStream.ChunkException( return AbsChunkedInputStream.ChunkException(
"Failed due to stream error, code: {}".format(stream_error) "Failed due to stream error, code: {}".format(stream_error))
)

View File

@@ -40,7 +40,8 @@ class CdnFeedHelper:
streamer = session.cdn().stream_file(file, key, url, halt_listener) streamer = session.cdn().stream_file(file, key, url, halt_listener)
input_stream = streamer.stream() input_stream = streamer.stream()
normalization_data = NormalizationData.NormalizationData.read(input_stream) normalization_data = NormalizationData.NormalizationData.read(
input_stream)
if input_stream.skip(0xA7) != 0xA7: if input_stream.skip(0xA7) != 0xA7:
raise IOError("Couldn't skip 0xa7 bytes!") raise IOError("Couldn't skip 0xa7 bytes!")
return PlayableContentFeeder.PlayableContentFeeder.LoadedStream( return PlayableContentFeeder.PlayableContentFeeder.LoadedStream(
@@ -48,13 +49,13 @@ class CdnFeedHelper:
streamer, streamer,
normalization_data, normalization_data,
PlayableContentFeeder.PlayableContentFeeder.Metrics( PlayableContentFeeder.PlayableContentFeeder.Metrics(
file.file_id, preload, -1 if preload else audio_key_time file.file_id, preload, -1 if preload else audio_key_time),
),
) )
@staticmethod @staticmethod
def load_episode_external( def load_episode_external(
session: Session, episode: Metadata.Episode, halt_listener: HaltListener session: Session, episode: Metadata.Episode,
halt_listener: HaltListener
) -> PlayableContentFeeder.PlayableContentFeeder.LoadedStream: ) -> PlayableContentFeeder.PlayableContentFeeder.LoadedStream:
resp = session.client().head(episode.external_url) resp = session.client().head(episode.external_url)
@@ -62,18 +63,17 @@ class CdnFeedHelper:
CdnFeedHelper._LOGGER.warning("Couldn't resolve redirect!") CdnFeedHelper._LOGGER.warning("Couldn't resolve redirect!")
url = resp.url url = resp.url
CdnFeedHelper._LOGGER.debug( CdnFeedHelper._LOGGER.debug("Fetched external url for {}: {}".format(
"Fetched external url for {}: {}".format( Utils.Utils.bytes_to_hex(episode.gid), url))
Utils.Utils.bytes_to_hex(episode.gid), url
)
)
streamer = session.cdn().stream_external_episode(episode, url, halt_listener) streamer = session.cdn().stream_external_episode(
episode, url, halt_listener)
return PlayableContentFeeder.PlayableContentFeeder.LoadedStream( return PlayableContentFeeder.PlayableContentFeeder.LoadedStream(
episode, episode,
streamer, streamer,
None, None,
PlayableContentFeeder.PlayableContentFeeder.Metrics(None, False, -1), PlayableContentFeeder.PlayableContentFeeder.Metrics(
None, False, -1),
) )
@staticmethod @staticmethod
@@ -102,6 +102,5 @@ class CdnFeedHelper:
streamer, streamer,
normalization_data, normalization_data,
PlayableContentFeeder.PlayableContentFeeder.Metrics( PlayableContentFeeder.PlayableContentFeeder.Metrics(
file.file_id, False, audio_key_time file.file_id, False, audio_key_time),
),
) )

View File

@@ -37,9 +37,9 @@ class CdnManager:
def get_head(self, file_id: bytes): def get_head(self, file_id: bytes):
resp = self._session.client().get( resp = self._session.client().get(
self._session.get_user_attribute( self._session.get_user_attribute(
"head-files-url", "https://heads-fa.spotify.com/head/{file_id}" "head-files-url",
).replace("{file_id}", Utils.bytes_to_hex(file_id)) "https://heads-fa.spotify.com/head/{file_id}").replace(
) "{file_id}", Utils.bytes_to_hex(file_id)))
if resp.status_code != 200: if resp.status_code != 200:
raise IOError("{}".format(resp.status_code)) raise IOError("{}".format(resp.status_code))
@@ -50,9 +50,9 @@ class CdnManager:
return body return body
def stream_external_episode( def stream_external_episode(self, episode: Metadata.Episode,
self, episode: Metadata.Episode, external_url: str, halt_listener: HaltListener external_url: str,
): halt_listener: HaltListener):
return CdnManager.Streamer( return CdnManager.Streamer(
self._session, self._session,
StreamId(episode), StreamId(episode),
@@ -84,8 +84,7 @@ class CdnManager:
resp = self._session.api().send( resp = self._session.api().send(
"GET", "GET",
"/storage-resolve/files/audio/interactive/{}".format( "/storage-resolve/files/audio/interactive/{}".format(
Utils.bytes_to_hex(file_id) Utils.bytes_to_hex(file_id)),
),
None, None,
None, None,
) )
@@ -101,13 +100,11 @@ class CdnManager:
proto.ParseFromString(body) proto.ParseFromString(body)
if proto.result == StorageResolve.StorageResolveResponse.Result.CDN: if proto.result == StorageResolve.StorageResolveResponse.Result.CDN:
url = random.choice(proto.cdnurl) url = random.choice(proto.cdnurl)
self._LOGGER.debug( self._LOGGER.debug("Fetched CDN url for {}: {}".format(
"Fetched CDN url for {}: {}".format(Utils.bytes_to_hex(file_id), url) Utils.bytes_to_hex(file_id), url))
)
return url return url
raise CdnManager.CdnException( raise CdnManager.CdnException(
"Could not retrieve CDN url! result: {}".format(proto.result) "Could not retrieve CDN url! result: {}".format(proto.result))
)
class CdnException(Exception): class CdnException(Exception):
pass pass
@@ -167,8 +164,7 @@ class CdnManager:
if expire_at is None: if expire_at is None:
self._expiration = -1 self._expiration = -1
self._cdnManager._LOGGER.warning( self._cdnManager._LOGGER.warning(
"Invalid __token__ in CDN url: {}".format(url) "Invalid __token__ in CDN url: {}".format(url))
)
return return
self._expiration = expire_at * 1000 self._expiration = expire_at * 1000
@@ -178,10 +174,8 @@ class CdnManager:
except ValueError: except ValueError:
self._expiration = -1 self._expiration = -1
self._cdnManager._LOGGER.warning( self._cdnManager._LOGGER.warning(
"Couldn't extract expiration, invalid parameter in CDN url: ".format( "Couldn't extract expiration, invalid parameter in CDN url: "
url .format(url))
)
)
return return
self._expiration = int(token_url.query[:i]) * 1000 self._expiration = int(token_url.query[:i]) * 1000
@@ -190,8 +184,8 @@ class CdnManager:
self._expiration = -1 self._expiration = -1
class Streamer( class Streamer(
GeneralAudioStream.GeneralAudioStream, GeneralAudioStream.GeneralAudioStream,
GeneralWritableStream.GeneralWritableStream, GeneralWritableStream.GeneralWritableStream,
): ):
_session: Session = None _session: Session = None
_streamId: StreamId = None _streamId: StreamId = None
@@ -224,38 +218,39 @@ class CdnManager:
self._cdnUrl = cdn_url self._cdnUrl = cdn_url
self._haltListener = halt_listener self._haltListener = halt_listener
resp = self.request(range_start=0, range_end=ChannelManager.CHUNK_SIZE - 1) resp = self.request(range_start=0,
range_end=ChannelManager.CHUNK_SIZE - 1)
content_range = resp._headers.get("Content-Range") content_range = resp._headers.get("Content-Range")
if content_range is None: if content_range is None:
raise IOError("Missing Content-Range header!") raise IOError("Missing Content-Range header!")
split = Utils.split(content_range, "/") split = Utils.split(content_range, "/")
self._size = int(split[1]) self._size = int(split[1])
self._chunks = int(math.ceil(self._size / ChannelManager.CHUNK_SIZE)) self._chunks = int(
math.ceil(self._size / ChannelManager.CHUNK_SIZE))
first_chunk = resp._buffer first_chunk = resp._buffer
self._available = [False for _ in range(self._chunks)] self._available = [False for _ in range(self._chunks)]
self._requested = [False for _ in range(self._chunks)] self._requested = [False for _ in range(self._chunks)]
self._buffer = [bytearray() for _ in range(self._chunks)] self._buffer = [bytearray() for _ in range(self._chunks)]
self._internalStream = CdnManager.Streamer.InternalStream(self, False) self._internalStream = CdnManager.Streamer.InternalStream(
self, False)
self._requested[0] = True self._requested[0] = True
self.write_chunk(first_chunk, 0, False) self.write_chunk(first_chunk, 0, False)
def write_chunk(self, chunk: bytes, chunk_index: int, cached: bool) -> None: def write_chunk(self, chunk: bytes, chunk_index: int,
cached: bool) -> None:
if self._internalStream.is_closed(): if self._internalStream.is_closed():
return return
self._session._LOGGER.debug( self._session._LOGGER.debug(
"Chunk {}/{} completed, cached: {}, stream: {}".format( "Chunk {}/{} completed, cached: {}, stream: {}".format(
chunk_index + 1, self._chunks, cached, self.describe() chunk_index + 1, self._chunks, cached, self.describe()))
)
)
self._buffer[chunk_index] = self._audioDecrypt.decrypt_chunk( self._buffer[chunk_index] = self._audioDecrypt.decrypt_chunk(
chunk_index, chunk chunk_index, chunk)
)
self._internalStream.notify_chunk_available(chunk_index) self._internalStream.notify_chunk_available(chunk_index)
def stream(self) -> AbsChunkedInputStream: def stream(self) -> AbsChunkedInputStream:
@@ -266,7 +261,8 @@ class CdnManager:
def describe(self) -> str: def describe(self) -> str:
if self._streamId.is_episode(): if self._streamId.is_episode():
return "episode_gid: {}".format(self._streamId.get_episode_gid()) return "episode_gid: {}".format(
self._streamId.get_episode_gid())
return "file_id: {}".format(self._streamId.get_file_id()) return "file_id: {}".format(self._streamId.get_file_id())
def decrypt_time_ms(self) -> int: def decrypt_time_ms(self) -> int:
@@ -276,9 +272,10 @@ class CdnManager:
resp = self.request(index) resp = self.request(index)
self.write_chunk(resp._buffer, index, False) self.write_chunk(resp._buffer, index, False)
def request( def request(self,
self, chunk: int = None, range_start: int = None, range_end: int = None chunk: int = None,
) -> CdnManager.InternalResponse: range_start: int = None,
range_end: int = None) -> CdnManager.InternalResponse:
if chunk is None and range_start is None and range_end is None: if chunk is None and range_start is None and range_end is None:
raise TypeError() raise TypeError()
@@ -288,7 +285,9 @@ class CdnManager:
resp = self._session.client().get( resp = self._session.client().get(
self._cdnUrl._url, self._cdnUrl._url,
headers={"Range": "bytes={}-{}".format(range_start, range_end)}, headers={
"Range": "bytes={}-{}".format(range_start, range_end)
},
) )
if resp.status_code != 206: if resp.status_code != 206:
@@ -324,21 +323,16 @@ class CdnManager:
def request_chunk_from_stream(self, index: int) -> None: def request_chunk_from_stream(self, index: int) -> None:
self.streamer._executorService.submit( self.streamer._executorService.submit(
lambda: self.streamer.request_chunk(index) lambda: self.streamer.request_chunk(index))
)
def stream_read_halted(self, chunk: int, _time: int) -> None: def stream_read_halted(self, chunk: int, _time: int) -> None:
if self.streamer._haltListener is not None: if self.streamer._haltListener is not None:
self.streamer._executorService.submit( self.streamer._executorService.submit(
lambda: self.streamer._haltListener.stream_read_halted( lambda: self.streamer._haltListener.stream_read_halted(
chunk, _time chunk, _time))
)
)
def stream_read_resumed(self, chunk: int, _time: int) -> None: def stream_read_resumed(self, chunk: int, _time: int) -> None:
if self.streamer._haltListener is not None: if self.streamer._haltListener is not None:
self.streamer._executorService.submit( self.streamer._executorService.submit(
lambda: self.streamer._haltListener.stream_read_resumed( lambda: self.streamer._haltListener.
chunk, _time stream_read_resumed(chunk, _time))
)
)

File diff suppressed because it is too large Load Diff