Change Directory

This commit is contained in:
kokarare1212
2021-02-25 08:07:17 +09:00
parent 01dae8ada4
commit 8daf68831e
133 changed files with 221 additions and 38 deletions

32
librespot/Version.py Normal file
View File

@@ -0,0 +1,32 @@
from librespot.proto.Keyexchange import *
import platform
class Version:
version = "0.0.1-SNAPSHOT"
@staticmethod
def platform() -> Platform:
if platform.system() == "Windows":
return Platform.PLATFORM_WIN32_X86
elif platform.system() == "Darwin":
return Platform.PLATFORM_OSX_X86
else:
return Platform.PLATFORM_LINUX_X86
pass
@staticmethod
def version_string():
return "librespot-python " + Version.version
@staticmethod
def system_info_string():
return Version.version_string(
) + "; Python " + platform.python_version() + "; " + platform.system()
@staticmethod
def standard_build_info() -> BuildInfo:
return BuildInfo(product=Product.PRODUCT_CLIENT,
product_flags=[ProductFlags.PRODUCT_FLAG_NONE],
platform=Version.platform(),
version=112800721)

0
librespot/__init__.py Normal file
View File

View File

@@ -0,0 +1,231 @@
from librespot.audio.HaltListener import HaltListener
from librespot.audio.storage import ChannelManager
from librespot.standard.InputStream import InputStream
import math
import threading
import time
import typing
class AbsChunkedInputStream(InputStream, HaltListener):
preload_ahead: typing.Final[int] = 3
preload_chunk_retries: typing.Final[int] = 2
max_chunk_tries: typing.Final[int] = 128
wait_lock: threading.Condition = threading.Condition()
retries: list[int]
retry_on_chunk_error: bool
chunk_exception = None
wait_for_chunk: int = -1
_pos: int = 0
_mark: int = 0
closed: bool = False
_decoded_length: int = 0
def __init__(self, retry_on_chunk_error: bool):
self.retries: typing.Final[list[int]] = [
0 for _ in range(self.chunks())
]
self.retry_on_chunk_error = retry_on_chunk_error
def is_closed(self) -> bool:
return self.closed
def buffer(self) -> list[bytearray]:
raise NotImplementedError()
def size(self) -> int:
raise NotImplementedError()
def close(self) -> None:
self.closed = True
with self.wait_lock:
self.wait_lock.notify_all()
def available(self):
return self.size() - self._pos
def mark_supported(self) -> bool:
return True
def mark(self, read_ahead_limit: int) -> None:
self._mark = self._pos
def reset(self) -> None:
self._pos = self._mark
def pos(self) -> int:
return self._pos
def seek(self, where: int) -> None:
if where < 0:
raise TypeError()
if self.closed:
raise IOError("Stream is closed!")
self._pos = where
self.check_availability(int(self._pos / ChannelManager.CHUNK_SIZE),
False, False)
def skip(self, n: int) -> int:
if n < 0:
raise TypeError()
if self.closed:
raise IOError("Stream is closed!")
k = self.size() - self._pos
if n < k:
k = n
self._pos += k
chunk = int(self._pos / ChannelManager.CHUNK_SIZE)
self.check_availability(chunk, False, False)
return k
def requested_chunks(self) -> list[bool]:
raise NotImplementedError()
def available_chunks(self) -> list[bool]:
raise NotImplementedError()
def chunks(self) -> int:
raise NotImplementedError()
def request_chunk_from_stream(self, index: int) -> None:
raise NotImplementedError()
def should_retry(self, chunk: int) -> bool:
if self.retries[chunk] < 1:
return True
if self.retries[chunk] > self.max_chunk_tries:
return False
return self.retry_on_chunk_error
def check_availability(self, chunk: int, wait: bool, halted: bool) -> None:
if halted and not wait:
raise TypeError()
if not self.requested_chunks()[chunk]:
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:
self.request_chunk_from_stream(i)
self.requested_chunks()[chunk] = True
if wait:
if self.available_chunks()[chunk]:
return
retry = False
with self.wait_lock:
if not halted:
self.stream_read_halted(chunk, int(time.time() * 1000))
self.chunk_exception = None
self.wait_for_chunk = chunk
self.wait_lock.wait()
if self.closed:
return
if self.chunk_exception is not None:
if self.should_retry(chunk):
retry = True
else:
raise AbsChunkedInputStream.ChunkException
if not retry:
self.stream_read_halted(chunk, int(time.time() * 1000))
if retry:
time.sleep(math.log10(self.retries[chunk]))
self.check_availability(chunk, True, True)
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()
elif not (b is not None and offset is not None and length is not None):
raise TypeError()
if self.closed:
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)))
elif length == 0:
return 0
if self._pos >= self.size():
return -1
i = 0
while True:
chunk = int(self._pos / ChannelManager.CHUNK_SIZE)
chunk_off = int(self._pos % ChannelManager.CHUNK_SIZE)
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]
i += copy
self._pos += copy
if i == length or self._pos >= self.size():
return i
def internal_read(self) -> int:
if self.closed:
raise IOError("Stream is closed!")
if self._pos >= self.size():
return -1
chunk = int(self._pos / ChannelManager.CHUNK_SIZE)
self.check_availability(chunk, True, False)
b = self.buffer()[chunk][self._pos % ChannelManager.CHUNK_SIZE]
self._pos = self._pos + 1
return b
def notify_chunk_available(self, index: int) -> None:
self.available_chunks()[index] = True
self._decoded_length += len(self.buffer()[index])
with self.wait_lock:
if index == self.wait_for_chunk and not self.closed:
self.wait_for_chunk = -1
self.wait_lock.notify_all()
def notify_chunk_error(self, index: int, ex):
self.available_chunks()[index] = False
self.requested_chunks()[index] = False
self.retries[index] += 1
with self.wait_lock:
if index == self.wait_for_chunk and not self.closed:
self.chunk_exception = ex
self.wait_for_chunk = -1
self.wait_lock.notify_all()
def decoded_length(self):
return self._decoded_length
class ChunkException(IOError):
def __init__(self, cause):
super().__init__(cause)
@staticmethod
def from_stream_error(stream_error: int):
return AbsChunkedInputStream.ChunkException(
"Failed due to stream error, code: {}".format(stream_error))

View File

@@ -0,0 +1,112 @@
from __future__ import annotations
from librespot.common import Utils
from librespot.core import Session
from librespot.core.PacketsReceiver import PacketsReceiver
from librespot.crypto import Packet
from librespot.standard import BytesInputStream, BytesOutputStream
import logging
import queue
import threading
class AudioKeyManager(PacketsReceiver):
_ZERO_SHORT: bytes = bytes([0, 0])
_LOGGER: logging = logging.getLogger(__name__)
_AUDIO_KEY_REQUEST_TIMEOUT: int = 20
_seqHolder: int = 0
_seqHolderLock: threading.Condition = threading.Condition()
_callbacks: dict[int, AudioKeyManager.Callback] = dict()
_session: Session = None
def __init__(self, session: Session):
self._session = session
def get_audio_key(self,
gid: bytes,
file_id: bytes,
retry: bool = True) -> bytes:
seq: int
with self._seqHolderLock:
seq = self._seqHolder
self._seqHolder += 1
out = BytesOutputStream()
out.write(file_id)
out.write(gid)
out.write_int(seq)
out.write(self._ZERO_SHORT)
self._session.send(Packet.Type.request_key, out.buffer)
callback = AudioKeyManager.SyncCallback(self)
self._callbacks[seq] = callback
key = callback.wait_response()
if key is None:
if retry:
return self.get_audio_key(gid, file_id, False)
else:
raise RuntimeError(
"Failed fetching audio key! gid: {}, fileId: {}".format(
Utils.Utils.bytes_to_hex(gid),
Utils.Utils.bytes_to_hex(file_id)))
return key
def dispatch(self, packet: Packet) -> None:
payload = BytesInputStream(packet.payload)
seq = payload.read_int()
callback = self._callbacks.get(seq)
if callback is None:
self._LOGGER.warning(
"Couldn't find callback for seq: {}".format(seq))
return
if packet.is_cmd(Packet.Type.aes_key):
key = payload.read(16)
callback.key(key)
elif packet.is_cmd(Packet.Type.aes_key_error):
code = payload.read_short()
callback.error(code)
else:
self._LOGGER.warning(
"Couldn't handle packet, cmd: {}, length: {}".format(
packet.cmd, len(packet.payload)))
class Callback:
def key(self, key: bytes) -> None:
pass
def error(self, code: int) -> None:
pass
class SyncCallback(Callback):
_audioKeyManager: AudioKeyManager
reference = queue.Queue()
reference_lock = threading.Condition()
def __init__(self, audio_key_manager: AudioKeyManager):
self._audioKeyManager = audio_key_manager
def key(self, key: bytes) -> None:
with self.reference_lock:
self.reference.put(key)
self.reference_lock.notify_all()
def error(self, code: int) -> None:
self._audioKeyManager._LOGGER.fatal(
"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)
return self.reference.get(block=False)
class AesKeyException(IOError):
def __init__(self, ex):
super().__init__(ex)

View File

@@ -0,0 +1,20 @@
from __future__ import annotations
import typing
if typing.TYPE_CHECKING:
from librespot.audio import AbsChunkedInputStream
from librespot.audio.format import SuperAudioFormat
class GeneralAudioStream:
def stream(self) -> AbsChunkedInputStream:
pass
def codec(self) -> SuperAudioFormat:
pass
def describe(self) -> str:
pass
def decrypt_time_ms(self) -> int:
pass

View File

@@ -0,0 +1,3 @@
class GeneralWritableStream:
def write_chunk(self, buffer: bytearray, chunk_index: int, cached: bool):
pass

View File

@@ -0,0 +1,6 @@
class HaltListener:
def stream_read_halted(self, chunk: int, _time: int) -> None:
pass
def stream_read_resumed(self, chunk: int, _time: int):
pass

View File

@@ -0,0 +1,50 @@
from __future__ import annotations
from librespot.standard import BytesInputStream, DataInputStream, InputStream
import logging
import math
class NormalizationData:
_LOGGER: logging = logging.getLogger(__name__)
track_gain_db: float
track_peak: float
album_gain_db: float
album_peak: float
def __init__(self, track_gain_db: float, track_peak: float,
album_gain_db: float, album_peak: float):
self.track_gain_db = track_gain_db
self.track_peak = track_peak
self.album_gain_db = album_gain_db
self.album_peak = album_peak
self._LOGGER.debug(
"Loaded normalization data, track_gain: {}, track_peak: {}, album_gain: {}, album_peak: {}"
.format(track_gain_db, track_peak, album_gain_db, album_peak))
@staticmethod
def read(input_stream: InputStream) -> NormalizationData:
data_input = DataInputStream(input_stream)
data_input.mark(16)
skip_bytes = data_input.skip_bytes(144)
if skip_bytes != 144:
raise IOError()
data = bytearray(4 * 4)
data_input.read_fully(data)
data_input.reset()
buffer = BytesInputStream(data, "<")
return NormalizationData(buffer.read_float(), buffer.read_float(),
buffer.read_float(), buffer.read_float())
def get_factor(self, normalisation_pregain) -> float:
normalisation_factor = float(
math.pow(10, (self.track_gain_db + normalisation_pregain) / 20))
if normalisation_factor * self.track_peak > 1:
self._LOGGER.warning(
"Reducing normalisation factor to prevent clipping. Please add negative pregain to avoid."
)
normalisation_factor = 1 / self.track_peak
return normalisation_factor

View File

@@ -0,0 +1,141 @@
from __future__ import annotations
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
from librespot.metadata.PlayableId import PlayableId
from librespot.metadata.TrackId import TrackId
from librespot.proto import Metadata, StorageResolve
import logging
import typing
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/{}"
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
def load(self, playable_id: PlayableId,
audio_quality_picker: AudioQualityPicker, preload: bool,
halt_listener: HaltListener):
if type(playable_id) is TrackId:
return self.load_track(playable_id, audio_quality_picker, preload,
halt_listener)
def resolve_storage_interactive(
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)), None, None)
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
def load_track(self, track_id_or_track: typing.Union[TrackId,
Metadata.Track],
audio_quality_picker: AudioQualityPicker, preload: bool,
halt_listener: HaltListener):
if type(track_id_or_track) is TrackId:
original = self.session.api().get_metadata_4_track(
track_id_or_track)
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:
self._LOGGER.fatal(
"Couldn't find any suitable audio file, available")
raise
return self.load_stream(file, track, None, preload, halt_listener)
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()
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)
else:
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
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
def __init__(self, track_or_episode: typing.Union[Metadata.Track,
Metadata.Episode],
input_stream: GeneralAudioStream,
normalization_data: NormalizationData,
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 Metrics:
file_id: str
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)
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()

View File

@@ -0,0 +1,30 @@
from librespot.common.Utils import Utils
from librespot.proto import Metadata
class StreamId:
file_id: bytes = None
episode_gid: bytes = None
def __init__(self,
file: Metadata.AudioFile = None,
episode: Metadata.Episode = None):
if file is None and episode is None:
return
if file is not None:
self.file_id = file.file_id
if episode is not None:
self.episode_gid = episode.gid
def get_file_id(self):
if self.file_id is None:
raise RuntimeError("Not a file!")
return Utils.bytes_to_hex(self.file_id)
def is_episode(self):
return self.episode_gid is not None
def get_episode_gid(self):
if self.episode_gid is None:
raise RuntimeError("Not an episode!")
return Utils.bytes_to_hex(self.episode_gid)

View File

@@ -0,0 +1,8 @@
from librespot.audio.AbsChunkedInputStream import AbsChunkedInputStream
from librespot.audio.AudioKeyManager import AudioKeyManager
from librespot.audio.GeneralAudioStream import GeneralAudioStream
from librespot.audio.GeneralWritableStream import GeneralWritableStream
from librespot.audio.HaltListener import HaltListener
from librespot.audio.NormalizationData import NormalizationData
from librespot.audio.PlayableContentFeeder import PlayableContentFeeder
from librespot.audio.StreamId import StreamId

View File

@@ -0,0 +1,85 @@
from __future__ import annotations
from librespot.audio import NormalizationData, PlayableContentFeeder, HaltListener
from librespot.common import Utils
from librespot.core import Session
from librespot.proto import Metadata, StorageResolve
import logging
import random
import time
import typing
class CdnFeedHelper:
_LOGGER: logging = logging.getLogger(__name__)
@staticmethod
def get_url(resp: StorageResolve.StorageResolveResponse) -> str:
return random.choice(resp.cdnurl)
@staticmethod
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.PlayableContentFeeder.LoadedStream:
if type(resp_or_url) is str:
url = resp_or_url
else:
url = CdnFeedHelper.get_url(resp_or_url)
start = int(time.time() * 1000)
key = session.audio_key().get_audio_key(track.gid, file.file_id)
audio_key_time = int(time.time() * 1000) - start
streamer = session.cdn().stream_file(file, key, url, halt_listener)
input_stream = streamer.stream()
normalization_data = NormalizationData.read(input_stream)
if input_stream.skip(0xa7) != 0xa7:
raise IOError("Couldn't skip 0xa7 bytes!")
return PlayableContentFeeder.PlayableContentFeeder.LoadedStream(
track, streamer, normalization_data,
PlayableContentFeeder.PlayableContentFeeder.Metrics(
file.file_id, preload, audio_key_time))
@staticmethod
def load_episode_external(
session: Session, episode: Metadata.Episode,
halt_listener: HaltListener
) -> PlayableContentFeeder.PlayableContentFeeder.LoadedStream:
resp = session.client().head(episode.external_url)
if resp.status_code != 200:
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))
streamer = session.cdn().stream_external_episode(
episode, url, halt_listener)
return PlayableContentFeeder.PlayableContentFeeder.LoadedStream(
episode, streamer, None,
PlayableContentFeeder.PlayableContentFeeder.Metrics(
None, False, -1))
@staticmethod
def load_episode(
session: Session, episode: Metadata.Episode, file: Metadata.AudioFile,
resp_or_url: typing.Union[StorageResolve.StorageResolveResponse,
str], halt_listener: HaltListener
) -> PlayableContentFeeder.PlayableContentFeeder.LoadedStream:
if type(resp_or_url) is str:
url = resp_or_url
else:
url = CdnFeedHelper.get_url(resp_or_url)
start = int(time.time() * 1000)
key = session.audio_key().get_audio_key(episode.gid, file.file_id)
audio_key_time = int(time.time() * 1000) - start
streamer = session.cdn().stream_file(file, key, url, halt_listener)
input_stream = streamer.stream()
normalization_data = NormalizationData.read(input_stream)
if input_stream.skip(0xa7) != 0xa7:
raise IOError("Couldn't skip 0xa7 bytes!")
return PlayableContentFeeder.PlayableContentFeeder.LoadedStream(
episode, streamer, normalization_data,
PlayableContentFeeder.PlayableContentFeeder.Metrics(
file.file_id, False, audio_key_time))

View File

@@ -0,0 +1,307 @@
from __future__ import annotations
from librespot.audio.AbsChunkedInputStream import AbsChunkedInputStream
from librespot.audio import GeneralAudioStream, GeneralWritableStream, StreamId
from librespot.audio.decrypt import AesAudioDecrypt, NoopAudioDecrypt
from librespot.audio.format import SuperAudioFormat
from librespot.audio.storage import ChannelManager
from librespot.common import Utils
from librespot.proto import StorageResolve
import concurrent.futures
import logging
import math
import random
import time
import typing
import urllib.parse
if typing.TYPE_CHECKING:
from librespot.audio.HaltListener import HaltListener
from librespot.audio.decrypt.AudioDecrypt import AudioDecrypt
from librespot.cache.CacheManager import CacheManager
from librespot.core.Session import Session
from librespot.proto import Metadata
class CdnManager:
_LOGGER: logging = logging.getLogger(__name__)
_session: Session = None
def __init__(self, session: Session):
self._session = session
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
def stream_external_episode(self, episode: Metadata.Episode,
external_url: str,
halt_listener: HaltListener):
return CdnManager.Streamer(self._session, StreamId(episode),
SuperAudioFormat.MP3,
CdnManager.CdnUrl(self, None, external_url),
self._session.cache(), NoopAudioDecrypt(),
halt_listener)
def stream_file(self, file: Metadata.AudioFile, key: bytes, url: str,
halt_listener: HaltListener):
return CdnManager.Streamer(self._session, StreamId.StreamId(file),
SuperAudioFormat.get(file.format),
CdnManager.CdnUrl(self, file.file_id, url),
self._session.cache(), AesAudioDecrypt(key),
halt_listener)
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
else:
raise CdnManager.CdnException(
"Could not retrieve CDN url! result: {}".format(proto.result))
class CdnException(Exception):
def __init__(self, ex):
super().__init__(ex)
class InternalResponse:
_buffer: bytearray
_headers: dict[str, str]
def __init__(self, buffer: bytearray, headers: dict[str, str]):
self._buffer = buffer
self._headers = headers
class CdnUrl:
_cdnManager = None
_fileId: bytes
_expiration: int
_url: str
def __init__(self, cdn_manager, file_id: bytes, url: str):
self._cdnManager: CdnManager = cdn_manager
self._fileId = file_id
self.set_url(url)
def url(self):
if self._expiration == -1:
return self._url
if self._expiration <= int(time.time() * 1000) + 5 * 60 * 1000:
self._url = self._cdnManager.get_audio_url(self._fileId)
return self.url
def set_url(self, url: str):
self._url = url
if self._fileId is not None:
token_url = urllib.parse.urlparse(url)
token_query = urllib.parse.parse_qs(token_url.query)
token_str = str(token_query.get("__token__"))
if token_str != "None" and len(token_str) != 0:
expire_at = None
split = token_str.split("~")
for s in split:
try:
i = s[0].index("=")
except ValueError:
continue
if s[0][:i] == "exp":
expire_at = int(s[0][i:])
break
if expire_at is None:
self._expiration = -1
self._cdnManager._LOGGER.warning(
"Invalid __token__ in CDN url: {}".format(url))
return
self._expiration = expire_at * 1000
else:
try:
i = token_url.query.index("_")
except ValueError:
self._expiration = -1
self._cdnManager._LOGGER.warning(
"Couldn't extract expiration, invalid parameter in CDN url: "
.format(url))
return
self._expiration = int(token_url.query[:i]) * 1000
else:
self._expiration = -1
class Streamer(GeneralAudioStream, GeneralWritableStream):
_session: Session = None
_streamId: StreamId = None
_executorService = concurrent.futures.ThreadPoolExecutor()
_audioFormat: SuperAudioFormat = None
_audioDecrypt: AudioDecrypt = None
_cdnUrl = None
_size: int
_buffer: list[bytearray]
_available: list[bool]
_requested: list[bool]
_chunks: int
_internalStream: CdnManager.Streamer.InternalStream = None
_haltListener: HaltListener = None
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 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, self._chunks, cached, self.describe()))
self._buffer[chunk_index] = self._audioDecrypt.decrypt_chunk(
chunk_index, chunk)
self._internalStream.notify_chunk_available(chunk_index)
def stream(self) -> AbsChunkedInputStream:
return self._internalStream
def codec(self) -> SuperAudioFormat:
return self._audioFormat
def describe(self) -> str:
if self._streamId.is_episode():
return "episode_gid: {}".format(
self._streamId.get_episode_gid())
else:
return "file_id: {}".format(self._streamId.get_file_id())
def decrypt_time_ms(self) -> int:
return self._audioDecrypt.decrypt_time_ms()
def request_chunk(self, index: int) -> None:
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:
if chunk is None and range_start is None and range_end is None:
raise TypeError()
if chunk is not None:
range_start = ChannelManager.CHUNK_SIZE * chunk
range_end = (chunk + 1) * ChannelManager.CHUNK_SIZE - 1
resp = self._session.client().get(self._cdnUrl._url,
headers={
"Range":
"bytes={}-{}".format(
range_start, range_end)
})
if resp.status_code != 206:
raise IOError(resp.status_code)
body = resp.content
if body is None:
raise IOError("Response body is empty!")
return CdnManager.InternalResponse(bytearray(body), resp.headers)
class InternalStream(AbsChunkedInputStream):
streamer = None
def __init__(self, streamer, retry_on_chunk_error: bool):
self.streamer: CdnManager.Streamer = streamer
super().__init__(retry_on_chunk_error)
def close(self) -> None:
super().close()
def buffer(self) -> list[bytearray]:
return self.streamer._buffer
def size(self) -> int:
return self.streamer._size
def requested_chunks(self) -> list[bool]:
return self.streamer._requested
def available_chunks(self) -> list[bool]:
return self.streamer._available
def chunks(self) -> int:
return self.streamer._chunks
def request_chunk_from_stream(self, index: int) -> None:
self.streamer._executorService.submit(
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))
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))

View File

@@ -0,0 +1,2 @@
from librespot.audio.cdn.CdnFeedHelper import CdnFeedHelper
from librespot.audio.cdn.CdnManager import ChannelManager

View File

@@ -0,0 +1,49 @@
from Crypto.Cipher import AES
from Crypto.Util import Counter
from librespot.audio.storage.ChannelManager import ChannelManager
from librespot.audio.decrypt.AudioDecrypt import AudioDecrypt
import time
class AesAudioDecrypt(AudioDecrypt):
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
decrypt_count = 0
decrypt_total_time = 0
key: bytes = None
def __init__(self, key: bytes):
self.key = key
def decrypt_chunk(self, chunk_index: int, buffer: bytes):
new_buffer = b""
iv = self.iv_int + int(ChannelManager.CHUNK_SIZE * chunk_index / 16)
start = time.time_ns()
for i in range(0, len(buffer), 4096):
cipher = AES.new(key=self.key,
mode=AES.MODE_CTR,
counter=Counter.new(128, initial_value=iv))
count = min(4096, len(buffer) - i)
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))
iv += self.iv_diff
self.decrypt_total_time += time.time_ns()
self.decrypt_count += 1
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)

View File

@@ -0,0 +1,6 @@
class AudioDecrypt:
def decrypt_chunk(self, chunk_index: int, buffer: bytes):
pass
def decrypt_time_ms(self):
pass

View File

@@ -0,0 +1,9 @@
from librespot.audio.decrypt import AudioDecrypt
class NoopAudioDecrypt(AudioDecrypt):
def decrypt_chunk(self, chunk_index: int, buffer: bytes):
pass
def decrypt_time_ms(self):
return 0

View File

@@ -0,0 +1,3 @@
from librespot.audio.decrypt.AesAudioDecrypt import AesAudioDecrypt
from librespot.audio.decrypt.AudioDecrypt import AudioDecrypt
from librespot.audio.decrypt.NoopAudioDecrypt import NoopAudioDecrypt

View File

@@ -0,0 +1,10 @@
from __future__ import annotations
import typing
if typing.TYPE_CHECKING:
from librespot.proto import Metadata
class AudioQualityPicker:
def get_file(self, files: list[Metadata.AudioFile]) -> Metadata.AudioFile:
pass

View File

@@ -0,0 +1,27 @@
from librespot.proto import Metadata
import enum
class SuperAudioFormat(enum.Enum):
MP3 = 0x00
VORBIS = 0x01
AAC = 0x02
@staticmethod
def get(audio_format: Metadata.AudioFile.Format):
if audio_format == Metadata.AudioFile.Format.OGG_VORBIS_96 or \
audio_format == Metadata.AudioFile.Format.OGG_VORBIS_160 or \
audio_format == Metadata.AudioFile.Format.OGG_VORBIS_320:
return SuperAudioFormat.VORBIS
elif audio_format == Metadata.AudioFile.Format.MP3_256 or \
audio_format == Metadata.AudioFile.Format.MP3_320 or \
audio_format == Metadata.AudioFile.Format.MP3_160 or \
audio_format == Metadata.AudioFile.Format.MP3_96 or \
audio_format == Metadata.AudioFile.Format.MP3_160_ENC:
return SuperAudioFormat.MP3
elif audio_format == Metadata.AudioFile.Format.AAC_24 or \
audio_format == Metadata.AudioFile.Format.AAC_48 or \
audio_format == Metadata.AudioFile.Format.AAC_24_NORM:
return SuperAudioFormat.AAC
else:
raise RuntimeError("Unknown audio format: {}".format(audio_format))

View File

@@ -0,0 +1,2 @@
from librespot.audio.format.AudioQualityPicker import AudioQualityPicker
from librespot.audio.format.SuperAudioFormat import SuperAudioFormat

View File

@@ -0,0 +1,12 @@
from librespot.audio.GeneralWritableStream import GeneralWritableStream
class AudioFile(GeneralWritableStream):
def write_chunk(self, buffer: bytearray, chunk_index: int, cached: bool):
pass
def write_header(self, chunk_id: int, b: bytearray, cached: bool):
pass
def stream_error(self, chunk_index: int, code: int):
pass

View File

@@ -0,0 +1,12 @@
from __future__ import annotations
import typing
if typing.TYPE_CHECKING:
from librespot.core.Session import Session
class AudioFileStreaming:
cache_handler = None
def __init__(self, session: Session):
pass

View File

@@ -0,0 +1,146 @@
from __future__ import annotations
import queue
from librespot.audio.storage import AudioFile
from librespot.common import Utils
from librespot.core import PacketsReceiver, Session
from librespot.crypto import Packet
from librespot.standard import BytesInputStream, BytesOutputStream, Closeable, Runnable
import concurrent.futures
import logging
import threading
class ChannelManager(Closeable, PacketsReceiver.PacketsReceiver):
CHUNK_SIZE: int = 128 * 1024
_LOGGER: logging = logging.getLogger(__name__)
_channels: dict[int, Channel] = dict()
_seqHolder: int = 0
_seqHolderLock: threading.Condition = threading.Condition()
_executorService: concurrent.futures.ThreadPoolExecutor = concurrent.futures.ThreadPoolExecutor(
)
_session: Session = None
def __init__(self, session: Session):
self._session = session
def request_chunk(self, file_id: bytes, index: int, file: AudioFile):
start = int(index * self.CHUNK_SIZE / 4)
end = int((index + 1) * self.CHUNK_SIZE / 4)
channel = ChannelManager.Channel(self, file, index)
self._channels[channel.chunkId] = channel
out = BytesOutputStream()
out.write_short(channel.chunkId)
out.write_int(0x00000000)
out.write_int(0x00000000)
out.write_int(0x00004e20)
out.write_int(0x00030d40)
out.write(file_id)
out.write_int(start)
out.write_int(end)
self._session.send(Packet.Type.stream_chunk, out.buffer)
def dispatch(self, packet: Packet) -> None:
payload = BytesInputStream(packet.payload)
if packet.is_cmd(Packet.Type.stream_chunk_res):
chunk_id = payload.read_short()
channel = self._channels.get(chunk_id)
if channel is None:
self._LOGGER.warning(
"Couldn't find channel, id: {}, received: {}".format(
chunk_id, len(packet.payload)))
return
channel._add_to_queue(payload)
elif packet.is_cmd(Packet.Type.channel_error):
chunk_id = payload.read_short()
channel = self._channels.get(chunk_id)
if channel is None:
self._LOGGER.warning(
"Dropping channel error, id: {}, code: {}".format(
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)))
def close(self) -> None:
self._executorService.shutdown()
class Channel:
_channelManager: ChannelManager
chunkId: int
_q: queue.Queue = queue.Queue()
_file: AudioFile
_chunkIndex: int
_buffer: BytesOutputStream = BytesOutputStream()
_header: bool = True
def __init__(self, channel_manager: ChannelManager, file: AudioFile,
chunk_index: int):
self._channelManager = channel_manager
self._file = file
self._chunkIndex = chunk_index
with self._channelManager._seqHolderLock:
self.chunkId = self._channelManager._seqHolder
self._channelManager._seqHolder += 1
self._channelManager._executorService.submit(
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)
return True
self._channelManager._LOGGER.debug(
"Received empty chunk, skipping.")
return False
if self._header:
length: int
while len(payload.buffer) > 0:
length = payload.read_short()
if not length > 0:
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._header = False
else:
self._buffer.write(payload.read(len(payload.buffer)))
return False
def _add_to_queue(self, payload):
self._q.put(payload)
def stream_error(self, code: int) -> None:
self._file.stream_error(self._chunkIndex, code)
class Handler(Runnable):
_channel: ChannelManager.Channel = None
def __init__(self, channel: ChannelManager.Channel):
self._channel = channel
def run(self) -> None:
self._channel._channelManager._LOGGER.debug(
"ChannelManager.Handler is starting")
with self._channel._q.all_tasks_done:
self._channel._channelManager._channels.pop(
self._channel.chunkId)
self._channel._channelManager._LOGGER.debug(
"ChannelManager.Handler is shutting down")

View File

@@ -0,0 +1,3 @@
from librespot.audio.storage.AudioFile import AudioFile
from librespot.audio.storage.AudioFileStreaming import AudioFileStreaming
from librespot.audio.storage.ChannelManager import ChannelManager

13
librespot/cache/CacheManager.py vendored Normal file
View File

@@ -0,0 +1,13 @@
from __future__ import annotations
from librespot.core import Session
class CacheManager:
CLEAN_UP_THRESHOLD = 604800000
HEADER_TIMESTAMP = 254
HEADER_HASH = 253
parent: str
def __init__(self, conf: Session.Configuration):
pass

1
librespot/cache/__init__.py vendored Normal file
View File

@@ -0,0 +1 @@
from librespot.cache.CacheManager import CacheManager

View File

@@ -0,0 +1,88 @@
import math
class Base62:
standard_base = 256
target_base = 62
alphabet: bytes
lookup: bytearray
def __init__(self, alphabet: bytes):
self.alphabet = alphabet
self.create_lookup_table()
@staticmethod
def create_instance_with_inverted_character_set():
return Base62(Base62.CharacterSets.inverted)
def encode(self, message: bytes, length: int = -1):
indices = self.convert(message, self.standard_base, self.target_base,
length)
return self.translate(indices, self.alphabet)
def decode(self, encoded: bytes, length: int = -1):
prepared = self.translate(encoded, self.lookup)
return self.convert(prepared, self.target_base, self.standard_base,
length)
def translate(self, indices: bytes, dictionary: bytes):
translation = bytearray(len(indices))
for i in range(len(indices)):
translation[i] = dictionary[int.from_bytes(indices[i].encode(),
"big")]
return translation
def convert(self, message: bytes, source_base: int, target_base: int,
length: int):
estimated_length = self.estimate_output_length(
len(message), source_base, target_base) if length == -1 else length
out = b""
source = message
while len(source) > 0:
quotient = b""
remainder = 0
for b in source:
accumulator = int(b & 0xff) + remainder * source_base
digit = int(
(accumulator - (accumulator % target_base)) / target_base)
remainder = int(accumulator % target_base)
if len(quotient) > 0 or digit > 0:
quotient += bytes([digit])
out += bytes([remainder])
source = quotient
if len(out) < estimated_length:
size = len(out)
for i in range(estimated_length - size):
out += bytes([0])
return self.reverse(out)
elif len(out) > estimated_length:
return self.reverse(out[:estimated_length])
else:
return self.reverse(out)
def estimate_output_length(self, input_length: int, source_base: int,
target_base: int):
return int(
math.ceil((math.log(source_base) / math.log(target_base)) *
input_length))
def reverse(self, arr: bytes):
length = len(arr)
reversed_arr = bytearray(length)
for i in range(length):
reversed_arr[length - i - 1] = arr[i]
return bytes(reversed_arr)
def create_lookup_table(self):
self.lookup = bytearray(256)
for i in range(len(self.alphabet)):
self.lookup[self.alphabet[i]] = i & 0xff
class CharacterSets:
gmp = b'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
inverted = b'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

42
librespot/common/Utils.py Normal file
View File

@@ -0,0 +1,42 @@
import binascii
import os
class Utils:
@staticmethod
def random_hex_string(length: int):
buffer = os.urandom(int(length / 2))
return Utils.bytes_to_hex(buffer)
@staticmethod
def truncate_middle(s: str, length: int) -> str:
if length <= 1:
raise TypeError()
first = length / 2
result = s[:first]
result += "..."
result += s[len(s) - (length - first):]
return result
@staticmethod
def split(s: str, c: str):
return s.split(c)
@staticmethod
def to_byte_array(i: int) -> bytes:
width = i.bit_length()
width += 8 - ((width % 8) or 8)
fmt = '%%0%dx' % (width // 4)
if i == 0:
return bytes([0])
else:
return binascii.unhexlify(fmt % i)
@staticmethod
def bytes_to_hex(buffer: bytes) -> str:
return binascii.hexlify(buffer).decode()
@staticmethod
def hex_to_bytes(s: str) -> bytes:
return binascii.unhexlify(s)

View File

@@ -0,0 +1,2 @@
from librespot.common.Base62 import Base62
from librespot.common.Utils import Utils

View File

@@ -0,0 +1,33 @@
import queue
import random
import requests
class ApResolver:
base_url = "http://apresolve.spotify.com/"
@staticmethod
def request(service_type: str):
response = requests.get("{}?type={}".format(ApResolver.base_url,
service_type))
return response.json()
@staticmethod
def get_random_of(service_type: str):
pool = ApResolver.request(service_type)
urls = pool.get(service_type)
if urls is None or len(urls) == 0:
raise RuntimeError()
return random.choice(urls)
@staticmethod
def get_random_dealer() -> str:
return ApResolver.get_random_of("dealer")
@staticmethod
def get_random_spclient() -> str:
return ApResolver.get_random_of("spclient")
@staticmethod
def get_random_accesspoint() -> str:
return ApResolver.get_random_of("accesspoint")

View File

@@ -0,0 +1,103 @@
from __future__ import annotations
import concurrent.futures
import enum
import time
import typing
import logging
from librespot.core import Session
from librespot.mercury import RawMercuryRequest
from librespot.standard import ByteArrayOutputStream
class EventService:
_session: Session
_LOGGER: logging = logging.getLogger(__name__)
_worker: concurrent.futures.ThreadPoolExecutor = concurrent.futures.ThreadPoolExecutor(
)
def __init__(self, session: Session):
self._session = session
def _worker_callback(self, event_builder: EventService.EventBuilder):
try:
body = event_builder.to_array()
resp = self._session.mercury().send_sync(RawMercuryRequest.Builder(
).set_uri("hm://event-service/v1/events").set_method(
"POST").add_user_field("Accept-Language", "en").add_user_field(
"X-ClientTimeStamp",
int(time.time() * 1000)).add_payload_part(body).build())
self._LOGGER.debug("Event sent. body: {}, result: {}".format(
body, resp.status_code))
except IOError as ex:
self._LOGGER.error("Failed sending event: {} {}".format(
event_builder, ex))
def send_event(self,
event_or_builder: typing.Union[EventService.GenericEvent,
EventService.EventBuilder]):
if type(event_or_builder) is EventService.GenericEvent:
builder = event_or_builder.build()
elif type(event_or_builder) is EventService.EventBuilder:
builder = event_or_builder
else:
TypeError()
self._worker.submit(lambda: self._worker_callback(builder))
def language(self, lang: str):
event = EventService.EventBuilder(EventService.Type.LANGUAGE)
event.append(s=lang)
def close(self):
pass
class Type(enum.Enum):
LANGUAGE = ("812", 1)
FETCHED_FILE_ID = ("274", 3)
NEW_SESSION_ID = ("557", 3)
NEW_PLAYBACK_ID = ("558", 1)
TRACK_PLAYED = ("372", 1)
TRACK_TRANSITION = ("12", 37)
CDN_REQUEST = ("10", 20)
_eventId: str
_unknown: str
def __init__(self, event_id: str, unknown: str):
self._eventId = event_id
self._unknown = unknown
class GenericEvent:
def build(self) -> EventService.EventBuilder:
pass
class EventBuilder:
body: ByteArrayOutputStream = ByteArrayOutputStream(256)
def __init__(self, type: EventService.Type):
self.append_no_delimiter(type.value[0])
self.append(type.value[1])
def append_no_delimiter(self, s: str = None) -> None:
if s is None:
s = ""
self.body.write(buffer=bytearray(s.encode()))
def append(self,
c: int = None,
s: str = None) -> EventService.EventBuilder:
if c is None and s is None or c is not None and s is not None:
raise TypeError()
if c is not None:
self.body.write(byte=0x09)
self.body.write(byte=c)
return self
elif s is not None:
self.body.write(byte=0x09)
self.append_no_delimiter(s)
return self
def to_array(self) -> bytearray:
return self.body.to_byte_array()

View File

@@ -0,0 +1,6 @@
from librespot.crypto.Packet import Packet
class PacketsReceiver:
def dispatch(self, packet: Packet):
pass

View File

@@ -0,0 +1,10 @@
from __future__ import annotations
from librespot.core import Session
class SearchManager:
_BASE_URL: str = "hm://searchview/km/v4/search/"
_session: Session
def __init__(self, session: Session):
self._session = session

1048
librespot/core/Session.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
import math
import time
class TimeProvider:
offset = 0
method = 0x00
def init(self, conf=None, session=None):
if conf is None and session is None:
return
if conf is not None:
self.method = conf.time_synchronization_method
if conf.time_synchronization_method == TimeProvider.Method.ntp:
self.update_with_ntp()
if conf.time_synchronization_method == TimeProvider.Method.manual:
self.offset = conf.time_manual_correction
if session is not None:
if self.method != TimeProvider.Method.melody:
return
self.update_melody(session)
def current_time_millis(self):
return math.floor(time.time() * 1000) + self.offset
def update_melody(self, session):
pass
def update_with_ntp(self):
pass
class Method:
ntp = 0x00
ping = 0x01
melody = 0x02
manual = 0x03

View File

@@ -0,0 +1,84 @@
from __future__ import annotations
from librespot.core import Session, TimeProvider
from librespot.mercury import MercuryRequests
import logging
class TokenProvider:
_LOGGER: logging = logging.getLogger(__name__)
_TOKEN_EXPIRE_THRESHOLD = 10
_session: Session = None
_tokens: list[TokenProvider.StoredToken] = []
def __init__(self, session: Session):
self._session = session
def find_token_with_all_scopes(
self, scopes: list[str]) -> TokenProvider.StoredToken:
for token in self._tokens:
if token.has_scopes(scopes):
return token
# noinspection PyTypeChecker
return None
def get_token(self, *scopes) -> TokenProvider.StoredToken:
scopes = list(scopes)
if len(scopes) == 0:
raise RuntimeError()
token = self.find_token_with_all_scopes(scopes)
if token is not None:
if token.expired():
self._tokens.remove(token)
else:
return token
self._LOGGER.debug(
"Token expired or not suitable, requesting again. scopes: {}, old_token: {}"
.format(scopes, token))
resp = self._session.mercury().send_sync_json(
MercuryRequests.request_token(self._session.device_id(),
",".join(scopes)))
token = TokenProvider.StoredToken(resp)
self._LOGGER.debug(
"Updated token successfully! scopes: {}, new_token: {}".format(
scopes, token))
self._tokens.append(token)
return token
def get(self, scope: str) -> str:
return self.get_token(scope).access_token
class StoredToken:
expires_in: int
access_token: str
scopes: list[str]
timestamp: int
def __init__(self, obj):
self.timestamp = TimeProvider.TimeProvider().current_time_millis()
self.expires_in = obj["expiresIn"]
self.access_token = obj["accessToken"]
self.scopes = obj["scope"]
def expired(self) -> bool:
return self.timestamp + (
self.expires_in - TokenProvider._TOKEN_EXPIRE_THRESHOLD
) * 1000 < TimeProvider.TimeProvider().current_time_millis()
def has_scope(self, scope: str) -> bool:
for s in self.scopes:
if s == scope:
return True
return False
def has_scopes(self, sc: list[str]) -> bool:
for s in sc:
if not self.has_scope(s):
return False
return True

View File

@@ -0,0 +1,7 @@
from librespot.core.ApResolver import ApResolver
from librespot.core.EventService import EventService
from librespot.core.PacketsReceiver import PacketsReceiver
from librespot.core.SearchManager import SearchManager
from librespot.core.Session import Session
from librespot.core.TimeProvider import TimeProvider
from librespot.core.TokenProvider import TokenProvider

View File

@@ -0,0 +1,62 @@
from librespot.crypto.Packet import Packet
from librespot.crypto.Shannon import Shannon
import struct
class CipherPair:
send_cipher: Shannon
receive_cipher: Shannon
send_nonce = 0
receive_nonce = 0
def __init__(self, send_key: bytes, receive_key: bytes):
# self.send_cipher = Shannon()
# self.send_cipher.key(send_key)
self.send_cipher = Shannon(send_key)
self.send_nonce = 0
# self.receive_cipher = Shannon()
# self.receive_cipher.key(receive_key)
self.receive_cipher = Shannon(receive_key)
self.receive_nonce = 0
def send_encoded(self, conn, cmd: bytes, payload: bytes):
self.send_cipher.nonce(self.send_nonce)
self.send_nonce += 1
buffer = b""
buffer += cmd
buffer += struct.pack(">H", len(payload))
buffer += payload
buffer = self.send_cipher.encrypt(buffer)
# mac = self.send_cipher.finish(bytes(4))
mac = self.send_cipher.finish(4)
conn.write(buffer)
conn.write(mac)
conn.flush()
def receive_encoded(self, conn) -> Packet:
try:
self.receive_cipher.nonce(self.receive_nonce)
self.receive_nonce += 1
header_bytes = self.receive_cipher.decrypt(conn.read(3))
cmd = struct.pack(">s", bytes([header_bytes[0]]))
payload_length = (header_bytes[1] << 8) | (header_bytes[2] & 0xff)
payload_bytes = self.receive_cipher.decrypt(
conn.read(payload_length))
mac = conn.read(4)
expected_mac = self.receive_cipher.finish(4)
if mac != expected_mac:
raise RuntimeError()
return Packet(cmd, payload_bytes)
except IndexError:
raise RuntimeError()

View File

@@ -0,0 +1,38 @@
from binascii import unhexlify
from librespot.common.Utils import Utils
import os
import struct
class DiffieHellman:
prime_bytes: bytearray = bytes([
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2,
0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1,
0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6,
0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd,
0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d,
0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45,
0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4, 0x4c, 0x42, 0xe9,
0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
])
prime: int = int.from_bytes(prime_bytes, "big")
private_key: int
public_key: int
def __init__(self):
key_data = os.urandom(95)
self.private_key = int.from_bytes(key_data, "big")
self.public_key = pow(2, self.private_key, self.prime)
def compute_shared_key(self, remote_key_bytes: bytes):
remote_key = int.from_bytes(remote_key_bytes, "big")
return pow(remote_key, self.private_key, self.prime)
def private_key(self):
return self.private_key
def public_key(self):
return self.public_key
def public_key_array(self):
return Utils.to_byte_array(self.public_key)

View File

@@ -0,0 +1,66 @@
import re
class Packet:
cmd: bytes
payload: bytes
def __init__(self, cmd: bytes, payload: bytes):
self.cmd = cmd
self.payload = payload
def is_cmd(self, cmd: bytes):
return cmd == self.cmd
class Type:
secret_block = b"\x02"
ping = b"\x04"
stream_chunk = b"\x08"
stream_chunk_res = b"\x09"
channel_error = b"\x0a"
channel_abort = b"\x0b"
request_key = b"\x0c"
aes_key = b"\x0d"
aes_key_error = b"\x0e"
image = b"\x19"
country_code = b"\x1b"
pong = b"\x49"
pong_ack = b"\x4a"
pause = b"\x4b"
product_info = b"\x50"
legacy_welcome = b"\x69"
license_version = b"\x76"
login = b"\xab"
ap_welcome = b"\xac"
auth_failure = b"\xad"
mercury_req = b"\xb2"
mercury_sub = b"\xb3"
mercury_unsub = b"\xb4"
mercury_event = b"\xb5"
track_ended_time = b"\x82"
unknown_data_all_zeros = b"\x1f"
preferred_locale = b"\x74"
unknown_0x4f = b"\x4f"
unknown_0x0f = b"\x0f"
unknown_0x10 = b"\x10"
@staticmethod
def parse(val: bytes):
for cmd in [
Packet.Type.__dict__[attr]
for attr in Packet.Type.__dict__.keys()
if re.search("__.+?__", attr) is None
and type(Packet.Type.__dict__[attr]) is bytes
]:
if cmd == val:
return cmd
return None
@staticmethod
def for_method(method: str):
if method == "SUB":
return Packet.Type.mercury_sub
if method == "UNSUB":
return Packet.Type.mercury_unsub
return Packet.Type.mercury_req

495
librespot/crypto/Shannon.py Normal file
View File

@@ -0,0 +1,495 @@
"""
Shannon: Shannon stream cipher and MAC -- reference implementation, ported from C code written by Greg Rose
https://github.com/sashahilton00/spotify-connect-resources/blob/master/Shannon-1.0/ShannonRef.c
Copyright 2017, Dmitry Borisov
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND AGAINST
INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
import struct, \
copy
# Constants
N = 16
INITKONST = 0x6996c53a
KEYP = 13 # where to insert key/MAC/counter words
FOLD = N # how many iterations of folding to do
class Shannon:
@staticmethod
def ROTL(w, x):
return ((w << x) | (w >> (32 - x))) & 0xFFFFFFFF
@staticmethod
def ROTR(w, x):
return ((w >> x) | (w << (32 - x))) & 0xFFFFFFFF
""" Nonlinear transform (sbox) of a word.
There are two slightly different combinations. """
@staticmethod
def sbox1(w):
w ^= Shannon.ROTL(w, 5) | Shannon.ROTL(w, 7)
w ^= Shannon.ROTL(w, 19) | Shannon.ROTL(w, 22)
return w
""" Nonlinear transform (sbox) of a word.
There are two slightly different combinations. """
@staticmethod
def sbox2(w):
w ^= Shannon.ROTL(w, 7) | Shannon.ROTL(w, 22)
w ^= Shannon.ROTL(w, 5) | Shannon.ROTL(w, 19)
return w
""" initialise to known state """
def _initstate(self):
global N, \
INITKONST
# Generate fibonacci numbers up to N
self._R = [1, 1]
for x in range(1, N - 1):
self._R.append(self._R[x] + self._R[x - 1])
self._konst = INITKONST
""" cycle the contents of the register and calculate output word in _sbuf. """
def _cycle(self):
# nonlinear feedback function
t = self._R[12] ^ self._R[13] ^ self._konst
t = Shannon.sbox1(t) ^ Shannon.ROTL(self._R[0], 1)
# Shift to the left
self._R = self._R[1:] + [t]
t = Shannon.sbox2(self._R[2] ^ self._R[15])
self._R[0] ^= t
self._sbuf = t ^ self._R[8] ^ self._R[12]
""" The Shannon MAC function is modelled after the concepts of Phelix and SHA.
Basically, words to be accumulated in the MAC are incorporated in two
different ways:
1. They are incorporated into the stream cipher register at a place
where they will immediately have a nonlinear effect on the state
2. They are incorporated into bit-parallel CRC-16 registers; the
contents of these registers will be used in MAC finalization. """
""" Accumulate a CRC of input words, later to be fed into MAC.
This is actually 32 parallel CRC-16s, using the IBM CRC-16
polynomial x^16 + x^15 + x^2 + 1. """
def _crcfunc(self, i):
t = self._CRC[0] ^ self._CRC[2] ^ self._CRC[15] ^ i
# Accumulate CRC of input
self._CRC = self._CRC[1:] + [t]
""" Normal MAC word processing: do both stream register and CRC. """
def _macfunc(self, i):
global KEYP
self._crcfunc(i)
self._R[KEYP] ^= i
""" extra nonlinear diffusion of register for key and MAC """
def _diffuse(self):
global FOLD
for i in range(FOLD):
self._cycle()
""" Common actions for loading key material
Allow non-word-multiple key and nonce material.
Note also initializes the CRC register as a side effect. """
def _loadkey(self, key):
global KEYP, \
N
# Pad key with 00s to align on 4 bytes and add key_len
padding_size = int((len(key) + 3) / 4) * 4 - len(key)
key = key + (b'\x00' * padding_size) + struct.pack("<I", len(key))
for i in range(0, len(key), 4):
self._R[KEYP] = self._R[KEYP] ^ struct.unpack(
"<I", key[i:i + 4])[0] # Little Endian order
self._cycle()
# save a copy of the register
self._CRC = copy.copy(self._R)
# now diffuse
self._diffuse()
# now xor the copy back -- makes key loading irreversible */
for i in range(N):
self._R[i] ^= self._CRC[i]
""" Constructor """
def __init__(self, key):
self._initstate()
self._loadkey(key)
self._konst = self._R[0] # in case we proceed to stream generation
self._initR = copy.copy(self._R)
self._nbuf = 0
""" Published "IV" interface """
def nonce(self, nonce):
global INITKONST
if type(nonce) == int:
# Accept int as well (BigEndian)
nonce = bytes(struct.pack(">I", nonce))
self._R = copy.copy(self._initR)
self._konst = INITKONST
self._loadkey(nonce)
self._konst = self._R[0]
self._nbuf = 0
self._mbuf = 0
""" Encrypt small chunk """
def _encrypt_chunk(self, chunk):
result = []
for c in chunk:
self._mbuf ^= c << (32 - self._nbuf)
result.append(c ^ (self._sbuf >> (32 - self._nbuf)) & 0xFF)
self._nbuf -= 8
return result
""" Combined MAC and encryption.
Note that plaintext is accumulated for MAC. """
def encrypt(self, buf):
# handle any previously buffered bytes
result = []
if self._nbuf != 0:
head = buf[:(self._nbuf >> 3)]
buf = buf[(self._nbuf >> 3):]
result = self._encrypt_chunk(head)
if self._nbuf != 0:
return bytes(result)
# LFSR already cycled
self._macfunc(self._mbuf)
# Handle body
i = 0
while len(buf) >= 4:
self._cycle()
t = struct.unpack("<I", buf[i:i + 4])[0]
self._macfunc(t)
t ^= self._sbuf
result += struct.pack("<I", t)
buf = buf[4:]
# handle any trailing bytes
if len(buf):
self._cycle()
self._mbuf = 0
self._nbuf = 32
result += self._encrypt_chunk(buf)
return bytes(result)
""" Decrypt small chunk """
def _decrypt_chunk(self, chunk):
result = []
for c in chunk:
result.append(c ^ ((self._sbuf >> (32 - self._nbuf)) & 0xFF))
self._mbuf ^= result[-1] << (32 - self._nbuf)
self._nbuf -= 8
return result
""" Combined MAC and decryption.
Note that plaintext is accumulated for MAC. """
def decrypt(self, buf):
# handle any previously buffered bytes
result = []
if self._nbuf != 0:
head = buf[:(self._nbuf >> 3)]
buf = buf[(self._nbuf >> 3):]
result = self._decrypt_chunk(head)
if self._nbuf != 0:
return bytes(result)
# LFSR already cycled
self._macfunc(self._mbuf)
# Handle whole words
i = 0
while len(buf) >= 4:
self._cycle()
t = struct.unpack("<I", buf[i:i + 4])[0] ^ self._sbuf
self._macfunc(t)
result += struct.pack("<I", t)
buf = buf[4:]
# handle any trailing bytes
if len(buf):
self._cycle()
self._mbuf = 0
self._nbuf = 32
result += self._decrypt_chunk(buf)
return bytes(result)
""" Having accumulated a MAC, finish processing and return it.
Note that any unprocessed bytes are treated as if
they were encrypted zero bytes, so plaintext (zero) is accumulated. """
def finish(self, buf_len):
global KEYP, \
INITKONST
# handle any previously buffered bytes
if self._nbuf != 0:
# LFSR already cycled
self._macfunc(self._mbuf)
# perturb the MAC to mark end of input.
# Note that only the stream register is updated, not the CRC. This is an
# action that can't be duplicated by passing in plaintext, hence
# defeating any kind of extension attack.
self._cycle()
self._R[KEYP] ^= INITKONST ^ (self._nbuf << 3)
self._nbuf = 0
# now add the CRC to the stream register and diffuse it
for i in range(N):
self._R[i] ^= self._CRC[i]
self._diffuse()
result = []
# produce output from the stream buffer
i = 0
for i in range(0, buf_len, 4):
self._cycle()
if i + 4 <= buf_len:
result += struct.pack("<I", self._sbuf)
else:
sbuf = self._sbuf
for j in range(i, buf_len):
result.append(sbuf & 0xFF)
sbuf >>= 8
return bytes(result)
if __name__ == '__main__':
TESTSIZE = 23
TEST_KEY = b"test key 128bits"
TEST_PHRASE = b'\x00' * 20
sh = Shannon(
bytes([
133, 199, 15, 101, 207, 100, 229, 237, 15, 249, 248, 155, 76, 170,
62, 189, 239, 251, 147, 213, 22, 186, 157, 47, 218, 198, 235, 14,
171, 50, 11, 121
]))
sh.set_nonce(0)
p1 = sh.decrypt(
bytes([
235,
94,
210,
19,
246,
203,
195,
35,
22,
215,
80,
69,
158,
247,
110,
146,
241,
101,
199,
37,
67,
92,
5,
197,
112,
244,
77,
185,
197,
118,
119,
56,
164,
246,
159,
242,
56,
200,
39,
27,
141,
191,
37,
244,
244,
164,
44,
250,
59,
227,
245,
155,
239,
155,
137,
85,
244,
29,
52,
233,
180,
119,
166,
46,
252,
24,
141,
20,
135,
73,
144,
10,
176,
79,
88,
228,
140,
62,
173,
192,
117,
116,
152,
182,
246,
183,
88,
90,
73,
51,
159,
83,
227,
222,
140,
48,
157,
137,
185,
131,
201,
202,
122,
112,
207,
231,
153,
155,
9,
163,
225,
73,
41,
252,
249,
65,
33,
102,
83,
100,
36,
115,
174,
191,
43,
250,
113,
229,
146,
47,
154,
175,
55,
101,
73,
164,
49,
234,
103,
32,
53,
190,
236,
47,
210,
78,
141,
0,
176,
255,
79,
151,
159,
66,
20,
]))
print([hex(x) for x in p1])
print([hex(x) for x in sh.finish(4)])
sh.set_nonce(1)
print([hex(x) for x in sh.decrypt(bytes([173, 184, 50]))])
sh = Shannon(TEST_KEY)
sh.set_nonce(0)
encr = [sh.encrypt(bytes([x])) for x in TEST_PHRASE]
print('Encrypted 1-by-1 (len %d)' % len(encr), [hex(x[0]) for x in encr])
print(' sbuf %08x' % sh._sbuf)
print(' MAC', [hex(x) for x in sh.finish(4)])
sh.set_nonce(0)
encr = sh.encrypt(TEST_PHRASE)
print('Encrypted whole (len %d)' % len(encr), [hex(x) for x in encr])
print(' sbuf %08x' % sh._sbuf)
print(' MAC', [hex(x) for x in sh.finish(4)])
sh.set_nonce(0)
print('Decrypted whole', [hex(x) for x in sh.decrypt(encr)])
print(' MAC', [hex(x) for x in sh.finish(4)])
sh.set_nonce(0)
decr = [sh.decrypt(bytes([x])) for x in encr]
print('Decrypted 1-by-1', [hex(x[0]) for x in decr])
print(' MAC', [hex(x) for x in sh.finish(4)])

View File

@@ -0,0 +1,311 @@
import struct
class Shannon:
n = 16
fold = n
initkonst = 0x6996c53a
keyp = 13
r: list
crc: list
initr: list
konst: int
sbuf: int
mbuf: int
nbuf: int
def __init__(self):
self.r = [0 for _ in range(self.n)]
self.crc = [0 for _ in range(self.n)]
self.initr = [0 for _ in range(self.n)]
def rotl(self, i: int, distance: int):
return ((i << distance) | (i >> (32 - distance))) & 0xffffffff
def sbox(self, i: int):
i ^= self.rotl(i, 5) | self.rotl(i, 7)
i ^= self.rotl(i, 19) | self.rotl(i, 22)
return i
def sbox2(self, i: int):
i ^= self.rotl(i, 7) | self.rotl(i, 22)
i ^= self.rotl(i, 5) | self.rotl(i, 19)
return i
def cycle(self):
t: int
t = self.r[12] ^ self.r[13] ^ self.konst
t = self.sbox(t) ^ self.rotl(self.r[0], 1)
for i in range(1, self.n):
self.r[i - 1] = self.r[i]
self.r[self.n - 1] = t
t = self.sbox2(self.r[2] ^ self.r[15])
self.r[0] ^= t
self.sbuf = t ^ self.r[8] ^ self.r[12]
def crc_func(self, i: int):
t: int
t = self.crc[0] ^ self.crc[2] ^ self.crc[15] ^ i
for j in range(1, self.n):
self.crc[j - 1] = self.crc[j]
self.crc[self.n - 1] = t
def mac_func(self, i: int):
self.crc_func(i)
self.r[self.keyp] ^= i
def init_state(self):
self.r[0] = 1
self.r[1] = 1
for i in range(2, self.n):
self.r[i] = self.r[i - 1] + self.r[i - 2]
self.konst = self.initkonst
def save_state(self):
for i in range(self.n):
self.initr[i] = self.r[i]
def reload_state(self):
for i in range(self.n):
self.r[i] = self.initr[i]
def gen_konst(self):
self.konst = self.r[0]
def add_key(self, k: int):
self.r[self.keyp] ^= k
def diffuse(self):
for i in range(self.fold):
self.cycle()
def load_key(self, key: bytes):
extra = bytearray(4)
i: int
j: int
t: int
padding_size = int((len(key) + 3) / 4) * 4 - len(key)
key = key + (b"\x00" * padding_size) + struct.pack("<I", len(key))
for i in range(0, len(key), 4):
self.r[self.keyp] = \
self.r[self.keyp] ^ \
struct.unpack("<I", key[i: i + 4])[0]
self.cycle()
for i in range(self.n):
self.crc[i] = self.r[i]
self.diffuse()
for i in range(self.n):
self.r[i] ^= self.crc[i]
def key(self, key: bytes):
self.init_state()
self.load_key(key)
self.gen_konst()
self.save_state()
self.nbuf = 0
def nonce(self, nonce: bytes):
self.reload_state()
self.konst = self.initkonst
self.load_key(nonce)
self.gen_konst()
self.nbuf = 0
def encrypt(self, buffer: bytes, n: int = None):
if n is None:
return self.encrypt(buffer, len(buffer))
buffer = bytearray(buffer)
i = 0
j: int
t: int
if self.nbuf != 0:
while self.nbuf != 0 and n != 0:
self.mbuf ^= (buffer[i] & 0xff) << (32 - self.nbuf)
buffer[i] ^= (self.sbuf >> (32 - self.nbuf)) & 0xff
i += 1
self.nbuf -= 8
n -= 1
if self.nbuf != 0:
return
self.mac_func(self.mbuf)
j = n & ~0x03
while i < j:
self.cycle()
t = ((buffer[i + 3] & 0xFF) << 24) | \
((buffer[i + 2] & 0xFF) << 16) | \
((buffer[i + 1] & 0xFF) << 8) | \
(buffer[i] & 0xFF)
self.mac_func(t)
t ^= self.sbuf
buffer[i + 3] = (t >> 24) & 0xFF
buffer[i + 2] = (t >> 16) & 0xFF
buffer[i + 3] = (t >> 8) & 0xFF
buffer[i] = t & 0xFF
i += 4
n &= 0x03
if n != 0:
self.cycle()
self.mbuf = 0
self.nbuf = 32
while self.nbuf != 0 and n != 0:
self.mbuf ^= (buffer[i] & 0xff) << (32 - self.nbuf)
buffer[i] ^= (self.sbuf >> (32 - self.nbuf)) & 0xff
i += 1
self.nbuf -= 8
n -= 1
return bytes(buffer)
def decrypt(self, buffer: bytes, n: int = None):
if n is None:
return self.decrypt(buffer, len(buffer))
buffer = bytearray(buffer)
i = 0
j: int
t: int
if self.nbuf != 0:
while self.nbuf != 0 and n != 0:
buffer[i] ^= (self.sbuf >> (32 - self.nbuf)) & 0xff
self.mbuf ^= (buffer[i] & 0xff) << (32 - self.nbuf)
i += 1
self.nbuf -= 8
n -= 1
if self.nbuf != 0:
return
self.mac_func(self.mbuf)
j = n & ~0x03
while i < j:
self.cycle()
t = ((buffer[i + 3] & 0xFF) << 24) | \
((buffer[i + 2] & 0xFF) << 16) | \
((buffer[i + 1] & 0xFF) << 8) | \
(buffer[i] & 0xFF)
t ^= self.sbuf
self.mac_func(t)
buffer[i + 3] = (t >> 24) & 0xFF
buffer[i + 2] = (t >> 16) & 0xFF
buffer[i + 1] = (t >> 8) & 0xFF
buffer[i] = t & 0xFF
i += 4
n &= 0x03
if n != 0:
self.cycle()
self.mbuf = 0
self.nbuf = 32
while self.nbuf != 0 and n != 0:
buffer[i] ^= (self.sbuf >> (32 - self.nbuf)) & 0xff
self.mbuf ^= (buffer[i] & 0xff) << (32 - self.nbuf)
i += 1
self.nbuf -= 8
n -= 1
return bytes(buffer)
def finish(self, buffer: bytes, n: int = None):
if n is None:
return self.finish(buffer, len(buffer))
buffer = bytearray(buffer)
i = 0
j: int
if self.nbuf != 0:
self.mac_func(self.mbuf)
self.cycle()
self.add_key(self.initkonst ^ (self.nbuf << 3))
self.nbuf = 0
for j in range(self.n):
self.r[j] ^= self.crc[j]
self.diffuse()
while n > 0:
self.cycle()
if n >= 4:
buffer[i + 3] = (self.sbuf >> 24) & 0xff
buffer[i + 2] = (self.sbuf >> 16) & 0xff
buffer[i + 1] = (self.sbuf >> 8) & 0xff
buffer[i] = self.sbuf & 0xff
n -= 4
i += 4
else:
for j in range(n):
buffer[i + j] = (self.sbuf >> (i * 8)) & 0xff
break
return bytes(buffer)

View File

@@ -0,0 +1,4 @@
from librespot.crypto.CipherPair import CipherPair
from librespot.crypto.DiffieHellman import DiffieHellman
from librespot.crypto.Packet import Packet
from librespot.crypto.Shannon import Shannon

View File

@@ -0,0 +1,129 @@
from librespot.core.ApResolver import ApResolver
from librespot.metadata import AlbumId, ArtistId, EpisodeId, TrackId, ShowId
from librespot.proto import Connect, Metadata
from librespot.standard import Closeable
from typing import Union
import logging
import requests
class ApiClient(Closeable):
_LOGGER: logging = logging.getLogger(__name__)
_session = None
_baseUrl: str = None
def __init__(self, session):
self._session = session
self._baseUrl = "https://{}".format(ApResolver.get_random_spclient())
def build_request(self, method: str, suffix: str,
headers: Union[None, dict[str, str]],
body: Union[None, bytes]) -> requests.PreparedRequest:
request = requests.PreparedRequest()
request.method = method
request.data = body
request.headers = {}
if headers is not None:
request.headers = headers
request.headers["Authorization"] = "Bearer {}".format(
self._session.tokens().get("playlist-read"))
request.url = self._baseUrl + suffix
return request
def send(self, method: str, suffix: str, headers: Union[None, dict[str,
str]],
body: Union[None, bytes]) -> requests.Response:
resp = self._session.client().send(
self.build_request(method, suffix, headers, body))
return resp
def put_connect_state(self, connection_id: str,
proto: Connect.PutStateRequest) -> None:
resp = self.send(
"PUT",
"/connect-state/v1/devices/{}".format(self._session.device_id()), {
"Content-Type": "application/protobuf",
"X-Spotify-Connection-Id": connection_id
}, proto.SerializeToString())
if resp.status_code == 413:
self._LOGGER.warning(
"PUT state payload is too large: {} bytes uncompressed.".
format(len(proto.SerializeToString())))
elif resp.status_code != 200:
self._LOGGER.warning("PUT state returned {}. headers: {}".format(
resp.status_code, resp.headers))
def get_metadata_4_track(self, track: TrackId) -> Metadata.Track:
resp = self.send("GET", "/metadata/4/track/{}".format(track.hex_id()),
None, None)
ApiClient.StatusCodeException.check_status(resp)
body = resp.content
if body is None:
raise RuntimeError()
proto = Metadata.Track()
proto.ParseFromString(body)
return proto
def get_metadata_4_episode(self, episode: EpisodeId) -> Metadata.Episode:
resp = self.send("GET",
"/metadata/4/episode/{}".format(episode.hex_id()),
None, None)
ApiClient.StatusCodeException.check_status(resp)
body = resp.content
if body is None:
raise IOError()
proto = Metadata.Episode()
proto.ParseFromString(body)
return proto
def get_metadata_4_album(self, album: AlbumId) -> Metadata.Album:
resp = self.send("GET", "/metadata/4/album/{}".format(album.hex_id()),
None, None)
ApiClient.StatusCodeException.check_status(resp)
body = resp.content
if body is None:
raise IOError()
proto = Metadata.Album()
proto.ParseFromString(body)
return proto
def get_metadata_4_artist(self, artist: ArtistId) -> Metadata.Artist:
resp = self.send("GET",
"/metadata/4/artist/{}".format(artist.hex_id()), None,
None)
ApiClient.StatusCodeException.check_status(resp)
body = resp.content
if body is None:
raise IOError()
proto = Metadata.Artist()
proto.ParseFromString(body)
return proto
def get_metadata_4_show(self, show: ShowId) -> Metadata.Show:
resp = self.send("GET", "/metadata/4/show/{}".format(show.hex_id()),
None, None)
ApiClient.StatusCodeException.check_status(resp)
body = resp.content
if body is None:
raise IOError()
proto = Metadata.Show()
proto.ParseFromString(body)
return proto
class StatusCodeException(IOError):
code: int
def __init__(self, resp: requests.Response):
super().__init__(resp.status_code)
self.code = resp.status_code
@staticmethod
def check_status(resp: requests.Response) -> None:
if resp.status_code != 200:
raise ApiClient.StatusCodeException(resp)

View File

@@ -0,0 +1,19 @@
from __future__ import annotations
from librespot.standard.Closeable import Closeable
class DealerClient(Closeable):
def __init__(self, session):
pass
def connect(self):
pass
def add_message_listener(self, listener: DealerClient.MessageListener,
*uris: str):
pass
class MessageListener:
def on_message(self, uri: str, headers: dict[str, str],
payload: bytes):
pass

View File

@@ -0,0 +1,2 @@
from librespot.dealer.ApiClient import ApResolver
from librespot.dealer.DealerClient import DealerClient

View File

@@ -0,0 +1,5 @@
class JsonMercuryRequest:
request = None
def __init__(self, request):
self.request = request

View File

@@ -0,0 +1,256 @@
from __future__ import annotations
from librespot.common import Utils
from librespot.core import Session, PacketsReceiver
from librespot.crypto import Packet
from librespot.mercury import JsonMercuryRequest, RawMercuryRequest, SubListener
from librespot.standard import BytesInputStream, BytesOutputStream, Closeable
from librespot.proto import Mercury, Pubsub
import json
import logging
import queue
import threading
import typing
class MercuryClient(PacketsReceiver.PacketsReceiver, Closeable):
_LOGGER: logging = logging.getLogger(__name__)
_MERCURY_REQUEST_TIMEOUT: int = 3
_seqHolder: int = 1
_seqHolderLock: threading.Condition = threading.Condition()
_callbacks: dict[int, Callback] = dict()
_removeCallbackLock: threading.Condition = threading.Condition()
_subscriptions: list[MercuryClient.InternalSubListener] = list()
_subscriptionsLock: threading.Condition = threading.Condition()
_partials: dict[int, bytes] = dict()
_session: Session = None
def __init__(self, session: Session):
self._session = session
def subscribe(self, uri: str, listener: SubListener) -> None:
response = self.send_sync(RawMercuryRequest.sub(uri))
if response.status_code != 200:
raise RuntimeError(response)
if len(response.payload) > 0:
for payload in response.payload:
sub = Pubsub.Subscription()
sub.ParseFromString(payload)
self._subscriptions.append(
MercuryClient.InternalSubListener(sub.uri, listener, True))
else:
self._subscriptions.append(
MercuryClient.InternalSubListener(uri, listener, True))
self._LOGGER.debug("Subscribed successfully to {}!".format(uri))
def unsubscribe(self, uri) -> None:
response = self.send_sync(RawMercuryRequest.unsub(uri))
if response.status_code != 200:
raise RuntimeError(response)
for subscription in self._subscriptions:
if subscription.matches(uri):
self._subscriptions.remove(subscription)
break
self._LOGGER.debug("Unsubscribed successfully from {}!".format(uri))
def send_sync(self, request: RawMercuryRequest) -> MercuryClient.Response:
callback = MercuryClient.SyncCallback()
seq = self.send(request, callback)
try:
resp = callback.wait_response()
if resp is None:
raise IOError(
"Request timeout out, {} passed, yet no response. seq: {}".
format(self._MERCURY_REQUEST_TIMEOUT, seq))
return resp
except queue.Empty as e:
raise IOError(e)
def send_sync_json(self, request: JsonMercuryRequest) -> typing.Any:
resp = self.send_sync(request.request)
if 200 <= resp.status_code < 300:
return json.loads(resp.payload[0])
else:
raise MercuryClient.MercuryException(resp)
def send(self, request: RawMercuryRequest, callback) -> int:
buffer = BytesOutputStream()
seq: int
with self._seqHolderLock:
seq = self._seqHolder
self._seqHolder += 1
self._LOGGER.debug(
"Send Mercury request, seq: {}, uri: {}, method: {}".format(
seq, request.header.uri, request.header.method))
buffer.write_short(4)
buffer.write_int(seq)
buffer.write_byte(1)
buffer.write_short(1 + len(request.payload))
header_bytes = request.header.SerializeToString()
buffer.write_short(len(header_bytes))
buffer.write(header_bytes)
for part in request.payload:
buffer.write_short(len(part))
buffer.write(part)
cmd = Packet.Type.for_method(request.header.method)
self._session.send(cmd, buffer.buffer)
self._callbacks[seq] = callback
return seq
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))
def interested_in(self, uri: str, listener: SubListener) -> None:
self._subscriptions.append(
MercuryClient.InternalSubListener(uri, listener, False))
def not_interested_in(self, listener: SubListener) -> None:
try:
# noinspection PyTypeChecker
self._subscriptions.remove(listener)
except ValueError:
pass
def close(self) -> None:
if len(self._subscriptions) != 0:
for listener in self._subscriptions:
if listener.isSub:
self.unsubscribe(listener.uri)
else:
self.not_interested_in(listener.listener)
if len(self._callbacks) != 0:
with self._removeCallbackLock:
self._removeCallbackLock.wait(self._MERCURY_REQUEST_TIMEOUT)
self._callbacks.clear()
class Callback:
def response(self, response) -> None:
pass
class SyncCallback(Callback):
_reference = queue.Queue()
def response(self, response) -> None:
self._reference.put(response)
self._reference.task_done()
def wait_response(self) -> typing.Any:
return self._reference.get(
timeout=MercuryClient._MERCURY_REQUEST_TIMEOUT)
# class PubSubException(MercuryClient.MercuryException):
# pass
class InternalSubListener:
uri: str
listener: SubListener
isSub: bool
def __init__(self, uri: str, listener: SubListener, is_sub: bool):
self.uri = uri
self.listener = listener
self.isSub = is_sub
def matches(self, uri: str) -> bool:
return uri.startswith(self.uri)
def dispatch(self, resp: MercuryClient.Response) -> None:
self.listener.event(resp)
class MercuryException(Exception):
code: int
def __init__(self, response):
super("status: {}".format(response.status_code))
self.code = response.status_code
class Response:
uri: str
payload: list[bytes]
status_code: int
def __init__(self, header: Mercury.Header, payload: list[bytes]):
self.uri = header.uri
self.status_code = header.status_code
self.payload = payload[1:]

View File

@@ -0,0 +1,18 @@
from librespot.mercury.JsonMercuryRequest import JsonMercuryRequest
from librespot.mercury.RawMercuryRequest import RawMercuryRequest
class MercuryRequests:
keymaster_client_id = "65b708073fc0480ea92a077233ca87bd"
@staticmethod
def get_root_playlists(username: str):
pass
@staticmethod
def request_token(device_id, scope):
return JsonMercuryRequest(
RawMercuryRequest.get(
"hm://keymaster/token/authenticated?scope={}&client_id={}&device_id={}"
.format(scope, MercuryRequests.keymaster_client_id,
device_id)))

View File

@@ -0,0 +1,7 @@
class ProtobufMercuryRequest:
request = None
parser = None
def __init__(self, request, parser):
self.request = request
self.parser = parser

View File

@@ -0,0 +1,87 @@
from librespot.proto import Mercury
class RawMercuryRequest:
header: Mercury.Header
payload: list[bytes]
def __init__(self, header: Mercury.Header, payload: list[bytes]):
self.header = header
self.payload = payload
@staticmethod
def sub(uri: str):
return RawMercuryRequest.new_builder().set_uri(uri).set_method(
"SUB").build()
@staticmethod
def unsub(uri: str):
return RawMercuryRequest.new_builder().set_uri(uri).set_method(
"UNSUB").build()
@staticmethod
def get(uri: str):
return RawMercuryRequest.new_builder().set_uri(uri).set_method(
"GET").build()
@staticmethod
def send(uri: str, part: bytes):
return RawMercuryRequest.new_builder().set_uri(uri).add_payload_part(
part).set_method("SEND").build()
@staticmethod
def post(uri: str, part: bytes):
return RawMercuryRequest.new_builder().set_uri(uri).set_method(
"POST").add_payload_part(part).build()
@staticmethod
def new_builder():
return RawMercuryRequest.Builder()
class Builder:
header_dict: dict
payload: list[bytes]
def __init__(self):
self.header_dict = {}
self.payload = []
def set_uri(self, uri: str):
self.header_dict["uri"] = uri
return self
def set_content_type(self, content_type: str):
self.header_dict["content_type"] = content_type
return self
def set_method(self, method: str):
self.header_dict["method"] = method
return self
def add_user_field(self,
field: Mercury.UserField = None,
key: str = None,
value: str = None):
if field is None and (key is None or value is None):
return self
try:
self.header_dict["user_fields"]
except KeyError:
self.header_dict["user_fields"] = []
if field is not None:
self.header_dict["user_fields"].append(field)
if key is not None and value is not None:
self.header_dict["user_fields"].append(
Mercury.UserField(key=key, value=value.encode()))
return self
def add_payload_part(self, part: bytes):
self.payload.append(part)
return self
def add_protobuf_payload(self, msg):
return self.add_payload_part(msg)
def build(self):
return RawMercuryRequest(Mercury.Header(**self.header_dict),
self.payload)

View File

@@ -0,0 +1,7 @@
from __future__ import annotations
from librespot.mercury import MercuryClient
class SubListener:
def event(self, resp: MercuryClient.Response) -> None:
pass

View File

@@ -0,0 +1,6 @@
from librespot.mercury.JsonMercuryRequest import JsonMercuryRequest
from librespot.mercury.MercuryClient import MercuryClient
from librespot.mercury.MercuryRequests import MercuryRequests
from librespot.mercury.ProtobufMercuryRequest import ProtobufMercuryRequest
from librespot.mercury.RawMercuryRequest import RawMercuryRequest
from librespot.mercury.SubListener import SubListener

View File

@@ -0,0 +1,40 @@
from __future__ import annotations
from librespot.common import Base62, Utils
from librespot.metadata import SpotifyId
import re
class AlbumId(SpotifyId.SpotifyId):
_PATTERN = re.compile(r"spotify:album:(.{22})")
_BASE62 = Base62.create_instance_with_inverted_character_set()
_hexId: str
def __init__(self, hex_id: str):
self._hexId = hex_id.lower()
@staticmethod
def from_uri(uri: str) -> AlbumId:
matcher = AlbumId._PATTERN.search(uri)
if matcher is not None:
album_id = matcher.group(1)
return AlbumId(
Utils.bytes_to_hex(AlbumId._BASE62.decode(album_id, 16)))
else:
raise TypeError("Not a Spotify album ID: {}.f".format(uri))
@staticmethod
def from_base62(base62: str) -> AlbumId:
return AlbumId(
Utils.bytes_to_hex(AlbumId._BASE62.decode(base62, 16)))
@staticmethod
def from_hex(hex_str: str) -> AlbumId:
return AlbumId(hex_str)
def to_mercury_uri(self) -> str:
return "spotify:album:{}".format(
AlbumId._BASE62.encode(Utils.hex_to_bytes(self._hexId)))
def hex_id(self) -> str:
return self._hexId

View File

@@ -0,0 +1,43 @@
from __future__ import annotations
from librespot.common import Base62, Utils
from librespot.metadata import SpotifyId
import re
class ArtistId(SpotifyId.SpotifyId):
_PATTERN = re.compile("spotify:artist:(.{22})")
_BASE62 = Base62.create_instance_with_inverted_character_set()
_hexId: str
def __init__(self, hex_id: str):
self._hexId = hex_id
@staticmethod
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)))
else:
raise TypeError("Not a Spotify artist ID: {}".format(uri))
@staticmethod
def from_base62(base62: str) -> ArtistId:
return ArtistId(
Utils.bytes_to_hex(ArtistId._BASE62.decode(base62, 16)))
@staticmethod
def from_hex(hex_str: str) -> ArtistId:
return ArtistId(hex_str)
def to_mercury_uri(self) -> str:
return "hm://metadata/4/artist/{}".format(self._hexId)
def to_spotify_uri(self) -> str:
return "spotify:artist:{}".format(
ArtistId._BASE62.encode(Utils.hex_to_bytes(self._hexId)))
def hex_id(self) -> str:
return self._hexId

View File

@@ -0,0 +1,46 @@
from __future__ import annotations
from librespot.common import Utils
from librespot.metadata import SpotifyId
from librespot.metadata.PlayableId import PlayableId
import re
class EpisodeId(SpotifyId.SpotifyId, PlayableId):
_PATTERN = re.compile(r"spotify:episode:(.{22})")
_hexId: str
def __init__(self, hex_id: str):
self._hexId = hex_id.lower()
@staticmethod
def from_uri(uri: str) -> EpisodeId:
matcher = EpisodeId._PATTERN.search(uri)
if matcher is not None:
episode_id = matcher.group(1)
return EpisodeId(
Utils.Utils.bytes_to_hex(
PlayableId.BASE62.decode(episode_id, 16)))
else:
TypeError("Not a Spotify episode ID: {}".format(uri))
@staticmethod
def from_base62(base62: str) -> EpisodeId:
return EpisodeId(
Utils.Utils.bytes_to_hex(PlayableId.BASE62.decode(base62, 16)))
@staticmethod
def from_hex(hex_str: str) -> EpisodeId:
return EpisodeId(hex_str)
def to_mercury_uri(self) -> str:
return "hm://metadata/4/episode/{}".format(self._hexId)
def to_spotify_uri(self) -> str:
return "Spotify:episode:{}".format(
PlayableId.BASE62.encode(Utils.Utils.hex_to_bytes(self._hexId)))
def hex_id(self) -> str:
return self._hexId
def get_gid(self) -> bytes:
return Utils.Utils.hex_to_bytes(self._hexId)

View File

@@ -0,0 +1,40 @@
from __future__ import annotations
from librespot.common.Base62 import Base62
# from librespot.metadata import EpisodeId, TrackId, UnsupportedId
from librespot.proto.context_track_pb2 import ContextTrack
class PlayableId:
BASE62 = Base62.create_instance_with_inverted_character_set()
@staticmethod
def from_uri(uri: str) -> PlayableId:
pass
# if not PlayableId.is_supported(uri):
# return UnsupportedId(uri)
# if TrackId._PATTERN.search(uri) is not None:
# return TrackId.from_uri(uri)
# elif EpisodeId._PATTERN.search(uri) is not None:
# return EpisodeId.from_uri(uri)
# else:
# raise TypeError("Unknown uri: {}".format(uri))
@staticmethod
def is_supported(uri: str):
return not uri.startswith("spotify:local:") and \
not uri == "spotify:delimiter" and \
not uri == "spotify:meta:delimiter"
@staticmethod
def should_play(track: ContextTrack):
return track.metadata_or_default
def get_gid(self) -> bytes:
pass
def hex_id(self) -> str:
pass
def to_spotify_uri(self) -> str:
pass

View File

@@ -0,0 +1,42 @@
from __future__ import annotations
from librespot.common import Base62, Utils
from librespot.metadata import SpotifyId
import re
class ShowId(SpotifyId.SpotifyId):
_PATTERN = re.compile("spotify:show:(.{22})")
_BASE62 = Base62.create_instance_with_inverted_character_set()
_hexId: str
def __init__(self, hex_id: str):
self._hexId = hex_id
@staticmethod
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)))
else:
raise TypeError("Not a Spotify show ID: {}".format(uri))
@staticmethod
def from_base62(base62: str) -> ShowId:
return ShowId(
Utils.bytes_to_hex(ShowId._BASE62.decode(base62, 16)))
@staticmethod
def from_hex(hex_str: str) -> ShowId:
return ShowId(hex_str)
def to_mercury_uri(self) -> str:
return "hm://metadata/4/show/{}".format(self._hexId)
def to_spotify_uri(self) -> str:
return "spotify:show:{}".format(
ShowId._BASE62.encode(Utils.hex_to_bytes(self._hexId)))
def hex_id(self) -> str:
return self._hexId

View File

@@ -0,0 +1,23 @@
class SpotifyId:
STATIC_FROM_URI = "fromUri"
STATIC_FROM_BASE62 = "fromBase62"
STATIC_FROM_HEX = "fromHex"
@staticmethod
def from_base62(base62: str):
pass
@staticmethod
def from_hex(hex_str: str):
pass
@staticmethod
def from_uri(uri: str):
pass
def to_spotify_uri(self) -> str:
pass
class SpotifyIdParsingException(Exception):
def __init__(self, cause):
super().__init__(cause)

View File

@@ -0,0 +1,42 @@
from __future__ import annotations
from librespot.common import Utils
from librespot.metadata import SpotifyId
from librespot.metadata.PlayableId import PlayableId
import re
class TrackId(PlayableId, SpotifyId):
_PATTERN = re.compile("spotify:track:(.{22})")
_hexId: str
def __init__(self, hex_id: str):
self._hexId = hex_id.lower()
@staticmethod
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)))
else:
raise RuntimeError("Not a Spotify track ID: {}".format(uri))
@staticmethod
def from_base62(base62: str) -> TrackId:
return TrackId(
Utils.bytes_to_hex(PlayableId.BASE62.decode(base62, 16)))
@staticmethod
def from_hex(hex_str: str) -> TrackId:
return TrackId(hex_str)
def to_spotify_uri(self) -> str:
return "spotify:track:{}".format(self._hexId)
def hex_id(self) -> str:
return self._hexId
def get_gid(self) -> bytes:
return Utils.hex_to_bytes(self._hexId)

View File

@@ -0,0 +1,17 @@
from librespot.metadata import PlayableId
class UnsupportedId(PlayableId):
uri: str
def __init__(self, uri: str):
self.uri = uri
def get_gid(self) -> bytes:
raise TypeError()
def hex_id(self) -> str:
raise TypeError()
def to_spotify_uri(self) -> str:
return self.uri

View File

@@ -0,0 +1,8 @@
from librespot.metadata.AlbumId import AlbumId
from librespot.metadata.ArtistId import ArtistId
from librespot.metadata.EpisodeId import EpisodeId
from librespot.metadata.PlayableId import PlayableId
from librespot.metadata.ShowId import SpotifyId
from librespot.metadata.SpotifyId import SpotifyId
from librespot.metadata.TrackId import TrackId
from librespot.metadata.UnsupportedId import UnsupportedId

View File

@@ -0,0 +1,52 @@
from __future__ import annotations
from librespot.core.Session import Session
from librespot.player import PlayerConfiguration, StateWrapper
from librespot.player.metrics import PlaybackMetrics
from librespot.player.mixing import AudioSink
from librespot.player.playback.PlayerSession import PlayerSession
from librespot.player.state.DeviceStateHandler import DeviceStateHandler
from librespot.standard.Closeable import Closeable
import sched
import time
class Player(Closeable, PlayerSession.Listener, AudioSink.Listener):
VOLUME_MAX: int = 65536
_scheduler: sched.scheduler = sched.scheduler(time.time)
_session: Session = None
_conf: PlayerConfiguration = None
_events: Player.EventsDispatcher = None
_sink: AudioSink = None
_metrics: dict[str, PlaybackMetrics] = dict()
_state: StateWrapper = None
_playerSession: PlayerSession = None
_releaseLineFuture = None
_deviceStateListener: DeviceStateHandler.Listener = None
def __init__(self, conf: PlayerConfiguration, session: Session):
self._conf = conf
self._session = session
self._events = Player.EventsDispatcher(conf)
self._sink = AudioSink(conf, self)
def init_state(self):
self._state = StateWrapper(self._session, self, self._conf)
class Anonymous(DeviceStateHandler.Listener):
def ready(self) -> None:
pass
def command(self, endpoint: DeviceStateHandler.Endpoint, data: DeviceStateHandler.CommandBody) -> None:
pass
self._deviceStateListener = Anonymous()
def volume_up(self, steps: int = 1):
if self.state is None:
return
class EventsDispatcher:
def __init__(self, conf: PlayerConfiguration):
pass

View File

@@ -0,0 +1,79 @@
from __future__ import annotations
from librespot.player.codecs import AudioQuality
class PlayerConfiguration:
# Audio
preferred_quality: AudioQuality
enable_normalisation: bool
normalisation_pregain: float
autoplay_enabled: bool
crossfade_duration: int
preload_enabled: bool
# Volume
initial_volume: int
volume_steps: int
def __init__(self, preferred_quality: AudioQuality,
enable_normalisation: bool, normalisation_pregain: float,
autoplay_enabled: bool, crossfade_duration: int,
preload_enabled: bool, initial_volume: int,
volume_steps: int):
self.preferred_quality = preferred_quality
self.enable_normalisation = enable_normalisation
self.normalisation_pregain = normalisation_pregain
self.autoplay_enabled = autoplay_enabled
self.crossfade_duration = crossfade_duration
self.preload_enabled = preload_enabled
self.initial_volume = initial_volume
self.volume_steps = volume_steps
class Builder:
preferred_quality: AudioQuality = AudioQuality.AudioQuality.NORMAL
enable_normalisation: bool = True
normalisation_pregain: float = 3.0
autoplay_enabled: bool = True
crossfade_duration: int = 0
preload_enabled: bool = True
# Volume
initial_volume: int = 65536
volume_steps: int = 64
def __init__(self):
pass
def set_preferred_quality(
self, preferred_quality: AudioQuality) -> __class__:
self.preferred_quality = preferred_quality
return self
def set_enable_normalisation(self,
enable_normalisation: bool) -> __class__:
self.enable_normalisation = enable_normalisation
return self
def set_normalisation_pregain(
self, normalisation_pregain: float) -> __class__:
self.normalisation_pregain = normalisation_pregain
return self
def set_autoplay_enabled(self, autoplay_enabled: bool) -> __class__:
self.autoplay_enabled = autoplay_enabled
return self
def set_crossfade_duration(self, crossfade_duration: int) -> __class__:
self.crossfade_duration = crossfade_duration
return self
def set_preload_enabled(self, preload_enabled: bool) -> __class__:
self.preload_enabled = preload_enabled
return self
def build(self) -> PlayerConfiguration:
return PlayerConfiguration(
self.preferred_quality, self.enable_normalisation,
self.normalisation_pregain, self.autoplay_enabled,
self.crossfade_duration, self.preload_enabled,
self.initial_volume, self.volume_steps)

View File

@@ -0,0 +1,38 @@
from __future__ import annotations
from librespot.core import Session
from librespot.player import Player, PlayerConfiguration
from librespot.player.state import DeviceStateHandler
from librespot.proto import Connect
from librespot.proto.Player import ContextPlayerOptions, PlayerState, Restrictions, Suppressions
class StateWrapper(DeviceStateHandler.Listener):
_state: PlayerState = None
_session: Session = None
_player: Player = None
_device: DeviceStateHandler = None
def __init__(self, session: Session, player: Player,
conf: PlayerConfiguration):
self._session = session
self._player = player
self._device = DeviceStateHandler(session, self, conf)
self._state = self._init_state()
def _init_state(self) -> PlayerState:
return PlayerState(
playback_speed=1.0,
suppressions=Suppressions(),
context_restrictions=Restrictions(),
options=ContextPlayerOptions(
repeating_context=False,
shuffling_context=False,
repeating_track=False
),
position_as_of_timestamp=0,
position=0,
is_playing=False
)
def ready(self) -> None:
self._device.update_state(Connect.PutStateReason.NEW_DEVICE, 0, self._state)

View File

@@ -0,0 +1,3 @@
from librespot.player.Player import Player
from librespot.player.PlayerConfiguration import PlayerConfiguration
from librespot.player.StateWrapper import StateWrapper

View File

@@ -0,0 +1,37 @@
from __future__ import annotations
from librespot.proto.Metadata import AudioFile
import enum
class AudioQuality(enum.Enum):
NORMAL = 0x00
HIGH = 0x01
VERY_HIGH = 0x02
@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:
return AudioQuality.NORMAL
elif 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
elif 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
else:
raise RuntimeError("Unknown format: {}".format(format))
def get_matches(self, files: list[AudioFile]) -> list[AudioFile]:
file_list = []
for file in files:
if hasattr(file, "format") and AudioQuality.get_quality(
file.format) == self:
file_list.append(file)
return file_list

View File

@@ -0,0 +1,42 @@
from __future__ import annotations
import logging
from librespot.audio.format.AudioQualityPicker import AudioQualityPicker
from librespot.audio.format.SuperAudioFormat import SuperAudioFormat
from librespot.player.codecs.AudioQuality import AudioQuality
from librespot.proto import Metadata
class VorbisOnlyAudioQuality(AudioQualityPicker):
_LOGGER: logging = logging.getLogger(__name__)
preferred: AudioQuality
def __init__(self, preferred: AudioQuality):
self.preferred = preferred
@staticmethod
def get_vorbis_file(files: list[Metadata.AudioFile]):
for file in files:
if hasattr(file, "format") and SuperAudioFormat.get(
file.format) == SuperAudioFormat.VORBIS:
return file
return None
def get_file(self, files: list[Metadata.AudioFile]):
matches: list[Metadata.AudioFile] = self.preferred.get_matches(files)
vorbis: Metadata.AudioFile = VorbisOnlyAudioQuality.get_vorbis_file(
matches)
if vorbis is None:
vorbis: Metadata.AudioFile = VorbisOnlyAudioQuality.get_vorbis_file(
files)
if vorbis is not None:
self._LOGGER.warning(
"Using {} because preferred {} couldn't be found.".format(
vorbis.format, self.preferred))
else:
self._LOGGER.fatal(
"Couldn't find any Vorbis file, available: {}")
return vorbis

View File

@@ -0,0 +1,2 @@
from librespot.player.codecs.AudioQuality import AudioFile
from librespot.player.codecs.VorbisOnlyAudioQuality import VorbisOnlyAudioQuality

View File

@@ -0,0 +1,6 @@
from __future__ import annotations
import logging
class PlaybackMetrics:
_LOGGER = logging.getLogger(__name__)

View File

View File

@@ -0,0 +1,11 @@
from __future__ import annotations
from librespot.player import PlayerConfiguration
class AudioSink:
def __init__(self, conf: PlayerConfiguration, listener: AudioSink.Listener):
pass
class Listener:
def sink_error(self, ex: Exception):
pass

View File

@@ -0,0 +1 @@
from librespot.player.mixing.AudioSink import AudioSink

View File

@@ -0,0 +1,7 @@
from __future__ import annotations
from librespot.standard import Closeable
class PlayerSession(Closeable):
class Listener:
pass

View File

@@ -0,0 +1 @@
from librespot.player.playback.PlayerSession import PlayerSession

View File

@@ -0,0 +1,97 @@
from __future__ import annotations
from librespot.common import Utils
from librespot.core import Session
from librespot.player import PlayerConfiguration
from librespot.proto import Connect, Player
import base64
import concurrent.futures
import enum
import logging
import time
import typing
import urllib.parse
class DeviceStateHandler:
_LOGGER: logging = logging.getLogger(__name__)
_session: Session = None
_deviceInfo: Connect.DeviceInfo = None
_listeners: list[DeviceStateHandler.Listener] = list()
_putState: Connect.PutStateRequest = None
_putStateWorker: concurrent.futures.ThreadPoolExecutor = concurrent.futures.ThreadPoolExecutor()
_connectionId: str = None
def __init__(self, session: Session, player, conf: PlayerConfiguration):
self._session = session
self._deviceInfo = None
self._putState = Connect.PutStateRequest()
def _update_connection_id(self, newer: str) -> None:
newer = urllib.parse.unquote(newer, "UTF-8")
if self._connectionId is None or \
self._connectionId != newer:
self._connectionId = newer
self._LOGGER.debug("Updated Spotify-Connection-Id: {}".format(self._connectionId))
self._notify_ready()
def _notify_ready(self) -> None:
for listener in self._listeners:
listener.ready()
def update_state(self, reason: Connect.PutStateReason, player_time: int, state: Player.PlayerState):
if self._connectionId is None:
raise TypeError()
if player_time == -1:
pass
else:
pass
self._putState.put_state_reason = reason
self._putState.client_side_timestamp = int(time.time() * 1000)
self._putState.device.device_info = self._deviceInfo
self._putState.device.player_state = state
self._putStateWorker.submit(self._put_connect_state, self._putState)
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
))
class Endpoint(enum.Enum):
Play: str = "play"
Pause: str = "pause"
Resume: str = "resume"
SeekTo: str = "seek_to"
SkipNext: str = "skip_next"
SkipPrev: str = "skip_prev"
class Listener:
def ready(self) -> None:
pass
def command(self, endpoint: DeviceStateHandler.Endpoint, data: DeviceStateHandler.CommandBody) -> None:
pass
def volume_changed(self) -> None:
pass
def not_active(self) -> None:
pass
class CommandBody:
_obj: typing.Any = None
_data: bytes = None
_value: str = None
def __init__(self, obj: typing.Any):
self._obj = obj
if obj.get("data") is not None:
self._data = base64.b64decode(obj.get("data"))
if obj.get("value") is not None:
self._value = obj.get("value")

View File

@@ -0,0 +1 @@
from librespot.player.state.DeviceStateHandler import DeviceStateHandler

File diff suppressed because one or more lines are too long

2340
librespot/proto/Connect.py Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,147 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: explicit_content_pubsub.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='explicit_content_pubsub.proto',
package='spotify.explicit_content.proto',
syntax='proto2',
serialized_options=b'\n\024com.spotify.explicitH\002',
create_key=_descriptor._internal_create_key,
serialized_pb=
b'\n\x1d\x65xplicit_content_pubsub.proto\x12\x1espotify.explicit_content.proto\"*\n\x0cKeyValuePair\x12\x0b\n\x03key\x18\x01 \x02(\t\x12\r\n\x05value\x18\x02 \x02(\t\"S\n\x14UserAttributesUpdate\x12;\n\x05pairs\x18\x01 \x03(\x0b\x32,.spotify.explicit_content.proto.KeyValuePairB\x18\n\x14\x63om.spotify.explicitH\x02'
)
_KEYVALUEPAIR = _descriptor.Descriptor(
name='KeyValuePair',
full_name='spotify.explicit_content.proto.KeyValuePair',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='key',
full_name='spotify.explicit_content.proto.KeyValuePair.key',
index=0,
number=1,
type=9,
cpp_type=9,
label=2,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='value',
full_name='spotify.explicit_content.proto.KeyValuePair.value',
index=1,
number=2,
type=9,
cpp_type=9,
label=2,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=65,
serialized_end=107,
)
_USERATTRIBUTESUPDATE = _descriptor.Descriptor(
name='UserAttributesUpdate',
full_name='spotify.explicit_content.proto.UserAttributesUpdate',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='pairs',
full_name=
'spotify.explicit_content.proto.UserAttributesUpdate.pairs',
index=0,
number=1,
type=11,
cpp_type=10,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=109,
serialized_end=192,
)
_USERATTRIBUTESUPDATE.fields_by_name['pairs'].message_type = _KEYVALUEPAIR
DESCRIPTOR.message_types_by_name['KeyValuePair'] = _KEYVALUEPAIR
DESCRIPTOR.message_types_by_name[
'UserAttributesUpdate'] = _USERATTRIBUTESUPDATE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
KeyValuePair = _reflection.GeneratedProtocolMessageType(
'KeyValuePair',
(_message.Message, ),
{
'DESCRIPTOR': _KEYVALUEPAIR,
'__module__': 'explicit_content_pubsub_pb2'
# @@protoc_insertion_point(class_scope:spotify.explicit_content.proto.KeyValuePair)
})
_sym_db.RegisterMessage(KeyValuePair)
UserAttributesUpdate = _reflection.GeneratedProtocolMessageType(
'UserAttributesUpdate',
(_message.Message, ),
{
'DESCRIPTOR': _USERATTRIBUTESUPDATE,
'__module__': 'explicit_content_pubsub_pb2'
# @@protoc_insertion_point(class_scope:spotify.explicit_content.proto.UserAttributesUpdate)
})
_sym_db.RegisterMessage(UserAttributesUpdate)
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

File diff suppressed because one or more lines are too long

625
librespot/proto/Mercury.py Normal file
View File

@@ -0,0 +1,625 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: mercury.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='mercury.proto',
package='spotify',
syntax='proto2',
serialized_options=b'\n\013com.spotify',
create_key=_descriptor._internal_create_key,
serialized_pb=
b'\n\rmercury.proto\x12\x07spotify\"B\n\x16MercuryMultiGetRequest\x12(\n\x07request\x18\x01 \x03(\x0b\x32\x17.spotify.MercuryRequest\"<\n\x14MercuryMultiGetReply\x12$\n\x05reply\x18\x01 \x03(\x0b\x32\x15.spotify.MercuryReply\"O\n\x0eMercuryRequest\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x14\n\x0c\x63ontent_type\x18\x02 \x01(\t\x12\x0c\n\x04\x62ody\x18\x03 \x01(\x0c\x12\x0c\n\x04\x65tag\x18\x04 \x01(\x0c\"\xf5\x01\n\x0cMercuryReply\x12\x13\n\x0bstatus_code\x18\x01 \x01(\x11\x12\x16\n\x0estatus_message\x18\x02 \x01(\t\x12\x37\n\x0c\x63\x61\x63he_policy\x18\x03 \x01(\x0e\x32!.spotify.MercuryReply.CachePolicy\x12\x0b\n\x03ttl\x18\x04 \x01(\x11\x12\x0c\n\x04\x65tag\x18\x05 \x01(\x0c\x12\x14\n\x0c\x63ontent_type\x18\x06 \x01(\t\x12\x0c\n\x04\x62ody\x18\x07 \x01(\x0c\"@\n\x0b\x43\x61\x63hePolicy\x12\x0c\n\x08\x43\x41\x43HE_NO\x10\x01\x12\x11\n\rCACHE_PRIVATE\x10\x02\x12\x10\n\x0c\x43\x41\x43HE_PUBLIC\x10\x03\"y\n\x06Header\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x14\n\x0c\x63ontent_type\x18\x02 \x01(\t\x12\x0e\n\x06method\x18\x03 \x01(\t\x12\x13\n\x0bstatus_code\x18\x04 \x01(\x11\x12\'\n\x0buser_fields\x18\x06 \x03(\x0b\x32\x12.spotify.UserField\"\'\n\tUserField\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x42\r\n\x0b\x63om.spotify'
)
_MERCURYREPLY_CACHEPOLICY = _descriptor.EnumDescriptor(
name='CachePolicy',
full_name='spotify.MercuryReply.CachePolicy',
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name='CACHE_NO',
index=0,
number=1,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='CACHE_PRIVATE',
index=1,
number=2,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='CACHE_PUBLIC',
index=2,
number=3,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
],
containing_type=None,
serialized_options=None,
serialized_start=419,
serialized_end=483,
)
_sym_db.RegisterEnumDescriptor(_MERCURYREPLY_CACHEPOLICY)
_MERCURYMULTIGETREQUEST = _descriptor.Descriptor(
name='MercuryMultiGetRequest',
full_name='spotify.MercuryMultiGetRequest',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='request',
full_name='spotify.MercuryMultiGetRequest.request',
index=0,
number=1,
type=11,
cpp_type=10,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=26,
serialized_end=92,
)
_MERCURYMULTIGETREPLY = _descriptor.Descriptor(
name='MercuryMultiGetReply',
full_name='spotify.MercuryMultiGetReply',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='reply',
full_name='spotify.MercuryMultiGetReply.reply',
index=0,
number=1,
type=11,
cpp_type=10,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=94,
serialized_end=154,
)
_MERCURYREQUEST = _descriptor.Descriptor(
name='MercuryRequest',
full_name='spotify.MercuryRequest',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='uri',
full_name='spotify.MercuryRequest.uri',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='content_type',
full_name='spotify.MercuryRequest.content_type',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='body',
full_name='spotify.MercuryRequest.body',
index=2,
number=3,
type=12,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"",
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='etag',
full_name='spotify.MercuryRequest.etag',
index=3,
number=4,
type=12,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"",
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=156,
serialized_end=235,
)
_MERCURYREPLY = _descriptor.Descriptor(
name='MercuryReply',
full_name='spotify.MercuryReply',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='status_code',
full_name='spotify.MercuryReply.status_code',
index=0,
number=1,
type=17,
cpp_type=1,
label=1,
has_default_value=False,
default_value=0,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='status_message',
full_name='spotify.MercuryReply.status_message',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='cache_policy',
full_name='spotify.MercuryReply.cache_policy',
index=2,
number=3,
type=14,
cpp_type=8,
label=1,
has_default_value=False,
default_value=1,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='ttl',
full_name='spotify.MercuryReply.ttl',
index=3,
number=4,
type=17,
cpp_type=1,
label=1,
has_default_value=False,
default_value=0,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='etag',
full_name='spotify.MercuryReply.etag',
index=4,
number=5,
type=12,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"",
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='content_type',
full_name='spotify.MercuryReply.content_type',
index=5,
number=6,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='body',
full_name='spotify.MercuryReply.body',
index=6,
number=7,
type=12,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"",
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[
_MERCURYREPLY_CACHEPOLICY,
],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=238,
serialized_end=483,
)
_HEADER = _descriptor.Descriptor(
name='Header',
full_name='spotify.Header',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='uri',
full_name='spotify.Header.uri',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='content_type',
full_name='spotify.Header.content_type',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='method',
full_name='spotify.Header.method',
index=2,
number=3,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='status_code',
full_name='spotify.Header.status_code',
index=3,
number=4,
type=17,
cpp_type=1,
label=1,
has_default_value=False,
default_value=0,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='user_fields',
full_name='spotify.Header.user_fields',
index=4,
number=6,
type=11,
cpp_type=10,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=485,
serialized_end=606,
)
_USERFIELD = _descriptor.Descriptor(
name='UserField',
full_name='spotify.UserField',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='key',
full_name='spotify.UserField.key',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='value',
full_name='spotify.UserField.value',
index=1,
number=2,
type=12,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"",
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=608,
serialized_end=647,
)
_MERCURYMULTIGETREQUEST.fields_by_name[
'request'].message_type = _MERCURYREQUEST
_MERCURYMULTIGETREPLY.fields_by_name['reply'].message_type = _MERCURYREPLY
_MERCURYREPLY.fields_by_name[
'cache_policy'].enum_type = _MERCURYREPLY_CACHEPOLICY
_MERCURYREPLY_CACHEPOLICY.containing_type = _MERCURYREPLY
_HEADER.fields_by_name['user_fields'].message_type = _USERFIELD
DESCRIPTOR.message_types_by_name[
'MercuryMultiGetRequest'] = _MERCURYMULTIGETREQUEST
DESCRIPTOR.message_types_by_name[
'MercuryMultiGetReply'] = _MERCURYMULTIGETREPLY
DESCRIPTOR.message_types_by_name['MercuryRequest'] = _MERCURYREQUEST
DESCRIPTOR.message_types_by_name['MercuryReply'] = _MERCURYREPLY
DESCRIPTOR.message_types_by_name['Header'] = _HEADER
DESCRIPTOR.message_types_by_name['UserField'] = _USERFIELD
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
MercuryMultiGetRequest = _reflection.GeneratedProtocolMessageType(
'MercuryMultiGetRequest',
(_message.Message, ),
{
'DESCRIPTOR': _MERCURYMULTIGETREQUEST,
'__module__': 'mercury_pb2'
# @@protoc_insertion_point(class_scope:spotify.MercuryMultiGetRequest)
})
_sym_db.RegisterMessage(MercuryMultiGetRequest)
MercuryMultiGetReply = _reflection.GeneratedProtocolMessageType(
'MercuryMultiGetReply',
(_message.Message, ),
{
'DESCRIPTOR': _MERCURYMULTIGETREPLY,
'__module__': 'mercury_pb2'
# @@protoc_insertion_point(class_scope:spotify.MercuryMultiGetReply)
})
_sym_db.RegisterMessage(MercuryMultiGetReply)
MercuryRequest = _reflection.GeneratedProtocolMessageType(
'MercuryRequest',
(_message.Message, ),
{
'DESCRIPTOR': _MERCURYREQUEST,
'__module__': 'mercury_pb2'
# @@protoc_insertion_point(class_scope:spotify.MercuryRequest)
})
_sym_db.RegisterMessage(MercuryRequest)
MercuryReply = _reflection.GeneratedProtocolMessageType(
'MercuryReply',
(_message.Message, ),
{
'DESCRIPTOR': _MERCURYREPLY,
'__module__': 'mercury_pb2'
# @@protoc_insertion_point(class_scope:spotify.MercuryReply)
})
_sym_db.RegisterMessage(MercuryReply)
Header = _reflection.GeneratedProtocolMessageType(
'Header',
(_message.Message, ),
{
'DESCRIPTOR': _HEADER,
'__module__': 'mercury_pb2'
# @@protoc_insertion_point(class_scope:spotify.Header)
})
_sym_db.RegisterMessage(Header)
UserField = _reflection.GeneratedProtocolMessageType(
'UserField',
(_message.Message, ),
{
'DESCRIPTOR': _USERFIELD,
'__module__': 'mercury_pb2'
# @@protoc_insertion_point(class_scope:spotify.UserField)
})
_sym_db.RegisterMessage(UserField)
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

3698
librespot/proto/Metadata.py Normal file

File diff suppressed because one or more lines are too long

1851
librespot/proto/Player.py Normal file

File diff suppressed because it is too large Load Diff

112
librespot/proto/Pubsub.py Normal file
View File

@@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: pubsub.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='pubsub.proto',
package='spotify',
syntax='proto2',
serialized_options=b'\n\013com.spotify',
create_key=_descriptor._internal_create_key,
serialized_pb=
b'\n\x0cpubsub.proto\x12\x07spotify\"@\n\x0cSubscription\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x0e\n\x06\x65xpiry\x18\x02 \x01(\x05\x12\x13\n\x0bstatus_code\x18\x03 \x01(\x05\x42\r\n\x0b\x63om.spotify'
)
_SUBSCRIPTION = _descriptor.Descriptor(
name='Subscription',
full_name='spotify.Subscription',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='uri',
full_name='spotify.Subscription.uri',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='expiry',
full_name='spotify.Subscription.expiry',
index=1,
number=2,
type=5,
cpp_type=1,
label=1,
has_default_value=False,
default_value=0,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='status_code',
full_name='spotify.Subscription.status_code',
index=2,
number=3,
type=5,
cpp_type=1,
label=1,
has_default_value=False,
default_value=0,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=25,
serialized_end=89,
)
DESCRIPTOR.message_types_by_name['Subscription'] = _SUBSCRIPTION
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
Subscription = _reflection.GeneratedProtocolMessageType(
'Subscription',
(_message.Message, ),
{
'DESCRIPTOR': _SUBSCRIPTION,
'__module__': 'pubsub_pb2'
# @@protoc_insertion_point(class_scope:spotify.Subscription)
})
_sym_db.RegisterMessage(Subscription)
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -0,0 +1,154 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: storage-resolve.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='storage-resolve.proto',
package='spotify.download.proto',
syntax='proto3',
serialized_options=b'\n\023com.spotify.storageH\002',
create_key=_descriptor._internal_create_key,
serialized_pb=
b'\n\x15storage-resolve.proto\x12\x16spotify.download.proto\"\xaf\x01\n\x16StorageResolveResponse\x12\x45\n\x06result\x18\x01 \x01(\x0e\x32\x35.spotify.download.proto.StorageResolveResponse.Result\x12\x0e\n\x06\x63\x64nurl\x18\x02 \x03(\t\x12\x0e\n\x06\x66ileid\x18\x04 \x01(\x0c\".\n\x06Result\x12\x07\n\x03\x43\x44N\x10\x00\x12\x0b\n\x07STORAGE\x10\x01\x12\x0e\n\nRESTRICTED\x10\x03\x42\x17\n\x13\x63om.spotify.storageH\x02\x62\x06proto3'
)
_STORAGERESOLVERESPONSE_RESULT = _descriptor.EnumDescriptor(
name='Result',
full_name='spotify.download.proto.StorageResolveResponse.Result',
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name='CDN',
index=0,
number=0,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='STORAGE',
index=1,
number=1,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='RESTRICTED',
index=2,
number=3,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
],
containing_type=None,
serialized_options=None,
serialized_start=179,
serialized_end=225,
)
_sym_db.RegisterEnumDescriptor(_STORAGERESOLVERESPONSE_RESULT)
_STORAGERESOLVERESPONSE = _descriptor.Descriptor(
name='StorageResolveResponse',
full_name='spotify.download.proto.StorageResolveResponse',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='result',
full_name='spotify.download.proto.StorageResolveResponse.result',
index=0,
number=1,
type=14,
cpp_type=8,
label=1,
has_default_value=False,
default_value=0,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='cdnurl',
full_name='spotify.download.proto.StorageResolveResponse.cdnurl',
index=1,
number=2,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='fileid',
full_name='spotify.download.proto.StorageResolveResponse.fileid',
index=2,
number=4,
type=12,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"",
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[
_STORAGERESOLVERESPONSE_RESULT,
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[],
serialized_start=50,
serialized_end=225,
)
_STORAGERESOLVERESPONSE.fields_by_name[
'result'].enum_type = _STORAGERESOLVERESPONSE_RESULT
_STORAGERESOLVERESPONSE_RESULT.containing_type = _STORAGERESOLVERESPONSE
DESCRIPTOR.message_types_by_name[
'StorageResolveResponse'] = _STORAGERESOLVERESPONSE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
StorageResolveResponse = _reflection.GeneratedProtocolMessageType(
'StorageResolveResponse',
(_message.Message, ),
{
'DESCRIPTOR': _STORAGERESOLVERESPONSE,
'__module__': 'storage_resolve_pb2'
# @@protoc_insertion_point(class_scope:spotify.download.proto.StorageResolveResponse)
})
_sym_db.RegisterMessage(StorageResolveResponse)
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

View File

@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: canvaz-meta.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='canvaz-meta.proto',
package='com.spotify.canvaz',
syntax='proto3',
serialized_options=b'\n\022com.spotify.canvazH\002',
create_key=_descriptor._internal_create_key,
serialized_pb=
b'\n\x11\x63\x61nvaz-meta.proto\x12\x12\x63om.spotify.canvaz*R\n\x04Type\x12\t\n\x05IMAGE\x10\x00\x12\t\n\x05VIDEO\x10\x01\x12\x11\n\rVIDEO_LOOPING\x10\x02\x12\x18\n\x14VIDEO_LOOPING_RANDOM\x10\x03\x12\x07\n\x03GIF\x10\x04\x42\x16\n\x12\x63om.spotify.canvazH\x02\x62\x06proto3'
)
_TYPE = _descriptor.EnumDescriptor(
name='Type',
full_name='com.spotify.canvaz.Type',
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name='IMAGE',
index=0,
number=0,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='VIDEO',
index=1,
number=1,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='VIDEO_LOOPING',
index=2,
number=2,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='VIDEO_LOOPING_RANDOM',
index=3,
number=3,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='GIF',
index=4,
number=4,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
],
containing_type=None,
serialized_options=None,
serialized_start=41,
serialized_end=123,
)
_sym_db.RegisterEnumDescriptor(_TYPE)
Type = enum_type_wrapper.EnumTypeWrapper(_TYPE)
IMAGE = 0
VIDEO = 1
VIDEO_LOOPING = 2
VIDEO_LOOPING_RANDOM = 3
GIF = 4
DESCRIPTOR.enum_types_by_name['Type'] = _TYPE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -0,0 +1,540 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: canvaz.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
import canvaz_meta_pb2 as canvaz__meta__pb2
DESCRIPTOR = _descriptor.FileDescriptor(
name='canvaz.proto',
package='com.spotify.canvazcache',
syntax='proto3',
serialized_options=b'\n\022com.spotify.canvazH\002',
create_key=_descriptor._internal_create_key,
serialized_pb=
b'\n\x0c\x63\x61nvaz.proto\x12\x17\x63om.spotify.canvazcache\x1a\x11\x63\x61nvaz-meta.proto\"3\n\x06\x41rtist\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06\x61vatar\x18\x03 \x01(\t\"\xe1\x02\n\x14\x45ntityCanvazResponse\x12\x46\n\x08\x63\x61nvases\x18\x01 \x03(\x0b\x32\x34.com.spotify.canvazcache.EntityCanvazResponse.Canvaz\x12\x16\n\x0ettl_in_seconds\x18\x02 \x01(\x03\x1a\xe8\x01\n\x06\x43\x61nvaz\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\x12\x0f\n\x07\x66ile_id\x18\x03 \x01(\t\x12&\n\x04type\x18\x04 \x01(\x0e\x32\x18.com.spotify.canvaz.Type\x12\x12\n\nentity_uri\x18\x05 \x01(\t\x12/\n\x06\x61rtist\x18\x06 \x01(\x0b\x32\x1f.com.spotify.canvazcache.Artist\x12\x10\n\x08\x65xplicit\x18\x07 \x01(\x08\x12\x13\n\x0buploaded_by\x18\x08 \x01(\t\x12\x0c\n\x04\x65tag\x18\t \x01(\t\x12\x12\n\ncanvas_uri\x18\x0b \x01(\t\"\x88\x01\n\x13\x45ntityCanvazRequest\x12\x45\n\x08\x65ntities\x18\x01 \x03(\x0b\x32\x33.com.spotify.canvazcache.EntityCanvazRequest.Entity\x1a*\n\x06\x45ntity\x12\x12\n\nentity_uri\x18\x01 \x01(\t\x12\x0c\n\x04\x65tag\x18\x02 \x01(\tB\x16\n\x12\x63om.spotify.canvazH\x02\x62\x06proto3',
dependencies=[
canvaz__meta__pb2.DESCRIPTOR,
])
_ARTIST = _descriptor.Descriptor(
name='Artist',
full_name='com.spotify.canvazcache.Artist',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='uri',
full_name='com.spotify.canvazcache.Artist.uri',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='name',
full_name='com.spotify.canvazcache.Artist.name',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='avatar',
full_name='com.spotify.canvazcache.Artist.avatar',
index=2,
number=3,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[],
serialized_start=60,
serialized_end=111,
)
_ENTITYCANVAZRESPONSE_CANVAZ = _descriptor.Descriptor(
name='Canvaz',
full_name='com.spotify.canvazcache.EntityCanvazResponse.Canvaz',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='id',
full_name='com.spotify.canvazcache.EntityCanvazResponse.Canvaz.id',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='url',
full_name='com.spotify.canvazcache.EntityCanvazResponse.Canvaz.url',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='file_id',
full_name=
'com.spotify.canvazcache.EntityCanvazResponse.Canvaz.file_id',
index=2,
number=3,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='type',
full_name=
'com.spotify.canvazcache.EntityCanvazResponse.Canvaz.type',
index=3,
number=4,
type=14,
cpp_type=8,
label=1,
has_default_value=False,
default_value=0,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='entity_uri',
full_name=
'com.spotify.canvazcache.EntityCanvazResponse.Canvaz.entity_uri',
index=4,
number=5,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='artist',
full_name=
'com.spotify.canvazcache.EntityCanvazResponse.Canvaz.artist',
index=5,
number=6,
type=11,
cpp_type=10,
label=1,
has_default_value=False,
default_value=None,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='explicit',
full_name=
'com.spotify.canvazcache.EntityCanvazResponse.Canvaz.explicit',
index=6,
number=7,
type=8,
cpp_type=7,
label=1,
has_default_value=False,
default_value=False,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='uploaded_by',
full_name=
'com.spotify.canvazcache.EntityCanvazResponse.Canvaz.uploaded_by',
index=7,
number=8,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='etag',
full_name=
'com.spotify.canvazcache.EntityCanvazResponse.Canvaz.etag',
index=8,
number=9,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='canvas_uri',
full_name=
'com.spotify.canvazcache.EntityCanvazResponse.Canvaz.canvas_uri',
index=9,
number=11,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[],
serialized_start=235,
serialized_end=467,
)
_ENTITYCANVAZRESPONSE = _descriptor.Descriptor(
name='EntityCanvazResponse',
full_name='com.spotify.canvazcache.EntityCanvazResponse',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='canvases',
full_name='com.spotify.canvazcache.EntityCanvazResponse.canvases',
index=0,
number=1,
type=11,
cpp_type=10,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='ttl_in_seconds',
full_name=
'com.spotify.canvazcache.EntityCanvazResponse.ttl_in_seconds',
index=1,
number=2,
type=3,
cpp_type=2,
label=1,
has_default_value=False,
default_value=0,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[
_ENTITYCANVAZRESPONSE_CANVAZ,
],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[],
serialized_start=114,
serialized_end=467,
)
_ENTITYCANVAZREQUEST_ENTITY = _descriptor.Descriptor(
name='Entity',
full_name='com.spotify.canvazcache.EntityCanvazRequest.Entity',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='entity_uri',
full_name=
'com.spotify.canvazcache.EntityCanvazRequest.Entity.entity_uri',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='etag',
full_name='com.spotify.canvazcache.EntityCanvazRequest.Entity.etag',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[],
serialized_start=564,
serialized_end=606,
)
_ENTITYCANVAZREQUEST = _descriptor.Descriptor(
name='EntityCanvazRequest',
full_name='com.spotify.canvazcache.EntityCanvazRequest',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='entities',
full_name='com.spotify.canvazcache.EntityCanvazRequest.entities',
index=0,
number=1,
type=11,
cpp_type=10,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[
_ENTITYCANVAZREQUEST_ENTITY,
],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[],
serialized_start=470,
serialized_end=606,
)
_ENTITYCANVAZRESPONSE_CANVAZ.fields_by_name[
'type'].enum_type = canvaz__meta__pb2._TYPE
_ENTITYCANVAZRESPONSE_CANVAZ.fields_by_name['artist'].message_type = _ARTIST
_ENTITYCANVAZRESPONSE_CANVAZ.containing_type = _ENTITYCANVAZRESPONSE
_ENTITYCANVAZRESPONSE.fields_by_name[
'canvases'].message_type = _ENTITYCANVAZRESPONSE_CANVAZ
_ENTITYCANVAZREQUEST_ENTITY.containing_type = _ENTITYCANVAZREQUEST
_ENTITYCANVAZREQUEST.fields_by_name[
'entities'].message_type = _ENTITYCANVAZREQUEST_ENTITY
DESCRIPTOR.message_types_by_name['Artist'] = _ARTIST
DESCRIPTOR.message_types_by_name[
'EntityCanvazResponse'] = _ENTITYCANVAZRESPONSE
DESCRIPTOR.message_types_by_name['EntityCanvazRequest'] = _ENTITYCANVAZREQUEST
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
Artist = _reflection.GeneratedProtocolMessageType(
'Artist',
(_message.Message, ),
{
'DESCRIPTOR': _ARTIST,
'__module__': 'canvaz_pb2'
# @@protoc_insertion_point(class_scope:com.spotify.canvazcache.Artist)
})
_sym_db.RegisterMessage(Artist)
EntityCanvazResponse = _reflection.GeneratedProtocolMessageType(
'EntityCanvazResponse',
(_message.Message, ),
{
'Canvaz':
_reflection.GeneratedProtocolMessageType(
'Canvaz',
(_message.Message, ),
{
'DESCRIPTOR': _ENTITYCANVAZRESPONSE_CANVAZ,
'__module__': 'canvaz_pb2'
# @@protoc_insertion_point(class_scope:com.spotify.canvazcache.EntityCanvazResponse.Canvaz)
}),
'DESCRIPTOR':
_ENTITYCANVAZRESPONSE,
'__module__':
'canvaz_pb2'
# @@protoc_insertion_point(class_scope:com.spotify.canvazcache.EntityCanvazResponse)
})
_sym_db.RegisterMessage(EntityCanvazResponse)
_sym_db.RegisterMessage(EntityCanvazResponse.Canvaz)
EntityCanvazRequest = _reflection.GeneratedProtocolMessageType(
'EntityCanvazRequest',
(_message.Message, ),
{
'Entity':
_reflection.GeneratedProtocolMessageType(
'Entity',
(_message.Message, ),
{
'DESCRIPTOR': _ENTITYCANVAZREQUEST_ENTITY,
'__module__': 'canvaz_pb2'
# @@protoc_insertion_point(class_scope:com.spotify.canvazcache.EntityCanvazRequest.Entity)
}),
'DESCRIPTOR':
_ENTITYCANVAZREQUEST,
'__module__':
'canvaz_pb2'
# @@protoc_insertion_point(class_scope:com.spotify.canvazcache.EntityCanvazRequest)
})
_sym_db.RegisterMessage(EntityCanvazRequest)
_sym_db.RegisterMessage(EntityCanvazRequest.Entity)
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -0,0 +1,229 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: context_page.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
import context_track_pb2 as context__track__pb2
DESCRIPTOR = _descriptor.FileDescriptor(
name='context_page.proto',
package='spotify.player.proto',
syntax='proto2',
serialized_options=b'\n\023com.spotify.contextH\002',
create_key=_descriptor._internal_create_key,
serialized_pb=
b'\n\x12\x63ontext_page.proto\x12\x14spotify.player.proto\x1a\x13\x63ontext_track.proto\"\xef\x01\n\x0b\x43ontextPage\x12\x10\n\x08page_url\x18\x01 \x01(\t\x12\x15\n\rnext_page_url\x18\x02 \x01(\t\x12\x41\n\x08metadata\x18\x03 \x03(\x0b\x32/.spotify.player.proto.ContextPage.MetadataEntry\x12\x32\n\x06tracks\x18\x04 \x03(\x0b\x32\".spotify.player.proto.ContextTrack\x12\x0f\n\x07loading\x18\x05 \x01(\x08\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x17\n\x13\x63om.spotify.contextH\x02',
dependencies=[
context__track__pb2.DESCRIPTOR,
])
_CONTEXTPAGE_METADATAENTRY = _descriptor.Descriptor(
name='MetadataEntry',
full_name='spotify.player.proto.ContextPage.MetadataEntry',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='key',
full_name='spotify.player.proto.ContextPage.MetadataEntry.key',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='value',
full_name='spotify.player.proto.ContextPage.MetadataEntry.value',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=b'8\001',
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=258,
serialized_end=305,
)
_CONTEXTPAGE = _descriptor.Descriptor(
name='ContextPage',
full_name='spotify.player.proto.ContextPage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='page_url',
full_name='spotify.player.proto.ContextPage.page_url',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='next_page_url',
full_name='spotify.player.proto.ContextPage.next_page_url',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='metadata',
full_name='spotify.player.proto.ContextPage.metadata',
index=2,
number=3,
type=11,
cpp_type=10,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='tracks',
full_name='spotify.player.proto.ContextPage.tracks',
index=3,
number=4,
type=11,
cpp_type=10,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='loading',
full_name='spotify.player.proto.ContextPage.loading',
index=4,
number=5,
type=8,
cpp_type=7,
label=1,
has_default_value=False,
default_value=False,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[
_CONTEXTPAGE_METADATAENTRY,
],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=66,
serialized_end=305,
)
_CONTEXTPAGE_METADATAENTRY.containing_type = _CONTEXTPAGE
_CONTEXTPAGE.fields_by_name[
'metadata'].message_type = _CONTEXTPAGE_METADATAENTRY
_CONTEXTPAGE.fields_by_name[
'tracks'].message_type = context__track__pb2._CONTEXTTRACK
DESCRIPTOR.message_types_by_name['ContextPage'] = _CONTEXTPAGE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
ContextPage = _reflection.GeneratedProtocolMessageType(
'ContextPage',
(_message.Message, ),
{
'MetadataEntry':
_reflection.GeneratedProtocolMessageType(
'MetadataEntry',
(_message.Message, ),
{
'DESCRIPTOR': _CONTEXTPAGE_METADATAENTRY,
'__module__': 'context_page_pb2'
# @@protoc_insertion_point(class_scope:spotify.player.proto.ContextPage.MetadataEntry)
}),
'DESCRIPTOR':
_CONTEXTPAGE,
'__module__':
'context_page_pb2'
# @@protoc_insertion_point(class_scope:spotify.player.proto.ContextPage)
})
_sym_db.RegisterMessage(ContextPage)
_sym_db.RegisterMessage(ContextPage.MetadataEntry)
DESCRIPTOR._options = None
_CONTEXTPAGE_METADATAENTRY._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -0,0 +1,249 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: context.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
import context_page_pb2 as context__page__pb2
import restrictions_pb2 as restrictions__pb2
DESCRIPTOR = _descriptor.FileDescriptor(
name='context.proto',
package='spotify.player.proto',
syntax='proto2',
serialized_options=b'\n\023com.spotify.contextH\002',
create_key=_descriptor._internal_create_key,
serialized_pb=
b'\n\rcontext.proto\x12\x14spotify.player.proto\x1a\x12\x63ontext_page.proto\x1a\x12restrictions.proto\"\x90\x02\n\x07\x43ontext\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\x12=\n\x08metadata\x18\x03 \x03(\x0b\x32+.spotify.player.proto.Context.MetadataEntry\x12\x38\n\x0crestrictions\x18\x04 \x01(\x0b\x32\".spotify.player.proto.Restrictions\x12\x30\n\x05pages\x18\x05 \x03(\x0b\x32!.spotify.player.proto.ContextPage\x12\x0f\n\x07loading\x18\x06 \x01(\x08\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x17\n\x13\x63om.spotify.contextH\x02',
dependencies=[
context__page__pb2.DESCRIPTOR,
restrictions__pb2.DESCRIPTOR,
])
_CONTEXT_METADATAENTRY = _descriptor.Descriptor(
name='MetadataEntry',
full_name='spotify.player.proto.Context.MetadataEntry',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='key',
full_name='spotify.player.proto.Context.MetadataEntry.key',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='value',
full_name='spotify.player.proto.Context.MetadataEntry.value',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=b'8\001',
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=305,
serialized_end=352,
)
_CONTEXT = _descriptor.Descriptor(
name='Context',
full_name='spotify.player.proto.Context',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='uri',
full_name='spotify.player.proto.Context.uri',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='url',
full_name='spotify.player.proto.Context.url',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='metadata',
full_name='spotify.player.proto.Context.metadata',
index=2,
number=3,
type=11,
cpp_type=10,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='restrictions',
full_name='spotify.player.proto.Context.restrictions',
index=3,
number=4,
type=11,
cpp_type=10,
label=1,
has_default_value=False,
default_value=None,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='pages',
full_name='spotify.player.proto.Context.pages',
index=4,
number=5,
type=11,
cpp_type=10,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='loading',
full_name='spotify.player.proto.Context.loading',
index=5,
number=6,
type=8,
cpp_type=7,
label=1,
has_default_value=False,
default_value=False,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[
_CONTEXT_METADATAENTRY,
],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=80,
serialized_end=352,
)
_CONTEXT_METADATAENTRY.containing_type = _CONTEXT
_CONTEXT.fields_by_name['metadata'].message_type = _CONTEXT_METADATAENTRY
_CONTEXT.fields_by_name[
'restrictions'].message_type = restrictions__pb2._RESTRICTIONS
_CONTEXT.fields_by_name['pages'].message_type = context__page__pb2._CONTEXTPAGE
DESCRIPTOR.message_types_by_name['Context'] = _CONTEXT
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
Context = _reflection.GeneratedProtocolMessageType(
'Context',
(_message.Message, ),
{
'MetadataEntry':
_reflection.GeneratedProtocolMessageType(
'MetadataEntry',
(_message.Message, ),
{
'DESCRIPTOR': _CONTEXT_METADATAENTRY,
'__module__': 'context_pb2'
# @@protoc_insertion_point(class_scope:spotify.player.proto.Context.MetadataEntry)
}),
'DESCRIPTOR':
_CONTEXT,
'__module__':
'context_pb2'
# @@protoc_insertion_point(class_scope:spotify.player.proto.Context)
})
_sym_db.RegisterMessage(Context)
_sym_db.RegisterMessage(Context.MetadataEntry)
DESCRIPTOR._options = None
_CONTEXT_METADATAENTRY._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -0,0 +1,206 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: context_player_options.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='context_player_options.proto',
package='spotify.player.proto',
syntax='proto2',
serialized_options=b'\n\023com.spotify.contextH\002',
create_key=_descriptor._internal_create_key,
serialized_pb=
b'\n\x1c\x63ontext_player_options.proto\x12\x14spotify.player.proto\"e\n\x14\x43ontextPlayerOptions\x12\x19\n\x11shuffling_context\x18\x01 \x01(\x08\x12\x19\n\x11repeating_context\x18\x02 \x01(\x08\x12\x17\n\x0frepeating_track\x18\x03 \x01(\x08\"m\n\x1c\x43ontextPlayerOptionOverrides\x12\x19\n\x11shuffling_context\x18\x01 \x01(\x08\x12\x19\n\x11repeating_context\x18\x02 \x01(\x08\x12\x17\n\x0frepeating_track\x18\x03 \x01(\x08\x42\x17\n\x13\x63om.spotify.contextH\x02'
)
_CONTEXTPLAYEROPTIONS = _descriptor.Descriptor(
name='ContextPlayerOptions',
full_name='spotify.player.proto.ContextPlayerOptions',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='shuffling_context',
full_name=
'spotify.player.proto.ContextPlayerOptions.shuffling_context',
index=0,
number=1,
type=8,
cpp_type=7,
label=1,
has_default_value=False,
default_value=False,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='repeating_context',
full_name=
'spotify.player.proto.ContextPlayerOptions.repeating_context',
index=1,
number=2,
type=8,
cpp_type=7,
label=1,
has_default_value=False,
default_value=False,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='repeating_track',
full_name=
'spotify.player.proto.ContextPlayerOptions.repeating_track',
index=2,
number=3,
type=8,
cpp_type=7,
label=1,
has_default_value=False,
default_value=False,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=54,
serialized_end=155,
)
_CONTEXTPLAYEROPTIONOVERRIDES = _descriptor.Descriptor(
name='ContextPlayerOptionOverrides',
full_name='spotify.player.proto.ContextPlayerOptionOverrides',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='shuffling_context',
full_name=
'spotify.player.proto.ContextPlayerOptionOverrides.shuffling_context',
index=0,
number=1,
type=8,
cpp_type=7,
label=1,
has_default_value=False,
default_value=False,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='repeating_context',
full_name=
'spotify.player.proto.ContextPlayerOptionOverrides.repeating_context',
index=1,
number=2,
type=8,
cpp_type=7,
label=1,
has_default_value=False,
default_value=False,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='repeating_track',
full_name=
'spotify.player.proto.ContextPlayerOptionOverrides.repeating_track',
index=2,
number=3,
type=8,
cpp_type=7,
label=1,
has_default_value=False,
default_value=False,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=157,
serialized_end=266,
)
DESCRIPTOR.message_types_by_name[
'ContextPlayerOptions'] = _CONTEXTPLAYEROPTIONS
DESCRIPTOR.message_types_by_name[
'ContextPlayerOptionOverrides'] = _CONTEXTPLAYEROPTIONOVERRIDES
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
ContextPlayerOptions = _reflection.GeneratedProtocolMessageType(
'ContextPlayerOptions',
(_message.Message, ),
{
'DESCRIPTOR': _CONTEXTPLAYEROPTIONS,
'__module__': 'context_player_options_pb2'
# @@protoc_insertion_point(class_scope:spotify.player.proto.ContextPlayerOptions)
})
_sym_db.RegisterMessage(ContextPlayerOptions)
ContextPlayerOptionOverrides = _reflection.GeneratedProtocolMessageType(
'ContextPlayerOptionOverrides',
(_message.Message, ),
{
'DESCRIPTOR': _CONTEXTPLAYEROPTIONOVERRIDES,
'__module__': 'context_player_options_pb2'
# @@protoc_insertion_point(class_scope:spotify.player.proto.ContextPlayerOptionOverrides)
})
_sym_db.RegisterMessage(ContextPlayerOptionOverrides)
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -0,0 +1,205 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: context_track.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='context_track.proto',
package='spotify.player.proto',
syntax='proto2',
serialized_options=b'\n\023com.spotify.contextH\002',
create_key=_descriptor._internal_create_key,
serialized_pb=
b'\n\x13\x63ontext_track.proto\x12\x14spotify.player.proto\"\xaa\x01\n\x0c\x43ontextTrack\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x0b\n\x03uid\x18\x02 \x01(\t\x12\x0b\n\x03gid\x18\x03 \x01(\x0c\x12\x42\n\x08metadata\x18\x04 \x03(\x0b\x32\x30.spotify.player.proto.ContextTrack.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x17\n\x13\x63om.spotify.contextH\x02'
)
_CONTEXTTRACK_METADATAENTRY = _descriptor.Descriptor(
name='MetadataEntry',
full_name='spotify.player.proto.ContextTrack.MetadataEntry',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='key',
full_name='spotify.player.proto.ContextTrack.MetadataEntry.key',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='value',
full_name='spotify.player.proto.ContextTrack.MetadataEntry.value',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=b'8\001',
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=169,
serialized_end=216,
)
_CONTEXTTRACK = _descriptor.Descriptor(
name='ContextTrack',
full_name='spotify.player.proto.ContextTrack',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='uri',
full_name='spotify.player.proto.ContextTrack.uri',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='uid',
full_name='spotify.player.proto.ContextTrack.uid',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='gid',
full_name='spotify.player.proto.ContextTrack.gid',
index=2,
number=3,
type=12,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"",
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='metadata',
full_name='spotify.player.proto.ContextTrack.metadata',
index=3,
number=4,
type=11,
cpp_type=10,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[
_CONTEXTTRACK_METADATAENTRY,
],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=46,
serialized_end=216,
)
_CONTEXTTRACK_METADATAENTRY.containing_type = _CONTEXTTRACK
_CONTEXTTRACK.fields_by_name[
'metadata'].message_type = _CONTEXTTRACK_METADATAENTRY
DESCRIPTOR.message_types_by_name['ContextTrack'] = _CONTEXTTRACK
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
ContextTrack = _reflection.GeneratedProtocolMessageType(
'ContextTrack',
(_message.Message, ),
{
'MetadataEntry':
_reflection.GeneratedProtocolMessageType(
'MetadataEntry',
(_message.Message, ),
{
'DESCRIPTOR': _CONTEXTTRACK_METADATAENTRY,
'__module__': 'context_track_pb2'
# @@protoc_insertion_point(class_scope:spotify.player.proto.ContextTrack.MetadataEntry)
}),
'DESCRIPTOR':
_CONTEXTTRACK,
'__module__':
'context_track_pb2'
# @@protoc_insertion_point(class_scope:spotify.player.proto.ContextTrack)
})
_sym_db.RegisterMessage(ContextTrack)
_sym_db.RegisterMessage(ContextTrack.MetadataEntry)
DESCRIPTOR._options = None
_CONTEXTTRACK_METADATAENTRY._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -0,0 +1,184 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: play_origin.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='play_origin.proto',
package='spotify.player.proto',
syntax='proto2',
serialized_options=b'\n\023com.spotify.contextH\002',
create_key=_descriptor._internal_create_key,
serialized_pb=
b'\n\x11play_origin.proto\x12\x14spotify.player.proto\"\xbf\x01\n\nPlayOrigin\x12\x1a\n\x12\x66\x65\x61ture_identifier\x18\x01 \x01(\t\x12\x17\n\x0f\x66\x65\x61ture_version\x18\x02 \x01(\t\x12\x10\n\x08view_uri\x18\x03 \x01(\t\x12\x19\n\x11\x65xternal_referrer\x18\x04 \x01(\t\x12\x1b\n\x13referrer_identifier\x18\x05 \x01(\t\x12\x19\n\x11\x64\x65vice_identifier\x18\x06 \x01(\t\x12\x17\n\x0f\x66\x65\x61ture_classes\x18\x07 \x03(\tB\x17\n\x13\x63om.spotify.contextH\x02'
)
_PLAYORIGIN = _descriptor.Descriptor(
name='PlayOrigin',
full_name='spotify.player.proto.PlayOrigin',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='feature_identifier',
full_name='spotify.player.proto.PlayOrigin.feature_identifier',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='feature_version',
full_name='spotify.player.proto.PlayOrigin.feature_version',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='view_uri',
full_name='spotify.player.proto.PlayOrigin.view_uri',
index=2,
number=3,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='external_referrer',
full_name='spotify.player.proto.PlayOrigin.external_referrer',
index=3,
number=4,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='referrer_identifier',
full_name='spotify.player.proto.PlayOrigin.referrer_identifier',
index=4,
number=5,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='device_identifier',
full_name='spotify.player.proto.PlayOrigin.device_identifier',
index=5,
number=6,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='feature_classes',
full_name='spotify.player.proto.PlayOrigin.feature_classes',
index=6,
number=7,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=44,
serialized_end=235,
)
DESCRIPTOR.message_types_by_name['PlayOrigin'] = _PLAYORIGIN
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
PlayOrigin = _reflection.GeneratedProtocolMessageType(
'PlayOrigin',
(_message.Message, ),
{
'DESCRIPTOR': _PLAYORIGIN,
'__module__': 'play_origin_pb2'
# @@protoc_insertion_point(class_scope:spotify.player.proto.PlayOrigin)
})
_sym_db.RegisterMessage(PlayOrigin)
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: playback.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
import context_track_pb2 as context__track__pb2
DESCRIPTOR = _descriptor.FileDescriptor(
name='playback.proto',
package='spotify.player.proto.transfer',
syntax='proto2',
serialized_options=b'\n\024com.spotify.transferH\002',
create_key=_descriptor._internal_create_key,
serialized_pb=
b'\n\x0eplayback.proto\x12\x1dspotify.player.proto.transfer\x1a\x13\x63ontext_track.proto\"\xa5\x01\n\x08Playback\x12\x11\n\ttimestamp\x18\x01 \x01(\x03\x12 \n\x18position_as_of_timestamp\x18\x02 \x01(\x05\x12\x16\n\x0eplayback_speed\x18\x03 \x01(\x01\x12\x11\n\tis_paused\x18\x04 \x01(\x08\x12\x39\n\rcurrent_track\x18\x05 \x01(\x0b\x32\".spotify.player.proto.ContextTrackB\x18\n\x14\x63om.spotify.transferH\x02',
dependencies=[
context__track__pb2.DESCRIPTOR,
])
_PLAYBACK = _descriptor.Descriptor(
name='Playback',
full_name='spotify.player.proto.transfer.Playback',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='timestamp',
full_name='spotify.player.proto.transfer.Playback.timestamp',
index=0,
number=1,
type=3,
cpp_type=2,
label=1,
has_default_value=False,
default_value=0,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='position_as_of_timestamp',
full_name=
'spotify.player.proto.transfer.Playback.position_as_of_timestamp',
index=1,
number=2,
type=5,
cpp_type=1,
label=1,
has_default_value=False,
default_value=0,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='playback_speed',
full_name='spotify.player.proto.transfer.Playback.playback_speed',
index=2,
number=3,
type=1,
cpp_type=5,
label=1,
has_default_value=False,
default_value=float(0),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='is_paused',
full_name='spotify.player.proto.transfer.Playback.is_paused',
index=3,
number=4,
type=8,
cpp_type=7,
label=1,
has_default_value=False,
default_value=False,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='current_track',
full_name='spotify.player.proto.transfer.Playback.current_track',
index=4,
number=5,
type=11,
cpp_type=10,
label=1,
has_default_value=False,
default_value=None,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=71,
serialized_end=236,
)
_PLAYBACK.fields_by_name[
'current_track'].message_type = context__track__pb2._CONTEXTTRACK
DESCRIPTOR.message_types_by_name['Playback'] = _PLAYBACK
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
Playback = _reflection.GeneratedProtocolMessageType(
'Playback',
(_message.Message, ),
{
'DESCRIPTOR': _PLAYBACK,
'__module__': 'playback_pb2'
# @@protoc_insertion_point(class_scope:spotify.player.proto.transfer.Playback)
})
_sym_db.RegisterMessage(Playback)
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,440 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: playlist_annotate3.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='playlist_annotate3.proto',
package='spotify_playlist_annotate3.proto',
syntax='proto2',
serialized_options=b'\n\036com.spotify.playlist_annotate3H\002',
create_key=_descriptor._internal_create_key,
serialized_pb=
b'\n\x18playlist_annotate3.proto\x12 spotify_playlist_annotate3.proto\"a\n\x0fTakedownRequest\x12N\n\x12\x61\x62use_report_state\x18\x01 \x01(\x0e\x32\x32.spotify_playlist_annotate3.proto.AbuseReportState\"9\n\x0f\x41nnotateRequest\x12\x13\n\x0b\x64\x65scription\x18\x01 \x01(\t\x12\x11\n\timage_uri\x18\x02 \x01(\t\"5\n\x11TranscodedPicture\x12\x13\n\x0btarget_name\x18\x01 \x01(\t\x12\x0b\n\x03uri\x18\x02 \x01(\t\"\xf4\x02\n\x12PlaylistAnnotation\x12\x13\n\x0b\x64\x65scription\x18\x01 \x01(\t\x12\x0f\n\x07picture\x18\x02 \x01(\t\x12i\n\x1a\x64\x65precated_render_features\x18\x03 \x01(\x0e\x32\x30.spotify_playlist_annotate3.proto.RenderFeatures:\x0fNORMAL_FEATURESB\x02\x18\x01\x12O\n\x12transcoded_picture\x18\x04 \x03(\x0b\x32\x33.spotify_playlist_annotate3.proto.TranscodedPicture\x12(\n\x1ais_abuse_reporting_enabled\x18\x06 \x01(\x08:\x04true\x12R\n\x12\x61\x62use_report_state\x18\x07 \x01(\x0e\x32\x32.spotify_playlist_annotate3.proto.AbuseReportState:\x02OK*<\n\x0eRenderFeatures\x12\x13\n\x0fNORMAL_FEATURES\x10\x01\x12\x15\n\x11\x45XTENDED_FEATURES\x10\x02**\n\x10\x41\x62useReportState\x12\x06\n\x02OK\x10\x00\x12\x0e\n\nTAKEN_DOWN\x10\x01\x42\"\n\x1e\x63om.spotify.playlist_annotate3H\x02'
)
_RENDERFEATURES = _descriptor.EnumDescriptor(
name='RenderFeatures',
full_name='spotify_playlist_annotate3.proto.RenderFeatures',
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name='NORMAL_FEATURES',
index=0,
number=1,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='EXTENDED_FEATURES',
index=1,
number=2,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
],
containing_type=None,
serialized_options=None,
serialized_start=650,
serialized_end=710,
)
_sym_db.RegisterEnumDescriptor(_RENDERFEATURES)
RenderFeatures = enum_type_wrapper.EnumTypeWrapper(_RENDERFEATURES)
_ABUSEREPORTSTATE = _descriptor.EnumDescriptor(
name='AbuseReportState',
full_name='spotify_playlist_annotate3.proto.AbuseReportState',
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name='OK',
index=0,
number=0,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='TAKEN_DOWN',
index=1,
number=1,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
],
containing_type=None,
serialized_options=None,
serialized_start=712,
serialized_end=754,
)
_sym_db.RegisterEnumDescriptor(_ABUSEREPORTSTATE)
AbuseReportState = enum_type_wrapper.EnumTypeWrapper(_ABUSEREPORTSTATE)
NORMAL_FEATURES = 1
EXTENDED_FEATURES = 2
OK = 0
TAKEN_DOWN = 1
_TAKEDOWNREQUEST = _descriptor.Descriptor(
name='TakedownRequest',
full_name='spotify_playlist_annotate3.proto.TakedownRequest',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='abuse_report_state',
full_name=
'spotify_playlist_annotate3.proto.TakedownRequest.abuse_report_state',
index=0,
number=1,
type=14,
cpp_type=8,
label=1,
has_default_value=False,
default_value=0,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=62,
serialized_end=159,
)
_ANNOTATEREQUEST = _descriptor.Descriptor(
name='AnnotateRequest',
full_name='spotify_playlist_annotate3.proto.AnnotateRequest',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='description',
full_name=
'spotify_playlist_annotate3.proto.AnnotateRequest.description',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='image_uri',
full_name=
'spotify_playlist_annotate3.proto.AnnotateRequest.image_uri',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=161,
serialized_end=218,
)
_TRANSCODEDPICTURE = _descriptor.Descriptor(
name='TranscodedPicture',
full_name='spotify_playlist_annotate3.proto.TranscodedPicture',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='target_name',
full_name=
'spotify_playlist_annotate3.proto.TranscodedPicture.target_name',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='uri',
full_name='spotify_playlist_annotate3.proto.TranscodedPicture.uri',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=220,
serialized_end=273,
)
_PLAYLISTANNOTATION = _descriptor.Descriptor(
name='PlaylistAnnotation',
full_name='spotify_playlist_annotate3.proto.PlaylistAnnotation',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='description',
full_name=
'spotify_playlist_annotate3.proto.PlaylistAnnotation.description',
index=0,
number=1,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='picture',
full_name=
'spotify_playlist_annotate3.proto.PlaylistAnnotation.picture',
index=1,
number=2,
type=9,
cpp_type=9,
label=1,
has_default_value=False,
default_value=b"".decode('utf-8'),
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='deprecated_render_features',
full_name=
'spotify_playlist_annotate3.proto.PlaylistAnnotation.deprecated_render_features',
index=2,
number=3,
type=14,
cpp_type=8,
label=1,
has_default_value=True,
default_value=1,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=b'\030\001',
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='transcoded_picture',
full_name=
'spotify_playlist_annotate3.proto.PlaylistAnnotation.transcoded_picture',
index=3,
number=4,
type=11,
cpp_type=10,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='is_abuse_reporting_enabled',
full_name=
'spotify_playlist_annotate3.proto.PlaylistAnnotation.is_abuse_reporting_enabled',
index=4,
number=6,
type=8,
cpp_type=7,
label=1,
has_default_value=True,
default_value=True,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='abuse_report_state',
full_name=
'spotify_playlist_annotate3.proto.PlaylistAnnotation.abuse_report_state',
index=5,
number=7,
type=14,
cpp_type=8,
label=1,
has_default_value=True,
default_value=0,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=276,
serialized_end=648,
)
_TAKEDOWNREQUEST.fields_by_name[
'abuse_report_state'].enum_type = _ABUSEREPORTSTATE
_PLAYLISTANNOTATION.fields_by_name[
'deprecated_render_features'].enum_type = _RENDERFEATURES
_PLAYLISTANNOTATION.fields_by_name[
'transcoded_picture'].message_type = _TRANSCODEDPICTURE
_PLAYLISTANNOTATION.fields_by_name[
'abuse_report_state'].enum_type = _ABUSEREPORTSTATE
DESCRIPTOR.message_types_by_name['TakedownRequest'] = _TAKEDOWNREQUEST
DESCRIPTOR.message_types_by_name['AnnotateRequest'] = _ANNOTATEREQUEST
DESCRIPTOR.message_types_by_name['TranscodedPicture'] = _TRANSCODEDPICTURE
DESCRIPTOR.message_types_by_name['PlaylistAnnotation'] = _PLAYLISTANNOTATION
DESCRIPTOR.enum_types_by_name['RenderFeatures'] = _RENDERFEATURES
DESCRIPTOR.enum_types_by_name['AbuseReportState'] = _ABUSEREPORTSTATE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
TakedownRequest = _reflection.GeneratedProtocolMessageType(
'TakedownRequest',
(_message.Message, ),
{
'DESCRIPTOR': _TAKEDOWNREQUEST,
'__module__': 'playlist_annotate3_pb2'
# @@protoc_insertion_point(class_scope:spotify_playlist_annotate3.proto.TakedownRequest)
})
_sym_db.RegisterMessage(TakedownRequest)
AnnotateRequest = _reflection.GeneratedProtocolMessageType(
'AnnotateRequest',
(_message.Message, ),
{
'DESCRIPTOR': _ANNOTATEREQUEST,
'__module__': 'playlist_annotate3_pb2'
# @@protoc_insertion_point(class_scope:spotify_playlist_annotate3.proto.AnnotateRequest)
})
_sym_db.RegisterMessage(AnnotateRequest)
TranscodedPicture = _reflection.GeneratedProtocolMessageType(
'TranscodedPicture',
(_message.Message, ),
{
'DESCRIPTOR': _TRANSCODEDPICTURE,
'__module__': 'playlist_annotate3_pb2'
# @@protoc_insertion_point(class_scope:spotify_playlist_annotate3.proto.TranscodedPicture)
})
_sym_db.RegisterMessage(TranscodedPicture)
PlaylistAnnotation = _reflection.GeneratedProtocolMessageType(
'PlaylistAnnotation',
(_message.Message, ),
{
'DESCRIPTOR': _PLAYLISTANNOTATION,
'__module__': 'playlist_annotate3_pb2'
# @@protoc_insertion_point(class_scope:spotify_playlist_annotate3.proto.PlaylistAnnotation)
})
_sym_db.RegisterMessage(PlaylistAnnotation)
DESCRIPTOR._options = None
_PLAYLISTANNOTATION.fields_by_name[
'deprecated_render_features']._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: queue.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
import context_track_pb2 as context__track__pb2
DESCRIPTOR = _descriptor.FileDescriptor(
name='queue.proto',
package='spotify.player.proto.transfer',
syntax='proto2',
serialized_options=b'\n\024com.spotify.transferH\002',
create_key=_descriptor._internal_create_key,
serialized_pb=
b'\n\x0bqueue.proto\x12\x1dspotify.player.proto.transfer\x1a\x13\x63ontext_track.proto\"U\n\x05Queue\x12\x32\n\x06tracks\x18\x01 \x03(\x0b\x32\".spotify.player.proto.ContextTrack\x12\x18\n\x10is_playing_queue\x18\x02 \x01(\x08\x42\x18\n\x14\x63om.spotify.transferH\x02',
dependencies=[
context__track__pb2.DESCRIPTOR,
])
_QUEUE = _descriptor.Descriptor(
name='Queue',
full_name='spotify.player.proto.transfer.Queue',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='tracks',
full_name='spotify.player.proto.transfer.Queue.tracks',
index=0,
number=1,
type=11,
cpp_type=10,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='is_playing_queue',
full_name='spotify.player.proto.transfer.Queue.is_playing_queue',
index=1,
number=2,
type=8,
cpp_type=7,
label=1,
has_default_value=False,
default_value=False,
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=67,
serialized_end=152,
)
_QUEUE.fields_by_name[
'tracks'].message_type = context__track__pb2._CONTEXTTRACK
DESCRIPTOR.message_types_by_name['Queue'] = _QUEUE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
Queue = _reflection.GeneratedProtocolMessageType(
'Queue',
(_message.Message, ),
{
'DESCRIPTOR': _QUEUE,
'__module__': 'queue_pb2'
# @@protoc_insertion_point(class_scope:spotify.player.proto.transfer.Queue)
})
_sym_db.RegisterMessage(Queue)
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

View File

@@ -0,0 +1,457 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: restrictions.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='restrictions.proto',
package='spotify.player.proto',
syntax='proto2',
serialized_options=b'\n\023com.spotify.contextH\002',
create_key=_descriptor._internal_create_key,
serialized_pb=
b'\n\x12restrictions.proto\x12\x14spotify.player.proto\"\xbb\x07\n\x0cRestrictions\x12 \n\x18\x64isallow_pausing_reasons\x18\x01 \x03(\t\x12!\n\x19\x64isallow_resuming_reasons\x18\x02 \x03(\t\x12 \n\x18\x64isallow_seeking_reasons\x18\x03 \x03(\t\x12%\n\x1d\x64isallow_peeking_prev_reasons\x18\x04 \x03(\t\x12%\n\x1d\x64isallow_peeking_next_reasons\x18\x05 \x03(\t\x12&\n\x1e\x64isallow_skipping_prev_reasons\x18\x06 \x03(\t\x12&\n\x1e\x64isallow_skipping_next_reasons\x18\x07 \x03(\t\x12\x30\n(disallow_toggling_repeat_context_reasons\x18\x08 \x03(\t\x12.\n&disallow_toggling_repeat_track_reasons\x18\t \x03(\t\x12)\n!disallow_toggling_shuffle_reasons\x18\n \x03(\t\x12\"\n\x1a\x64isallow_set_queue_reasons\x18\x0b \x03(\t\x12.\n&disallow_interrupting_playback_reasons\x18\x0c \x03(\t\x12.\n&disallow_transferring_playback_reasons\x18\r \x03(\t\x12\'\n\x1f\x64isallow_remote_control_reasons\x18\x0e \x03(\t\x12\x33\n+disallow_inserting_into_next_tracks_reasons\x18\x0f \x03(\t\x12\x36\n.disallow_inserting_into_context_tracks_reasons\x18\x10 \x03(\t\x12\x32\n*disallow_reordering_in_next_tracks_reasons\x18\x11 \x03(\t\x12\x35\n-disallow_reordering_in_context_tracks_reasons\x18\x12 \x03(\t\x12\x32\n*disallow_removing_from_next_tracks_reasons\x18\x13 \x03(\t\x12\x35\n-disallow_removing_from_context_tracks_reasons\x18\x14 \x03(\t\x12)\n!disallow_updating_context_reasons\x18\x15 \x03(\tB\x17\n\x13\x63om.spotify.contextH\x02'
)
_RESTRICTIONS = _descriptor.Descriptor(
name='Restrictions',
full_name='spotify.player.proto.Restrictions',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='disallow_pausing_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_pausing_reasons',
index=0,
number=1,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_resuming_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_resuming_reasons',
index=1,
number=2,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_seeking_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_seeking_reasons',
index=2,
number=3,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_peeking_prev_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_peeking_prev_reasons',
index=3,
number=4,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_peeking_next_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_peeking_next_reasons',
index=4,
number=5,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_skipping_prev_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_skipping_prev_reasons',
index=5,
number=6,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_skipping_next_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_skipping_next_reasons',
index=6,
number=7,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_toggling_repeat_context_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_toggling_repeat_context_reasons',
index=7,
number=8,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_toggling_repeat_track_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_toggling_repeat_track_reasons',
index=8,
number=9,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_toggling_shuffle_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_toggling_shuffle_reasons',
index=9,
number=10,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_set_queue_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_set_queue_reasons',
index=10,
number=11,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_interrupting_playback_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_interrupting_playback_reasons',
index=11,
number=12,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_transferring_playback_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_transferring_playback_reasons',
index=12,
number=13,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_remote_control_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_remote_control_reasons',
index=13,
number=14,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_inserting_into_next_tracks_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_inserting_into_next_tracks_reasons',
index=14,
number=15,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_inserting_into_context_tracks_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_inserting_into_context_tracks_reasons',
index=15,
number=16,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_reordering_in_next_tracks_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_reordering_in_next_tracks_reasons',
index=16,
number=17,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_reordering_in_context_tracks_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_reordering_in_context_tracks_reasons',
index=17,
number=18,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_removing_from_next_tracks_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_removing_from_next_tracks_reasons',
index=18,
number=19,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_removing_from_context_tracks_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_removing_from_context_tracks_reasons',
index=19,
number=20,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='disallow_updating_context_reasons',
full_name=
'spotify.player.proto.Restrictions.disallow_updating_context_reasons',
index=20,
number=21,
type=9,
cpp_type=9,
label=3,
has_default_value=False,
default_value=[],
message_type=None,
enum_type=None,
containing_type=None,
is_extension=False,
extension_scope=None,
serialized_options=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key),
],
extensions=[],
nested_types=[],
enum_types=[],
serialized_options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[],
serialized_start=45,
serialized_end=1000,
)
DESCRIPTOR.message_types_by_name['Restrictions'] = _RESTRICTIONS
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
Restrictions = _reflection.GeneratedProtocolMessageType(
'Restrictions',
(_message.Message, ),
{
'DESCRIPTOR': _RESTRICTIONS,
'__module__': 'restrictions_pb2'
# @@protoc_insertion_point(class_scope:spotify.player.proto.Restrictions)
})
_sym_db.RegisterMessage(Restrictions)
DESCRIPTOR._options = None
# @@protoc_insertion_point(module_scope)

Some files were not shown because too many files have changed in this diff Show More