Restyled by yapf
This commit is contained in:
@@ -22,7 +22,9 @@ class AbsChunkedInputStream(InputStream, HaltListener):
|
||||
_decoded_length: int = 0
|
||||
|
||||
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
|
||||
|
||||
def is_closed(self) -> bool:
|
||||
@@ -107,13 +109,10 @@ class AbsChunkedInputStream(InputStream, HaltListener):
|
||||
self.request_chunk_from_stream(chunk)
|
||||
self.requested_chunks()[chunk] = True
|
||||
|
||||
for i in range(
|
||||
chunk + 1, min(self.chunks() - 1, chunk + self.preload_ahead) + 1
|
||||
):
|
||||
if (
|
||||
self.requested_chunks()[i]
|
||||
and self.retries[i] < self.preload_chunk_retries
|
||||
):
|
||||
for i in range(chunk + 1,
|
||||
min(self.chunks() - 1, chunk + self.preload_ahead) + 1):
|
||||
if (self.requested_chunks()[i]
|
||||
and self.retries[i] < self.preload_chunk_retries):
|
||||
self.request_chunk_from_stream(i)
|
||||
self.requested_chunks()[chunk] = True
|
||||
|
||||
@@ -147,7 +146,10 @@ class AbsChunkedInputStream(InputStream, HaltListener):
|
||||
|
||||
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:
|
||||
return self.internal_read()
|
||||
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!")
|
||||
|
||||
if offset < 0 or length < 0 or length > len(b) - offset:
|
||||
raise IndexError(
|
||||
"offset: {}, length: {}, buffer: {}".format(offset, length, len(b))
|
||||
)
|
||||
raise IndexError("offset: {}, length: {}, buffer: {}".format(
|
||||
offset, length, len(b)))
|
||||
elif length == 0:
|
||||
return 0
|
||||
|
||||
@@ -174,7 +175,8 @@ class AbsChunkedInputStream(InputStream, HaltListener):
|
||||
self.check_availability(chunk, True, False)
|
||||
|
||||
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
|
||||
self._pos += copy
|
||||
|
||||
@@ -222,5 +224,4 @@ class AbsChunkedInputStream(InputStream, HaltListener):
|
||||
@staticmethod
|
||||
def from_stream_error(stream_error: int):
|
||||
return AbsChunkedInputStream.ChunkException(
|
||||
"Failed due to stream error, code: {}".format(stream_error)
|
||||
)
|
||||
"Failed due to stream error, code: {}".format(stream_error))
|
||||
|
||||
@@ -26,7 +26,10 @@ class AudioKeyManager(PacketsReceiver):
|
||||
def __init__(self, session: Session):
|
||||
self._session = session
|
||||
|
||||
def get_audio_key(self, gid: bytes, file_id: bytes, retry: bool = True) -> bytes:
|
||||
def get_audio_key(self,
|
||||
gid: bytes,
|
||||
file_id: bytes,
|
||||
retry: bool = True) -> bytes:
|
||||
seq: int
|
||||
with self._seqHolderLock:
|
||||
seq = self._seqHolder
|
||||
@@ -49,9 +52,8 @@ class AudioKeyManager(PacketsReceiver):
|
||||
return self.get_audio_key(gid, file_id, False)
|
||||
raise RuntimeError(
|
||||
"Failed fetching audio key! gid: {}, fileId: {}".format(
|
||||
Utils.Utils.bytes_to_hex(gid), Utils.Utils.bytes_to_hex(file_id)
|
||||
)
|
||||
)
|
||||
Utils.Utils.bytes_to_hex(gid),
|
||||
Utils.Utils.bytes_to_hex(file_id)))
|
||||
|
||||
return key
|
||||
|
||||
@@ -61,7 +63,8 @@ class AudioKeyManager(PacketsReceiver):
|
||||
|
||||
callback = self._callbacks.get(seq)
|
||||
if callback is None:
|
||||
self._LOGGER.warning("Couldn't find callback for seq: {}".format(seq))
|
||||
self._LOGGER.warning(
|
||||
"Couldn't find callback for seq: {}".format(seq))
|
||||
return
|
||||
|
||||
if packet.is_cmd(Packet.Type.aes_key):
|
||||
@@ -73,9 +76,7 @@ class AudioKeyManager(PacketsReceiver):
|
||||
else:
|
||||
self._LOGGER.warning(
|
||||
"Couldn't handle packet, cmd: {}, length: {}".format(
|
||||
packet.cmd, len(packet.payload)
|
||||
)
|
||||
)
|
||||
packet.cmd, len(packet.payload)))
|
||||
|
||||
class Callback:
|
||||
def key(self, key: bytes) -> None:
|
||||
@@ -99,15 +100,15 @@ class AudioKeyManager(PacketsReceiver):
|
||||
|
||||
def error(self, code: int) -> None:
|
||||
self._audioKeyManager._LOGGER.fatal(
|
||||
"Audio key error, code: {}".format(code)
|
||||
)
|
||||
"Audio key error, code: {}".format(code))
|
||||
with self.reference_lock:
|
||||
self.reference.put(None)
|
||||
self.reference_lock.notify_all()
|
||||
|
||||
def wait_response(self) -> bytes:
|
||||
with self.reference_lock:
|
||||
self.reference_lock.wait(AudioKeyManager._AUDIO_KEY_REQUEST_TIMEOUT)
|
||||
self.reference_lock.wait(
|
||||
AudioKeyManager._AUDIO_KEY_REQUEST_TIMEOUT)
|
||||
return self.reference.get(block=False)
|
||||
|
||||
class AesKeyException(IOError):
|
||||
|
||||
@@ -20,8 +20,7 @@ class PlayableContentFeeder:
|
||||
_LOGGER: logging = logging.getLogger(__name__)
|
||||
STORAGE_RESOLVE_INTERACTIVE: str = "/storage-resolve/files/audio/interactive/{}"
|
||||
STORAGE_RESOLVE_INTERACTIVE_PREFETCH: str = (
|
||||
"/storage-resolve/files/audio/interactive_prefetch/{}"
|
||||
)
|
||||
"/storage-resolve/files/audio/interactive_prefetch/{}")
|
||||
session: Session
|
||||
|
||||
def __init__(self, session: Session):
|
||||
@@ -45,20 +44,17 @@ class PlayableContentFeeder:
|
||||
halt_listener: HaltListener,
|
||||
):
|
||||
if type(playable_id) is TrackId:
|
||||
return self.load_track(
|
||||
playable_id, audio_quality_picker, preload, halt_listener
|
||||
)
|
||||
return self.load_track(playable_id, audio_quality_picker, preload,
|
||||
halt_listener)
|
||||
|
||||
def resolve_storage_interactive(
|
||||
self, file_id: bytes, preload: bool
|
||||
) -> StorageResolve.StorageResolveResponse:
|
||||
self, file_id: bytes,
|
||||
preload: bool) -> StorageResolve.StorageResolveResponse:
|
||||
resp = self.session.api().send(
|
||||
"GET",
|
||||
(
|
||||
self.STORAGE_RESOLVE_INTERACTIVE_PREFETCH
|
||||
if preload
|
||||
else self.STORAGE_RESOLVE_INTERACTIVE
|
||||
).format(Utils.bytes_to_hex(file_id)),
|
||||
(self.STORAGE_RESOLVE_INTERACTIVE_PREFETCH
|
||||
if preload else self.STORAGE_RESOLVE_INTERACTIVE).format(
|
||||
Utils.bytes_to_hex(file_id)),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
@@ -81,7 +77,8 @@ class PlayableContentFeeder:
|
||||
halt_listener: HaltListener,
|
||||
):
|
||||
if type(track_id_or_track) is TrackId:
|
||||
original = self.session.api().get_metadata_4_track(track_id_or_track)
|
||||
original = self.session.api().get_metadata_4_track(
|
||||
track_id_or_track)
|
||||
track = self.pick_alternative_if_necessary(original)
|
||||
if track is None:
|
||||
raise
|
||||
@@ -89,7 +86,8 @@ class PlayableContentFeeder:
|
||||
track = track_id_or_track
|
||||
file = audio_quality_picker.get_file(track.file)
|
||||
if file is None:
|
||||
self._LOGGER.fatal("Couldn't find any suitable audio file, available")
|
||||
self._LOGGER.fatal(
|
||||
"Couldn't find any suitable audio file, available")
|
||||
raise
|
||||
|
||||
return self.load_stream(file, track, None, preload, halt_listener)
|
||||
@@ -108,12 +106,10 @@ class PlayableContentFeeder:
|
||||
resp = self.resolve_storage_interactive(file.file_id, preload)
|
||||
if resp.result == StorageResolve.StorageResolveResponse.Result.CDN:
|
||||
if track is not None:
|
||||
return CdnFeedHelper.load_track(
|
||||
self.session, track, file, resp, preload, halt_lister
|
||||
)
|
||||
return CdnFeedHelper.load_episode(
|
||||
self.session, episode, file, resp, preload, halt_lister
|
||||
)
|
||||
return CdnFeedHelper.load_track(self.session, track, file,
|
||||
resp, preload, halt_lister)
|
||||
return CdnFeedHelper.load_episode(self.session, episode, file,
|
||||
resp, preload, halt_lister)
|
||||
elif resp.result == StorageResolve.StorageResolveResponse.Result.STORAGE:
|
||||
if track is None:
|
||||
# return StorageFeedHelper
|
||||
@@ -156,10 +152,10 @@ class PlayableContentFeeder:
|
||||
preloaded_audio_key: bool
|
||||
audio_key_time: int
|
||||
|
||||
def __init__(
|
||||
self, file_id: bytes, preloaded_audio_key: bool, audio_key_time: int
|
||||
):
|
||||
self.file_id = None if file_id is None else Utils.bytes_to_hex(file_id)
|
||||
def __init__(self, file_id: bytes, preloaded_audio_key: bool,
|
||||
audio_key_time: int):
|
||||
self.file_id = None if file_id is None else Utils.bytes_to_hex(
|
||||
file_id)
|
||||
self.preloaded_audio_key = preloaded_audio_key
|
||||
self.audio_key_time = audio_key_time
|
||||
|
||||
|
||||
@@ -40,7 +40,8 @@ class CdnFeedHelper:
|
||||
|
||||
streamer = session.cdn().stream_file(file, key, url, halt_listener)
|
||||
input_stream = streamer.stream()
|
||||
normalization_data = NormalizationData.NormalizationData.read(input_stream)
|
||||
normalization_data = NormalizationData.NormalizationData.read(
|
||||
input_stream)
|
||||
if input_stream.skip(0xA7) != 0xA7:
|
||||
raise IOError("Couldn't skip 0xa7 bytes!")
|
||||
return PlayableContentFeeder.PlayableContentFeeder.LoadedStream(
|
||||
@@ -48,13 +49,13 @@ class CdnFeedHelper:
|
||||
streamer,
|
||||
normalization_data,
|
||||
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
|
||||
def load_episode_external(
|
||||
session: Session, episode: Metadata.Episode, halt_listener: HaltListener
|
||||
session: Session, episode: Metadata.Episode,
|
||||
halt_listener: HaltListener
|
||||
) -> PlayableContentFeeder.PlayableContentFeeder.LoadedStream:
|
||||
resp = session.client().head(episode.external_url)
|
||||
|
||||
@@ -62,18 +63,17 @@ class CdnFeedHelper:
|
||||
CdnFeedHelper._LOGGER.warning("Couldn't resolve redirect!")
|
||||
|
||||
url = resp.url
|
||||
CdnFeedHelper._LOGGER.debug(
|
||||
"Fetched external url for {}: {}".format(
|
||||
Utils.Utils.bytes_to_hex(episode.gid), url
|
||||
)
|
||||
)
|
||||
CdnFeedHelper._LOGGER.debug("Fetched external url for {}: {}".format(
|
||||
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(
|
||||
episode,
|
||||
streamer,
|
||||
None,
|
||||
PlayableContentFeeder.PlayableContentFeeder.Metrics(None, False, -1),
|
||||
PlayableContentFeeder.PlayableContentFeeder.Metrics(
|
||||
None, False, -1),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -102,6 +102,5 @@ class CdnFeedHelper:
|
||||
streamer,
|
||||
normalization_data,
|
||||
PlayableContentFeeder.PlayableContentFeeder.Metrics(
|
||||
file.file_id, False, audio_key_time
|
||||
),
|
||||
file.file_id, False, audio_key_time),
|
||||
)
|
||||
|
||||
@@ -37,9 +37,9 @@ class CdnManager:
|
||||
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))
|
||||
)
|
||||
"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))
|
||||
@@ -50,9 +50,9 @@ class CdnManager:
|
||||
|
||||
return body
|
||||
|
||||
def stream_external_episode(
|
||||
self, episode: Metadata.Episode, external_url: str, halt_listener: HaltListener
|
||||
):
|
||||
def stream_external_episode(self, episode: Metadata.Episode,
|
||||
external_url: str,
|
||||
halt_listener: HaltListener):
|
||||
return CdnManager.Streamer(
|
||||
self._session,
|
||||
StreamId(episode),
|
||||
@@ -84,8 +84,7 @@ class CdnManager:
|
||||
resp = self._session.api().send(
|
||||
"GET",
|
||||
"/storage-resolve/files/audio/interactive/{}".format(
|
||||
Utils.bytes_to_hex(file_id)
|
||||
),
|
||||
Utils.bytes_to_hex(file_id)),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
@@ -101,13 +100,11 @@ class CdnManager:
|
||||
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)
|
||||
)
|
||||
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)
|
||||
)
|
||||
"Could not retrieve CDN url! result: {}".format(proto.result))
|
||||
|
||||
class CdnException(Exception):
|
||||
pass
|
||||
@@ -161,14 +158,13 @@ class CdnManager:
|
||||
continue
|
||||
|
||||
if s[0][:i] == "exp":
|
||||
expire_at = int(s[0][i + 1 :])
|
||||
expire_at = int(s[0][i + 1:])
|
||||
break
|
||||
|
||||
if expire_at is None:
|
||||
self._expiration = -1
|
||||
self._cdnManager._LOGGER.warning(
|
||||
"Invalid __token__ in CDN url: {}".format(url)
|
||||
)
|
||||
"Invalid __token__ in CDN url: {}".format(url))
|
||||
return
|
||||
|
||||
self._expiration = expire_at * 1000
|
||||
@@ -178,10 +174,8 @@ class CdnManager:
|
||||
except ValueError:
|
||||
self._expiration = -1
|
||||
self._cdnManager._LOGGER.warning(
|
||||
"Couldn't extract expiration, invalid parameter in CDN url: ".format(
|
||||
url
|
||||
)
|
||||
)
|
||||
"Couldn't extract expiration, invalid parameter in CDN url: "
|
||||
.format(url))
|
||||
return
|
||||
|
||||
self._expiration = int(token_url.query[:i]) * 1000
|
||||
@@ -190,8 +184,8 @@ class CdnManager:
|
||||
self._expiration = -1
|
||||
|
||||
class Streamer(
|
||||
GeneralAudioStream.GeneralAudioStream,
|
||||
GeneralWritableStream.GeneralWritableStream,
|
||||
GeneralAudioStream.GeneralAudioStream,
|
||||
GeneralWritableStream.GeneralWritableStream,
|
||||
):
|
||||
_session: Session = None
|
||||
_streamId: StreamId = None
|
||||
@@ -224,38 +218,39 @@ class CdnManager:
|
||||
self._cdnUrl = cdn_url
|
||||
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")
|
||||
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))
|
||||
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._internalStream = CdnManager.Streamer.InternalStream(
|
||||
self, False)
|
||||
|
||||
self._requested[0] = True
|
||||
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():
|
||||
return
|
||||
|
||||
self._session._LOGGER.debug(
|
||||
"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(
|
||||
chunk_index, chunk
|
||||
)
|
||||
chunk_index, chunk)
|
||||
self._internalStream.notify_chunk_available(chunk_index)
|
||||
|
||||
def stream(self) -> AbsChunkedInputStream:
|
||||
@@ -266,7 +261,8 @@ class CdnManager:
|
||||
|
||||
def describe(self) -> str:
|
||||
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())
|
||||
|
||||
def decrypt_time_ms(self) -> int:
|
||||
@@ -276,9 +272,10 @@ class CdnManager:
|
||||
resp = self.request(index)
|
||||
self.write_chunk(resp._buffer, index, False)
|
||||
|
||||
def request(
|
||||
self, chunk: int = None, range_start: int = None, range_end: int = None
|
||||
) -> CdnManager.InternalResponse:
|
||||
def request(self,
|
||||
chunk: int = None,
|
||||
range_start: int = None,
|
||||
range_end: int = None) -> CdnManager.InternalResponse:
|
||||
if chunk is None and range_start is None and range_end is None:
|
||||
raise TypeError()
|
||||
|
||||
@@ -288,7 +285,9 @@ class CdnManager:
|
||||
|
||||
resp = self._session.client().get(
|
||||
self._cdnUrl._url,
|
||||
headers={"Range": "bytes={}-{}".format(range_start, range_end)},
|
||||
headers={
|
||||
"Range": "bytes={}-{}".format(range_start, range_end)
|
||||
},
|
||||
)
|
||||
|
||||
if resp.status_code != 206:
|
||||
@@ -324,21 +323,16 @@ class CdnManager:
|
||||
|
||||
def request_chunk_from_stream(self, index: int) -> None:
|
||||
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:
|
||||
if self.streamer._haltListener is not None:
|
||||
self.streamer._executorService.submit(
|
||||
lambda: self.streamer._haltListener.stream_read_halted(
|
||||
chunk, _time
|
||||
)
|
||||
)
|
||||
chunk, _time))
|
||||
|
||||
def stream_read_resumed(self, chunk: int, _time: int) -> None:
|
||||
if self.streamer._haltListener is not None:
|
||||
self.streamer._executorService.submit(
|
||||
lambda: self.streamer._haltListener.stream_read_resumed(
|
||||
chunk, _time
|
||||
)
|
||||
)
|
||||
lambda: self.streamer._haltListener.
|
||||
stream_read_resumed(chunk, _time))
|
||||
|
||||
@@ -13,35 +13,28 @@ class AudioQuality(enum.Enum):
|
||||
|
||||
@staticmethod
|
||||
def get_quality(audio_format: AudioFile.Format) -> AudioQuality:
|
||||
if (
|
||||
audio_format == AudioFile.MP3_96
|
||||
or audio_format == AudioFile.OGG_VORBIS_96
|
||||
or audio_format == AudioFile.AAC_24_NORM
|
||||
):
|
||||
if (audio_format == AudioFile.MP3_96
|
||||
or audio_format == AudioFile.OGG_VORBIS_96
|
||||
or audio_format == AudioFile.AAC_24_NORM):
|
||||
return AudioQuality.NORMAL
|
||||
if (
|
||||
audio_format == AudioFile.MP3_160
|
||||
or audio_format == AudioFile.MP3_160_ENC
|
||||
or audio_format == AudioFile.OGG_VORBIS_160
|
||||
or audio_format == AudioFile.AAC_24
|
||||
):
|
||||
if (audio_format == AudioFile.MP3_160
|
||||
or audio_format == AudioFile.MP3_160_ENC
|
||||
or audio_format == AudioFile.OGG_VORBIS_160
|
||||
or audio_format == AudioFile.AAC_24):
|
||||
return AudioQuality.HIGH
|
||||
if (
|
||||
audio_format == AudioFile.MP3_320
|
||||
or audio_format == AudioFile.MP3_256
|
||||
or audio_format == AudioFile.OGG_VORBIS_320
|
||||
or audio_format == AudioFile.AAC_48
|
||||
):
|
||||
if (audio_format == AudioFile.MP3_320
|
||||
or audio_format == AudioFile.MP3_256
|
||||
or audio_format == AudioFile.OGG_VORBIS_320
|
||||
or audio_format == AudioFile.AAC_48):
|
||||
return AudioQuality.VERY_HIGH
|
||||
raise RuntimeError("Unknown format: {}".format(format))
|
||||
|
||||
def get_matches(self, files: typing.List[AudioFile]) -> typing.List[AudioFile]:
|
||||
def get_matches(self,
|
||||
files: typing.List[AudioFile]) -> typing.List[AudioFile]:
|
||||
file_list = []
|
||||
for file in files:
|
||||
if (
|
||||
hasattr(file, "format")
|
||||
and AudioQuality.get_quality(file.format) == self
|
||||
):
|
||||
if (hasattr(file, "format")
|
||||
and AudioQuality.get_quality(file.format) == self):
|
||||
file_list.append(file)
|
||||
|
||||
return file_list
|
||||
|
||||
@@ -8,26 +8,24 @@ from librespot.audio.storage import ChannelManager
|
||||
|
||||
|
||||
class AesAudioDecrypt(AudioDecrypt):
|
||||
audio_aes_iv = bytes(
|
||||
[
|
||||
0x72,
|
||||
0xE0,
|
||||
0x67,
|
||||
0xFB,
|
||||
0xDD,
|
||||
0xCB,
|
||||
0xCF,
|
||||
0x77,
|
||||
0xEB,
|
||||
0xE8,
|
||||
0xBC,
|
||||
0x64,
|
||||
0x3F,
|
||||
0x63,
|
||||
0x0D,
|
||||
0x93,
|
||||
]
|
||||
)
|
||||
audio_aes_iv = bytes([
|
||||
0x72,
|
||||
0xE0,
|
||||
0x67,
|
||||
0xFB,
|
||||
0xDD,
|
||||
0xCB,
|
||||
0xCF,
|
||||
0x77,
|
||||
0xEB,
|
||||
0xE8,
|
||||
0xBC,
|
||||
0x64,
|
||||
0x3F,
|
||||
0x63,
|
||||
0x0D,
|
||||
0x93,
|
||||
])
|
||||
iv_int = int.from_bytes(audio_aes_iv, "big")
|
||||
iv_diff = 0x100
|
||||
cipher = None
|
||||
@@ -50,14 +48,12 @@ class AesAudioDecrypt(AudioDecrypt):
|
||||
)
|
||||
|
||||
count = min(4096, len(buffer) - i)
|
||||
decrypted_buffer = cipher.decrypt(buffer[i : i + count])
|
||||
decrypted_buffer = cipher.decrypt(buffer[i:i + count])
|
||||
new_buffer += decrypted_buffer
|
||||
if count != len(decrypted_buffer):
|
||||
raise RuntimeError(
|
||||
"Couldn't process all data, actual: {}, expected: {}".format(
|
||||
len(decrypted_buffer), count
|
||||
)
|
||||
)
|
||||
"Couldn't process all data, actual: {}, expected: {}".
|
||||
format(len(decrypted_buffer), count))
|
||||
|
||||
iv += self.iv_diff
|
||||
|
||||
@@ -67,8 +63,5 @@ class AesAudioDecrypt(AudioDecrypt):
|
||||
return new_buffer
|
||||
|
||||
def decrypt_time_ms(self):
|
||||
return (
|
||||
0
|
||||
if self.decrypt_count == 0
|
||||
else int((self.decrypt_total_time / self.decrypt_count) / 1000000)
|
||||
)
|
||||
return (0 if self.decrypt_count == 0 else int(
|
||||
(self.decrypt_total_time / self.decrypt_count) / 1000000))
|
||||
|
||||
@@ -7,5 +7,6 @@ if typing.TYPE_CHECKING:
|
||||
|
||||
|
||||
class AudioQualityPicker:
|
||||
def get_file(self, files: typing.List[Metadata.AudioFile]) -> Metadata.AudioFile:
|
||||
def get_file(self,
|
||||
files: typing.List[Metadata.AudioFile]) -> Metadata.AudioFile:
|
||||
pass
|
||||
|
||||
@@ -24,8 +24,7 @@ class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
|
||||
_seqHolder: int = 0
|
||||
_seqHolderLock: threading.Condition = threading.Condition()
|
||||
_executorService: concurrent.futures.ThreadPoolExecutor = (
|
||||
concurrent.futures.ThreadPoolExecutor()
|
||||
)
|
||||
concurrent.futures.ThreadPoolExecutor())
|
||||
_session: Session = None
|
||||
|
||||
def __init__(self, session: Session):
|
||||
@@ -58,9 +57,7 @@ class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
|
||||
if channel is None:
|
||||
self._LOGGER.warning(
|
||||
"Couldn't find channel, id: {}, received: {}".format(
|
||||
chunk_id, len(packet.payload)
|
||||
)
|
||||
)
|
||||
chunk_id, len(packet.payload)))
|
||||
return
|
||||
|
||||
channel._add_to_queue(payload)
|
||||
@@ -70,18 +67,14 @@ class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
|
||||
if channel is None:
|
||||
self._LOGGER.warning(
|
||||
"Dropping channel error, id: {}, code: {}".format(
|
||||
chunk_id, payload.read_short()
|
||||
)
|
||||
)
|
||||
chunk_id, payload.read_short()))
|
||||
return
|
||||
|
||||
channel.stream_error(payload.read_short())
|
||||
else:
|
||||
self._LOGGER.warning(
|
||||
"Couldn't handle packet, cmd: {}, payload: {}".format(
|
||||
packet.cmd, Utils.Utils.bytes_to_hex(packet.payload)
|
||||
)
|
||||
)
|
||||
packet.cmd, Utils.Utils.bytes_to_hex(packet.payload)))
|
||||
|
||||
def close(self) -> None:
|
||||
self._executorService.shutdown()
|
||||
@@ -95,9 +88,8 @@ class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
|
||||
_buffer: BytesOutputStream = BytesOutputStream()
|
||||
_header: bool = True
|
||||
|
||||
def __init__(
|
||||
self, channel_manager: ChannelManager, file: AudioFile, chunk_index: int
|
||||
):
|
||||
def __init__(self, channel_manager: ChannelManager, file: AudioFile,
|
||||
chunk_index: int):
|
||||
self._channelManager = channel_manager
|
||||
self._file = file
|
||||
self._chunkIndex = chunk_index
|
||||
@@ -106,18 +98,17 @@ class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
|
||||
self._channelManager._seqHolder += 1
|
||||
|
||||
self._channelManager._executorService.submit(
|
||||
lambda: ChannelManager.Channel.Handler(self)
|
||||
)
|
||||
lambda: ChannelManager.Channel.Handler(self))
|
||||
|
||||
def _handle(self, payload: BytesInputStream) -> bool:
|
||||
if len(payload.buffer) == 0:
|
||||
if not self._header:
|
||||
self._file.write_chunk(
|
||||
bytearray(payload.buffer), self._chunkIndex, False
|
||||
)
|
||||
self._file.write_chunk(bytearray(payload.buffer),
|
||||
self._chunkIndex, False)
|
||||
return True
|
||||
|
||||
self._channelManager._LOGGER.debug("Received empty chunk, skipping.")
|
||||
self._channelManager._LOGGER.debug(
|
||||
"Received empty chunk, skipping.")
|
||||
return False
|
||||
|
||||
if self._header:
|
||||
@@ -128,9 +119,8 @@ class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
|
||||
break
|
||||
header_id = payload.read_byte()
|
||||
header_data = payload.read(length - 1)
|
||||
self._file.write_header(
|
||||
int.from_bytes(header_id, "big"), bytearray(header_data), False
|
||||
)
|
||||
self._file.write_header(int.from_bytes(header_id, "big"),
|
||||
bytearray(header_data), False)
|
||||
self._header = False
|
||||
else:
|
||||
self._buffer.write(payload.read(len(payload.buffer)))
|
||||
@@ -151,12 +141,11 @@ class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
|
||||
|
||||
def run(self) -> None:
|
||||
self._channel._channelManager._LOGGER.debug(
|
||||
"ChannelManager.Handler is starting"
|
||||
)
|
||||
"ChannelManager.Handler is starting")
|
||||
|
||||
with self._channel._q.all_tasks_done:
|
||||
self._channel._channelManager._channels.pop(self._channel.chunkId)
|
||||
self._channel._channelManager._channels.pop(
|
||||
self._channel.chunkId)
|
||||
|
||||
self._channel._channelManager._LOGGER.debug(
|
||||
"ChannelManager.Handler is shutting down"
|
||||
)
|
||||
"ChannelManager.Handler is shutting down")
|
||||
|
||||
Reference in New Issue
Block a user