librespot-python/librespot/audio/PlayableContentFeeder.py

164 lines
5.6 KiB
Python
Raw Normal View History

2021-02-24 00:46:59 +01:00
from __future__ import annotations
2021-05-22 03:26:35 +02:00
import logging
import typing
2021-02-24 00:46:59 +01:00
from librespot.audio import GeneralAudioStream, HaltListener, NormalizationData
from librespot.audio.cdn import CdnFeedHelper
from librespot.audio.format import AudioQualityPicker
from librespot.common.Utils import Utils
from librespot.core import Session
2021-05-15 00:07:29 +02:00
from librespot.metadata import PlayableId, TrackId
2021-02-24 00:46:59 +01:00
from librespot.proto import Metadata, StorageResolve
class PlayableContentFeeder:
_LOGGER: logging = logging.getLogger(__name__)
STORAGE_RESOLVE_INTERACTIVE: str = "/storage-resolve/files/audio/interactive/{}"
2021-05-22 03:26:33 +02:00
STORAGE_RESOLVE_INTERACTIVE_PREFETCH: str = (
"/storage-resolve/files/audio/interactive_prefetch/{}"
)
2021-02-24 00:46:59 +01:00
session: Session
def __init__(self, session: Session):
self.session = session
def pick_alternative_if_necessary(self, track: Metadata.Track):
if len(track.file) > 0:
return track
for alt in track.alternative_list:
if len(alt.file) > 0:
pass
return None
2021-05-22 03:26:33 +02:00
def load(
self,
playable_id: PlayableId,
audio_quality_picker: AudioQualityPicker,
preload: bool,
halt_listener: HaltListener,
):
2021-02-24 00:46:59 +01:00
if type(playable_id) is TrackId:
2021-05-22 03:26:33 +02:00
return self.load_track(
playable_id, audio_quality_picker, preload, halt_listener
)
2021-02-24 00:46:59 +01:00
def resolve_storage_interactive(
2021-05-22 03:26:33 +02:00
self, file_id: bytes, preload: bool
) -> StorageResolve.StorageResolveResponse:
2021-02-24 00:46:59 +01:00
resp = self.session.api().send(
2021-05-22 03:26:33 +02:00
"GET",
(
self.STORAGE_RESOLVE_INTERACTIVE_PREFETCH
if preload
else self.STORAGE_RESOLVE_INTERACTIVE
).format(Utils.bytes_to_hex(file_id)),
None,
None,
)
2021-02-24 00:46:59 +01:00
if resp.status_code != 200:
raise RuntimeError(resp.status_code)
body = resp.content
if body is None:
RuntimeError("Response body is empty!")
storage_resolve_response = StorageResolve.StorageResolveResponse()
storage_resolve_response.ParseFromString(body)
return storage_resolve_response
2021-05-22 03:26:33 +02:00
def load_track(
self,
track_id_or_track: typing.Union[TrackId, Metadata.Track],
audio_quality_picker: AudioQualityPicker,
preload: bool,
halt_listener: HaltListener,
):
2021-02-24 00:46:59 +01:00
if type(track_id_or_track) is TrackId:
2021-05-22 03:26:33 +02:00
original = self.session.api().get_metadata_4_track(track_id_or_track)
2021-02-24 00:46:59 +01:00
track = self.pick_alternative_if_necessary(original)
if track is None:
raise
else:
track = track_id_or_track
file = audio_quality_picker.get_file(track.file)
if file is None:
2021-05-22 03:26:33 +02:00
self._LOGGER.fatal("Couldn't find any suitable audio file, available")
2021-02-24 00:46:59 +01:00
raise
return self.load_stream(file, track, None, preload, halt_listener)
2021-05-22 03:26:33 +02:00
def load_stream(
self,
file: Metadata.AudioFile,
track: Metadata.Track,
episode: Metadata.Episode,
preload: bool,
halt_lister: HaltListener,
):
2021-02-24 00:46:59 +01:00
if track is None and episode is None:
raise RuntimeError()
resp = self.resolve_storage_interactive(file.file_id, preload)
if resp.result == StorageResolve.StorageResolveResponse.Result.CDN:
if track is not None:
2021-05-22 03:26:33 +02:00
return CdnFeedHelper.load_track(
self.session, track, file, resp, preload, halt_lister
)
return CdnFeedHelper.load_episode(
self.session, episode, file, resp, preload, halt_lister
)
2021-02-24 00:46:59 +01:00
elif resp.result == StorageResolve.StorageResolveResponse.Result.STORAGE:
if track is None:
# return StorageFeedHelper
pass
elif resp.result == StorageResolve.StorageResolveResponse.Result.RESTRICTED:
raise RuntimeError("Content is restricted!")
elif resp.result == StorageResolve.StorageResolveResponse.Response.UNRECOGNIZED:
raise RuntimeError("Content is unrecognized!")
else:
raise RuntimeError("Unknown result: {}".format(resp.result))
class LoadedStream:
episode: Metadata.Episode
track: Metadata.Track
input_stream: GeneralAudioStream
normalization_data: NormalizationData
metrics: PlayableContentFeeder.Metrics
2021-05-22 03:26:33 +02:00
def __init__(
self,
track_or_episode: typing.Union[Metadata.Track, Metadata.Episode],
input_stream: GeneralAudioStream,
normalization_data: NormalizationData,
metrics: PlayableContentFeeder.Metrics,
):
2021-02-24 00:46:59 +01:00
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 Metrics:
file_id: str
preloaded_audio_key: bool
audio_key_time: int
2021-05-22 03:26:33 +02:00
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)
2021-02-24 00:46:59 +01:00
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()