Merge pull request #324 from Googolplexed0/proto-ext-metadata
Implement ExtendedMetadata Method for Content Loading
This commit is contained in:
@@ -6,7 +6,7 @@ import platform
|
||||
|
||||
|
||||
class Version:
|
||||
version_name = "0.0.9"
|
||||
version_name = "0.0.10"
|
||||
|
||||
@staticmethod
|
||||
def platform() -> Platform:
|
||||
|
||||
@@ -331,7 +331,7 @@ class CdnFeedHelper:
|
||||
session: Session, track: Metadata.Track, file: Metadata.AudioFile,
|
||||
resp_or_url: typing.Union[StorageResolve.StorageResolveResponse,
|
||||
str], preload: bool,
|
||||
halt_listener: HaltListener) -> PlayableContentFeeder.LoadedStream:
|
||||
halt_listener: HaltListener) -> LoadedStream:
|
||||
if type(resp_or_url) is str:
|
||||
url = resp_or_url
|
||||
else:
|
||||
@@ -345,18 +345,17 @@ class CdnFeedHelper:
|
||||
normalization_data = NormalizationData.read(input_stream)
|
||||
if input_stream.skip(0xA7) != 0xA7:
|
||||
raise IOError("Couldn't skip 0xa7 bytes!")
|
||||
return PlayableContentFeeder.LoadedStream(
|
||||
return LoadedStream(
|
||||
track,
|
||||
streamer,
|
||||
normalization_data,
|
||||
PlayableContentFeeder.Metrics(file.file_id, preload,
|
||||
-1 if preload else audio_key_time),
|
||||
file.file_id, preload, audio_key_time
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def load_episode_external(
|
||||
session: Session, episode: Metadata.Episode,
|
||||
halt_listener: HaltListener) -> PlayableContentFeeder.LoadedStream:
|
||||
halt_listener: HaltListener) -> LoadedStream:
|
||||
resp = session.client().head(episode.external_url)
|
||||
|
||||
if resp.status_code != 200:
|
||||
@@ -368,11 +367,11 @@ class CdnFeedHelper:
|
||||
|
||||
streamer = session.cdn().stream_external_episode(
|
||||
episode, url, halt_listener)
|
||||
return PlayableContentFeeder.LoadedStream(
|
||||
return LoadedStream(
|
||||
episode,
|
||||
streamer,
|
||||
None,
|
||||
PlayableContentFeeder.Metrics(None, False, -1),
|
||||
None, False, -1
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -383,7 +382,7 @@ class CdnFeedHelper:
|
||||
resp_or_url: typing.Union[StorageResolve.StorageResolveResponse, str],
|
||||
preload: bool,
|
||||
halt_listener: HaltListener,
|
||||
) -> PlayableContentFeeder.LoadedStream:
|
||||
) -> LoadedStream:
|
||||
if type(resp_or_url) is str:
|
||||
url = resp_or_url
|
||||
else:
|
||||
@@ -397,12 +396,11 @@ class CdnFeedHelper:
|
||||
normalization_data = NormalizationData.read(input_stream)
|
||||
if input_stream.skip(0xA7) != 0xA7:
|
||||
raise IOError("Couldn't skip 0xa7 bytes!")
|
||||
return PlayableContentFeeder.LoadedStream(
|
||||
return LoadedStream(
|
||||
episode,
|
||||
streamer,
|
||||
normalization_data,
|
||||
PlayableContentFeeder.Metrics(file.file_id, preload,
|
||||
-1 if preload else audio_key_time),
|
||||
file.file_id, preload, audio_key_time
|
||||
)
|
||||
|
||||
|
||||
@@ -748,7 +746,9 @@ class PlayableContentFeeder:
|
||||
episode: Metadata.Episode, preload: bool,
|
||||
halt_lister: HaltListener):
|
||||
if track is None and episode is None:
|
||||
raise RuntimeError()
|
||||
raise RuntimeError("No content passed!")
|
||||
elif file is None:
|
||||
raise RuntimeError("Content has no audio file!")
|
||||
response = self.resolve_storage_interactive(file.file_id, preload)
|
||||
if response.result == StorageResolve.StorageResolveResponse.Result.CDN:
|
||||
if track is not None:
|
||||
@@ -778,6 +778,7 @@ class PlayableContentFeeder:
|
||||
self.logger.fatal(
|
||||
"Couldn't find any suitable audio file, available: {}".format(
|
||||
episode.audio))
|
||||
raise FeederException("Cannot find suitable audio file")
|
||||
return self.load_stream(file, None, episode, preload, halt_listener)
|
||||
|
||||
def load_track(self, track_id_or_track: typing.Union[TrackId,
|
||||
@@ -797,7 +798,7 @@ class PlayableContentFeeder:
|
||||
self.logger.fatal(
|
||||
"Couldn't find any suitable audio file, available: {}".format(
|
||||
track.file))
|
||||
raise FeederException()
|
||||
raise FeederException("Cannot find suitable audio file")
|
||||
return self.load_stream(file, track, None, preload, halt_listener)
|
||||
|
||||
def pick_alternative_if_necessary(
|
||||
@@ -848,29 +849,13 @@ class PlayableContentFeeder:
|
||||
storage_resolve_response.ParseFromString(body)
|
||||
return storage_resolve_response
|
||||
|
||||
|
||||
class LoadedStream:
|
||||
episode: Metadata.Episode
|
||||
track: Metadata.Track
|
||||
input_stream: GeneralAudioStream
|
||||
normalization_data: NormalizationData
|
||||
metrics: PlayableContentFeeder.Metrics
|
||||
|
||||
def __init__(self, track_or_episode: typing.Union[Metadata.Track,
|
||||
Metadata.Episode],
|
||||
input_stream: GeneralAudioStream,
|
||||
normalization_data: typing.Union[NormalizationData, None],
|
||||
metrics: PlayableContentFeeder.Metrics):
|
||||
if type(track_or_episode) is Metadata.Track:
|
||||
self.track = track_or_episode
|
||||
self.episode = None
|
||||
elif type(track_or_episode) is Metadata.Episode:
|
||||
self.track = None
|
||||
self.episode = track_or_episode
|
||||
else:
|
||||
raise TypeError()
|
||||
self.input_stream = input_stream
|
||||
self.normalization_data = normalization_data
|
||||
self.metrics = metrics
|
||||
metrics: Metrics
|
||||
|
||||
class Metrics:
|
||||
file_id: str
|
||||
@@ -882,9 +867,23 @@ class PlayableContentFeeder:
|
||||
self.file_id = None if file_id is None else util.bytes_to_hex(
|
||||
file_id)
|
||||
self.preloaded_audio_key = preloaded_audio_key
|
||||
self.audio_key_time = audio_key_time
|
||||
if preloaded_audio_key and audio_key_time != -1:
|
||||
raise RuntimeError()
|
||||
self.audio_key_time = -1 if preloaded_audio_key else audio_key_time
|
||||
|
||||
def __init__(self, track_or_episode: typing.Union[Metadata.Track, Metadata.Episode],
|
||||
input_stream: GeneralAudioStream,
|
||||
normalization_data: typing.Union[NormalizationData, None],
|
||||
file_id: str, preloaded_audio_key: bool, audio_key_time: int):
|
||||
if type(track_or_episode) is Metadata.Track:
|
||||
self.track = track_or_episode
|
||||
self.episode = None
|
||||
elif type(track_or_episode) is Metadata.Episode:
|
||||
self.track = None
|
||||
self.episode = track_or_episode
|
||||
else:
|
||||
raise TypeError()
|
||||
self.input_stream = input_stream
|
||||
self.normalization_data = normalization_data
|
||||
self.metrics = self.Metrics(file_id, preloaded_audio_key, audio_key_time)
|
||||
|
||||
|
||||
class StreamId:
|
||||
|
||||
@@ -57,6 +57,8 @@ from librespot.proto import Connectivity_pb2 as Connectivity
|
||||
from librespot.proto import Keyexchange_pb2 as Keyexchange
|
||||
from librespot.proto import Metadata_pb2 as Metadata
|
||||
from librespot.proto import Playlist4External_pb2 as Playlist4External
|
||||
from librespot.proto.ExtendedMetadata_pb2 import EntityRequest, BatchedEntityRequest, ExtensionQuery, BatchedExtensionResponse
|
||||
from librespot.proto.ExtensionKind_pb2 import ExtensionKind
|
||||
from librespot.proto.ExplicitContentPubsub_pb2 import UserAttributesUpdate
|
||||
from librespot.proto.spotify.login5.v3 import Login5_pb2 as Login5
|
||||
from librespot.proto.spotify.login5.v3.credentials import Credentials_pb2 as Login5Credentials
|
||||
@@ -104,20 +106,20 @@ class ApiClient(Closeable):
|
||||
self.logger.debug("Updated client token: {}".format(
|
||||
self.__client_token_str))
|
||||
|
||||
request = requests.PreparedRequest()
|
||||
request.method = method
|
||||
request.data = body
|
||||
request.headers = CaseInsensitiveDict()
|
||||
if headers is not None:
|
||||
request.headers = headers
|
||||
request.headers["Authorization"] = "Bearer {}".format(
|
||||
self.__session.tokens().get("playlist-read"))
|
||||
request.headers["client-token"] = self.__client_token_str
|
||||
if url is None:
|
||||
request.url = self.__base_url + suffix
|
||||
url = self.__base_url + suffix
|
||||
else:
|
||||
request.url = url + suffix
|
||||
return request
|
||||
url = url + suffix
|
||||
|
||||
if headers is None:
|
||||
headers = CaseInsensitiveDict()
|
||||
headers["Authorization"] = "Bearer {}".format(
|
||||
self.__session.tokens().get("playlist-read"))
|
||||
headers["client-token"] = self.__client_token_str
|
||||
|
||||
request = requests.Request(method, url, headers=headers, data=body)
|
||||
|
||||
return request.prepare()
|
||||
|
||||
def send(
|
||||
self,
|
||||
@@ -190,22 +192,36 @@ class ApiClient(Closeable):
|
||||
self.logger.warning("PUT state returned {}. headers: {}".format(
|
||||
response.status_code, response.headers))
|
||||
|
||||
def get_ext_metadata(self, extension_kind: ExtensionKind, uri: str):
|
||||
headers = CaseInsensitiveDict({"content-type": "application/x-protobuf"})
|
||||
req = EntityRequest(entity_uri=uri, query=[ExtensionQuery(extension_kind=extension_kind),])
|
||||
|
||||
response = self.send("POST", "/extended-metadata/v0/extended-metadata",
|
||||
headers, BatchedEntityRequest(entity_request=[req,]).SerializeToString())
|
||||
ApiClient.StatusCodeException.check_status(response)
|
||||
|
||||
body = response.content
|
||||
if body is None:
|
||||
raise ConnectionError("Extended Metadata request failed: No response body")
|
||||
|
||||
proto = BatchedExtensionResponse()
|
||||
proto.ParseFromString(body)
|
||||
entityextd = proto.extended_metadata.pop().extension_data.pop()
|
||||
if entityextd.header.status_code != 200:
|
||||
raise ConnectionError("Extended Metadata request failed: Status code {}".format(entityextd.header.status_code))
|
||||
mdb: bytes = entityextd.extension_data.value
|
||||
return mdb
|
||||
|
||||
def get_metadata_4_track(self, track: TrackId) -> Metadata.Track:
|
||||
"""
|
||||
|
||||
:param track: TrackId:
|
||||
|
||||
"""
|
||||
response = self.sendToUrl("GET", "https://spclient.wg.spotify.com",
|
||||
"/metadata/4/track/{}".format(track.hex_id()),
|
||||
None, None)
|
||||
ApiClient.StatusCodeException.check_status(response)
|
||||
body = response.content
|
||||
if body is None:
|
||||
raise RuntimeError()
|
||||
proto = Metadata.Track()
|
||||
proto.ParseFromString(body)
|
||||
return proto
|
||||
mdb = self.get_ext_metadata(ExtensionKind.TRACK_V4, track.to_spotify_uri())
|
||||
md = Metadata.Track()
|
||||
md.ParseFromString(mdb)
|
||||
return md
|
||||
|
||||
def get_metadata_4_episode(self, episode: EpisodeId) -> Metadata.Episode:
|
||||
"""
|
||||
@@ -213,16 +229,10 @@ class ApiClient(Closeable):
|
||||
:param episode: EpisodeId:
|
||||
|
||||
"""
|
||||
response = self.sendToUrl("GET", "https://spclient.wg.spotify.com",
|
||||
"/metadata/4/episode/{}".format(episode.hex_id()),
|
||||
None, None)
|
||||
ApiClient.StatusCodeException.check_status(response)
|
||||
body = response.content
|
||||
if body is None:
|
||||
raise IOError()
|
||||
proto = Metadata.Episode()
|
||||
proto.ParseFromString(body)
|
||||
return proto
|
||||
mdb = self.get_ext_metadata(ExtensionKind.EPISODE_V4, episode.to_spotify_uri())
|
||||
md = Metadata.Episode()
|
||||
md.ParseFromString(mdb)
|
||||
return md
|
||||
|
||||
def get_metadata_4_album(self, album: AlbumId) -> Metadata.Album:
|
||||
"""
|
||||
@@ -230,17 +240,10 @@ class ApiClient(Closeable):
|
||||
:param album: AlbumId:
|
||||
|
||||
"""
|
||||
response = self.sendToUrl("GET", "https://spclient.wg.spotify.com",
|
||||
"/metadata/4/album/{}".format(album.hex_id()),
|
||||
None, None)
|
||||
ApiClient.StatusCodeException.check_status(response)
|
||||
|
||||
body = response.content
|
||||
if body is None:
|
||||
raise IOError()
|
||||
proto = Metadata.Album()
|
||||
proto.ParseFromString(body)
|
||||
return proto
|
||||
mdb = self.get_ext_metadata(ExtensionKind.ALBUM_V4, album.to_spotify_uri())
|
||||
md = Metadata.Album()
|
||||
md.ParseFromString(mdb)
|
||||
return md
|
||||
|
||||
def get_metadata_4_artist(self, artist: ArtistId) -> Metadata.Artist:
|
||||
"""
|
||||
@@ -248,16 +251,10 @@ class ApiClient(Closeable):
|
||||
:param artist: ArtistId:
|
||||
|
||||
"""
|
||||
response = self.sendToUrl("GET", "https://spclient.wg.spotify.com",
|
||||
"/metadata/4/artist/{}".format(artist.hex_id()),
|
||||
None, None)
|
||||
ApiClient.StatusCodeException.check_status(response)
|
||||
body = response.content
|
||||
if body is None:
|
||||
raise IOError()
|
||||
proto = Metadata.Artist()
|
||||
proto.ParseFromString(body)
|
||||
return proto
|
||||
mdb = self.get_ext_metadata(ExtensionKind.ARTIST_V4, artist.to_spotify_uri())
|
||||
md = Metadata.Artist()
|
||||
md.ParseFromString(mdb)
|
||||
return md
|
||||
|
||||
def get_metadata_4_show(self, show: ShowId) -> Metadata.Show:
|
||||
"""
|
||||
@@ -265,16 +262,10 @@ class ApiClient(Closeable):
|
||||
:param show: ShowId:
|
||||
|
||||
"""
|
||||
response = self.sendToUrl("GET", "https://spclient.wg.spotify.com",
|
||||
"/metadata/4/show/{}".format(show.hex_id()), None,
|
||||
None)
|
||||
ApiClient.StatusCodeException.check_status(response)
|
||||
body = response.content
|
||||
if body is None:
|
||||
raise IOError()
|
||||
proto = Metadata.Show()
|
||||
proto.ParseFromString(body)
|
||||
return proto
|
||||
mdb = self.get_ext_metadata(ExtensionKind.SHOW_V4, show.to_spotify_uri())
|
||||
md = Metadata.Show()
|
||||
md.ParseFromString(mdb)
|
||||
return md
|
||||
|
||||
def get_playlist(self,
|
||||
_id: PlaylistId) -> Playlist4External.SelectedListContent:
|
||||
|
||||
35
librespot/proto/EntityExtensionData_pb2.py
Normal file
35
librespot/proto/EntityExtensionData_pb2.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: entity_extension_data.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf.internal import builder as _builder
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1b\x65ntity_extension_data.proto\x12\x18spotify.extendedmetadata\x1a\x19google/protobuf/any.proto\"\x8c\x01\n\x19\x45ntityExtensionDataHeader\x12\x13\n\x0bstatus_code\x18\x01 \x01(\x05\x12\x0c\n\x04\x65tag\x18\x02 \x01(\t\x12\x0e\n\x06locale\x18\x03 \x01(\t\x12\x1c\n\x14\x63\x61\x63he_ttl_in_seconds\x18\x04 \x01(\x03\x12\x1e\n\x16offline_ttl_in_seconds\x18\x05 \x01(\x03\"\x9c\x01\n\x13\x45ntityExtensionData\x12\x43\n\x06header\x18\x01 \x01(\x0b\x32\x33.spotify.extendedmetadata.EntityExtensionDataHeader\x12\x12\n\nentity_uri\x18\x02 \x01(\t\x12,\n\x0e\x65xtension_data\x18\x03 \x01(\x0b\x32\x14.google.protobuf.Any\"$\n\x0ePlainListAssoc\x12\x12\n\nentity_uri\x18\x01 \x03(\t\"\r\n\x0b\x41ssocHeader\"|\n\x05\x41ssoc\x12\x35\n\x06header\x18\x01 \x01(\x0b\x32%.spotify.extendedmetadata.AssocHeader\x12<\n\nplain_list\x18\x02 \x01(\x0b\x32(.spotify.extendedmetadata.PlainListAssocB+\n\"com.spotify.extendedmetadata.protoH\x02P\x01\xf8\x01\x01\x62\x06proto3')
|
||||
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'EntityExtensionData_pb2', globals())
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\"com.spotify.extendedmetadata.protoH\002P\001\370\001\001'
|
||||
_ENTITYEXTENSIONDATAHEADER._serialized_start=85
|
||||
_ENTITYEXTENSIONDATAHEADER._serialized_end=225
|
||||
_ENTITYEXTENSIONDATA._serialized_start=228
|
||||
_ENTITYEXTENSIONDATA._serialized_end=384
|
||||
_PLAINLISTASSOC._serialized_start=386
|
||||
_PLAINLISTASSOC._serialized_end=422
|
||||
_ASSOCHEADER._serialized_start=424
|
||||
_ASSOCHEADER._serialized_end=437
|
||||
_ASSOC._serialized_start=439
|
||||
_ASSOC._serialized_end=563
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
44
librespot/proto/ExtendedMetadata_pb2.py
Normal file
44
librespot/proto/ExtendedMetadata_pb2.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: extended_metadata.proto
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf.internal import builder as _builder
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
import librespot.proto.ExtensionKind_pb2 as extension__kind__pb2
|
||||
import librespot.proto.EntityExtensionData_pb2 as entity__extension__data__pb2
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x65xtended_metadata.proto\x12\x18spotify.extendedmetadata\x1a\x14\x65xtension_kind.proto\x1a\x1b\x65ntity_extension_data.proto\"_\n\x0e\x45xtensionQuery\x12?\n\x0e\x65xtension_kind\x18\x01 \x01(\x0e\x32\'.spotify.extendedmetadata.ExtensionKind\x12\x0c\n\x04\x65tag\x18\x02 \x01(\t\"\\\n\rEntityRequest\x12\x12\n\nentity_uri\x18\x01 \x01(\t\x12\x37\n\x05query\x18\x02 \x03(\x0b\x32(.spotify.extendedmetadata.ExtensionQuery\"Q\n\x1a\x42\x61tchedEntityRequestHeader\x12\x0f\n\x07\x63ountry\x18\x01 \x01(\t\x12\x11\n\tcatalogue\x18\x02 \x01(\t\x12\x0f\n\x07task_id\x18\x03 \x01(\x0c\"\x9d\x01\n\x14\x42\x61tchedEntityRequest\x12\x44\n\x06header\x18\x01 \x01(\x0b\x32\x34.spotify.extendedmetadata.BatchedEntityRequestHeader\x12?\n\x0e\x65ntity_request\x18\x02 \x03(\x0b\x32\'.spotify.extendedmetadata.EntityRequest\"\xbe\x01\n\x1e\x45ntityExtensionDataArrayHeader\x12\x1d\n\x15provider_error_status\x18\x01 \x01(\x05\x12\x1c\n\x14\x63\x61\x63he_ttl_in_seconds\x18\x02 \x01(\x03\x12\x1e\n\x16offline_ttl_in_seconds\x18\x03 \x01(\x03\x12?\n\x0e\x65xtension_type\x18\x04 \x01(\x0e\x32\'.spotify.extendedmetadata.ExtensionType\"\xec\x01\n\x18\x45ntityExtensionDataArray\x12H\n\x06header\x18\x01 \x01(\x0b\x32\x38.spotify.extendedmetadata.EntityExtensionDataArrayHeader\x12?\n\x0e\x65xtension_kind\x18\x02 \x01(\x0e\x32\'.spotify.extendedmetadata.ExtensionKind\x12\x45\n\x0e\x65xtension_data\x18\x03 \x03(\x0b\x32-.spotify.extendedmetadata.EntityExtensionData\" \n\x1e\x42\x61tchedExtensionResponseHeader\"\xb3\x01\n\x18\x42\x61tchedExtensionResponse\x12H\n\x06header\x18\x01 \x01(\x0b\x32\x38.spotify.extendedmetadata.BatchedExtensionResponseHeader\x12M\n\x11\x65xtended_metadata\x18\x02 \x03(\x0b\x32\x32.spotify.extendedmetadata.EntityExtensionDataArray*4\n\rExtensionType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0b\n\x07GENERIC\x10\x01\x12\t\n\x05\x41SSOC\x10\x02\x42+\n\"com.spotify.extendedmetadata.protoH\x02P\x01\xf8\x01\x01\x62\x06proto3')
|
||||
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'ExtendedMetadata_pb2', globals())
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
|
||||
DESCRIPTOR._options = None
|
||||
DESCRIPTOR._serialized_options = b'\n\"com.spotify.extendedmetadata.protoH\002P\001\370\001\001'
|
||||
_EXTENSIONTYPE._serialized_start=1186
|
||||
_EXTENSIONTYPE._serialized_end=1238
|
||||
_EXTENSIONQUERY._serialized_start=104
|
||||
_EXTENSIONQUERY._serialized_end=199
|
||||
_ENTITYREQUEST._serialized_start=201
|
||||
_ENTITYREQUEST._serialized_end=293
|
||||
_BATCHEDENTITYREQUESTHEADER._serialized_start=295
|
||||
_BATCHEDENTITYREQUESTHEADER._serialized_end=376
|
||||
_BATCHEDENTITYREQUEST._serialized_start=379
|
||||
_BATCHEDENTITYREQUEST._serialized_end=536
|
||||
_ENTITYEXTENSIONDATAARRAYHEADER._serialized_start=539
|
||||
_ENTITYEXTENSIONDATAARRAYHEADER._serialized_end=729
|
||||
_ENTITYEXTENSIONDATAARRAY._serialized_start=732
|
||||
_ENTITYEXTENSIONDATAARRAY._serialized_end=968
|
||||
_BATCHEDEXTENSIONRESPONSEHEADER._serialized_start=970
|
||||
_BATCHEDEXTENSIONRESPONSEHEADER._serialized_end=1002
|
||||
_BATCHEDEXTENSIONRESPONSE._serialized_start=1005
|
||||
_BATCHEDEXTENSIONRESPONSE._serialized_end=1184
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
26
librespot/proto/ExtensionKind_pb2.py
Normal file
26
librespot/proto/ExtensionKind_pb2.py
Normal file
File diff suppressed because one or more lines are too long
38
proto/entity_extension_data.proto
Normal file
38
proto/entity_extension_data.proto
Normal file
@@ -0,0 +1,38 @@
|
||||
// Extracted from: Spotify 1.2.52.442 (windows)
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package spotify.extendedmetadata;
|
||||
|
||||
import "google/protobuf/any.proto";
|
||||
|
||||
option cc_enable_arenas = true;
|
||||
option java_multiple_files = true;
|
||||
option optimize_for = CODE_SIZE;
|
||||
option java_package = "com.spotify.extendedmetadata.proto";
|
||||
|
||||
message EntityExtensionDataHeader {
|
||||
int32 status_code = 1;
|
||||
string etag = 2;
|
||||
string locale = 3;
|
||||
int64 cache_ttl_in_seconds = 4;
|
||||
int64 offline_ttl_in_seconds = 5;
|
||||
}
|
||||
|
||||
message EntityExtensionData {
|
||||
EntityExtensionDataHeader header = 1;
|
||||
string entity_uri = 2;
|
||||
google.protobuf.Any extension_data = 3;
|
||||
}
|
||||
|
||||
message PlainListAssoc {
|
||||
repeated string entity_uri = 1;
|
||||
}
|
||||
|
||||
message AssocHeader {
|
||||
}
|
||||
|
||||
message Assoc {
|
||||
AssocHeader header = 1;
|
||||
PlainListAssoc plain_list = 2;
|
||||
}
|
||||
59
proto/extended_metadata.proto
Normal file
59
proto/extended_metadata.proto
Normal file
@@ -0,0 +1,59 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package spotify.extendedmetadata;
|
||||
|
||||
import "extension_kind.proto";
|
||||
import "entity_extension_data.proto";
|
||||
|
||||
option cc_enable_arenas = true;
|
||||
option java_multiple_files = true;
|
||||
option optimize_for = CODE_SIZE;
|
||||
option java_package = "com.spotify.extendedmetadata.proto";
|
||||
|
||||
message ExtensionQuery {
|
||||
ExtensionKind extension_kind = 1;
|
||||
string etag = 2;
|
||||
}
|
||||
|
||||
message EntityRequest {
|
||||
string entity_uri = 1;
|
||||
repeated ExtensionQuery query = 2;
|
||||
}
|
||||
|
||||
message BatchedEntityRequestHeader {
|
||||
string country = 1;
|
||||
string catalogue = 2;
|
||||
bytes task_id = 3;
|
||||
}
|
||||
|
||||
message BatchedEntityRequest {
|
||||
BatchedEntityRequestHeader header = 1;
|
||||
repeated EntityRequest entity_request = 2;
|
||||
}
|
||||
|
||||
message EntityExtensionDataArrayHeader {
|
||||
int32 provider_error_status = 1;
|
||||
int64 cache_ttl_in_seconds = 2;
|
||||
int64 offline_ttl_in_seconds = 3;
|
||||
ExtensionType extension_type = 4;
|
||||
}
|
||||
|
||||
message EntityExtensionDataArray {
|
||||
EntityExtensionDataArrayHeader header = 1;
|
||||
ExtensionKind extension_kind = 2;
|
||||
repeated EntityExtensionData extension_data = 3;
|
||||
}
|
||||
|
||||
message BatchedExtensionResponseHeader {
|
||||
}
|
||||
|
||||
message BatchedExtensionResponse {
|
||||
BatchedExtensionResponseHeader header = 1;
|
||||
repeated EntityExtensionDataArray extended_metadata = 2;
|
||||
}
|
||||
|
||||
enum ExtensionType {
|
||||
UNKNOWN = 0;
|
||||
GENERIC = 1;
|
||||
ASSOC = 2;
|
||||
}
|
||||
209
proto/extension_kind.proto
Normal file
209
proto/extension_kind.proto
Normal file
@@ -0,0 +1,209 @@
|
||||
// Extracted from: Spotify 1.2.52.442 (windows)
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package spotify.extendedmetadata;
|
||||
|
||||
option objc_class_prefix = "SPTExtendedMetadata";
|
||||
option cc_enable_arenas = true;
|
||||
option java_multiple_files = true;
|
||||
option optimize_for = CODE_SIZE;
|
||||
option java_package = "com.spotify.extendedmetadata.proto";
|
||||
|
||||
enum ExtensionKind {
|
||||
UNKNOWN_EXTENSION = 0;
|
||||
CANVAZ = 1;
|
||||
STORYLINES = 2;
|
||||
PODCAST_TOPICS = 3;
|
||||
PODCAST_SEGMENTS = 4;
|
||||
AUDIO_FILES = 5;
|
||||
TRACK_DESCRIPTOR = 6;
|
||||
PODCAST_COUNTER = 7;
|
||||
ARTIST_V4 = 8;
|
||||
ALBUM_V4 = 9;
|
||||
TRACK_V4 = 10;
|
||||
SHOW_V4 = 11;
|
||||
EPISODE_V4 = 12;
|
||||
PODCAST_HTML_DESCRIPTION = 13;
|
||||
PODCAST_QUOTES = 14;
|
||||
USER_PROFILE = 15;
|
||||
CANVAS_V1 = 16;
|
||||
SHOW_V4_BASE = 17;
|
||||
SHOW_V4_EPISODES_ASSOC = 18;
|
||||
TRACK_DESCRIPTOR_SIGNATURES = 19;
|
||||
PODCAST_AD_SEGMENTS = 20;
|
||||
EPISODE_TRANSCRIPTS = 21;
|
||||
PODCAST_SUBSCRIPTIONS = 22;
|
||||
EXTRACTED_COLOR = 23;
|
||||
PODCAST_VIRALITY = 24;
|
||||
IMAGE_SPARKLES_HACK = 25;
|
||||
PODCAST_POPULARITY_HACK = 26;
|
||||
AUTOMIX_MODE = 27;
|
||||
CUEPOINTS = 28;
|
||||
PODCAST_POLL = 29;
|
||||
EPISODE_ACCESS = 30;
|
||||
SHOW_ACCESS = 31;
|
||||
PODCAST_QNA = 32;
|
||||
CLIPS = 33;
|
||||
SHOW_V5 = 34;
|
||||
EPISODE_V5 = 35;
|
||||
PODCAST_CTA_CARDS = 36;
|
||||
PODCAST_RATING = 37;
|
||||
DISPLAY_SEGMENTS = 38;
|
||||
GREENROOM = 39;
|
||||
USER_CREATED = 40;
|
||||
SHOW_DESCRIPTION = 41;
|
||||
SHOW_HTML_DESCRIPTION = 42;
|
||||
SHOW_PLAYABILITY = 43;
|
||||
EPISODE_DESCRIPTION = 44;
|
||||
EPISODE_HTML_DESCRIPTION = 45;
|
||||
EPISODE_PLAYABILITY = 46;
|
||||
SHOW_EPISODES_ASSOC = 47;
|
||||
CLIENT_CONFIG = 48;
|
||||
PLAYLISTABILITY = 49;
|
||||
AUDIOBOOK_V5 = 50;
|
||||
CHAPTER_V5 = 51;
|
||||
AUDIOBOOK_SPECIFICS = 52;
|
||||
EPISODE_RANKING = 53;
|
||||
HTML_DESCRIPTION = 54;
|
||||
CREATOR_CHANNEL = 55;
|
||||
AUDIOBOOK_PROVIDERS = 56;
|
||||
PLAY_TRAIT = 57;
|
||||
CONTENT_WARNING = 58;
|
||||
IMAGE_CUE = 59;
|
||||
STREAM_COUNT = 60;
|
||||
AUDIO_ATTRIBUTES = 61;
|
||||
NAVIGABLE_TRAIT = 62;
|
||||
NEXT_BEST_EPISODE = 63;
|
||||
AUDIOBOOK_PRICE = 64;
|
||||
EXPRESSIVE_PLAYLISTS = 65;
|
||||
DYNAMIC_SHOW_EPISODE = 66;
|
||||
LIVE = 67;
|
||||
SKIP_PLAYED = 68;
|
||||
AD_BREAK_FREE_PODCASTS = 69;
|
||||
ASSOCIATIONS = 70;
|
||||
PLAYLIST_EVALUATION = 71;
|
||||
CACHE_INVALIDATIONS = 72;
|
||||
LIVESTREAM_ENTITY = 73;
|
||||
SINGLE_TAP_REACTIONS = 74;
|
||||
USER_COMMENTS = 75;
|
||||
CLIENT_RESTRICTIONS = 76;
|
||||
PODCAST_GUEST = 77;
|
||||
PLAYABILITY = 78;
|
||||
COVER_IMAGE = 79;
|
||||
SHARE_TRAIT = 80;
|
||||
INSTANCE_SHARING = 81;
|
||||
ARTIST_TOUR = 82;
|
||||
AUDIOBOOK_GENRE = 83;
|
||||
CONCEPT = 84;
|
||||
ORIGINAL_VIDEO = 85;
|
||||
SMART_SHUFFLE = 86;
|
||||
LIVE_EVENTS = 87;
|
||||
AUDIOBOOK_RELATIONS = 88;
|
||||
HOME_POC_BASECARD = 89;
|
||||
AUDIOBOOK_SUPPLEMENTS = 90;
|
||||
PAID_PODCAST_BANNER = 91;
|
||||
FEWER_ADS = 92;
|
||||
WATCH_FEED_SHOW_EXPLORER = 93;
|
||||
TRACK_EXTRA_DESCRIPTORS = 94;
|
||||
TRACK_EXTRA_AUDIO_ATTRIBUTES = 95;
|
||||
TRACK_EXTENDED_CREDITS = 96;
|
||||
SIMPLE_TRAIT = 97;
|
||||
AUDIO_ASSOCIATIONS = 98;
|
||||
VIDEO_ASSOCIATIONS = 99;
|
||||
PLAYLIST_TUNER = 100;
|
||||
ARTIST_VIDEOS_ENTRYPOINT = 101;
|
||||
ALBUM_PRERELEASE = 102;
|
||||
CONTENT_ALTERNATIVES = 103;
|
||||
SNAPSHOT_SHARING = 105;
|
||||
DISPLAY_SEGMENTS_COUNT = 106;
|
||||
PODCAST_FEATURED_EPISODE = 107;
|
||||
PODCAST_SPONSORED_CONTENT = 108;
|
||||
PODCAST_EPISODE_TOPICS_LLM = 109;
|
||||
PODCAST_EPISODE_TOPICS_KG = 110;
|
||||
EPISODE_RANKING_POPULARITY = 111;
|
||||
MERCH = 112;
|
||||
COMPANION_CONTENT = 113;
|
||||
WATCH_FEED_ENTITY_EXPLORER = 114;
|
||||
ANCHOR_CARD_TRAIT = 115;
|
||||
AUDIO_PREVIEW_PLAYBACK_TRAIT = 116;
|
||||
VIDEO_PREVIEW_STILL_TRAIT = 117;
|
||||
PREVIEW_CARD_TRAIT = 118;
|
||||
SHORTCUTS_CARD_TRAIT = 119;
|
||||
VIDEO_PREVIEW_PLAYBACK_TRAIT = 120;
|
||||
COURSE_SPECIFICS = 121;
|
||||
CONCERT = 122;
|
||||
CONCERT_LOCATION = 123;
|
||||
CONCERT_MARKETING = 124;
|
||||
CONCERT_PERFORMERS = 125;
|
||||
TRACK_PAIR_TRANSITION = 126;
|
||||
CONTENT_TYPE_TRAIT = 127;
|
||||
NAME_TRAIT = 128;
|
||||
ARTWORK_TRAIT = 129;
|
||||
RELEASE_DATE_TRAIT = 130;
|
||||
CREDITS_TRAIT = 131;
|
||||
RELEASE_URI_TRAIT = 132;
|
||||
ENTITY_CAPPING = 133;
|
||||
LESSON_SPECIFICS = 134;
|
||||
CONCERT_OFFERS = 135;
|
||||
TRANSITION_MAPS = 136;
|
||||
ARTIST_HAS_CONCERTS = 137;
|
||||
PRERELEASE = 138;
|
||||
PLAYLIST_ATTRIBUTES_V2 = 139;
|
||||
LIST_ATTRIBUTES_V2 = 140;
|
||||
LIST_METADATA = 141;
|
||||
LIST_TUNER_AUDIO_ANALYSIS = 142;
|
||||
LIST_TUNER_CUEPOINTS = 143;
|
||||
CONTENT_RATING_TRAIT = 144;
|
||||
COPYRIGHT_TRAIT = 145;
|
||||
SUPPORTED_BADGES = 146;
|
||||
BADGES = 147;
|
||||
PREVIEW_TRAIT = 148;
|
||||
ROOTLISTABILITY_TRAIT = 149;
|
||||
LOCAL_CONCERTS = 150;
|
||||
RECOMMENDED_PLAYLISTS = 151;
|
||||
POPULAR_RELEASES = 152;
|
||||
RELATED_RELEASES = 153;
|
||||
SHARE_RESTRICTIONS = 154;
|
||||
CONCERT_OFFER = 155;
|
||||
CONCERT_OFFER_PROVIDER = 156;
|
||||
ENTITY_BOOKMARKS = 157;
|
||||
PRIVACY_TRAIT = 158;
|
||||
DUPLICATE_ITEMS_TRAIT = 159;
|
||||
REORDERING_TRAIT = 160;
|
||||
PODCAST_RESUMPTION_SEGMENTS = 161;
|
||||
ARTIST_EXPRESSION_VIDEO = 162;
|
||||
PRERELEASE_VIDEO = 163;
|
||||
GATED_ENTITY_RELATIONS = 164;
|
||||
RELATED_CREATORS_SECTION = 165;
|
||||
CREATORS_APPEARS_ON_SECTION = 166;
|
||||
PROMO_V1_TRAIT = 167;
|
||||
SPEECHLESS_SHARE_CARD = 168;
|
||||
TOP_PLAYABLES_SECTION = 169;
|
||||
AUTO_LENS = 170;
|
||||
PROMO_V3_TRAIT = 171;
|
||||
TRACK_CONTENT_FILTER = 172;
|
||||
HIGHLIGHTABILITY = 173;
|
||||
LINK_CARD_WITH_IMAGE_TRAIT = 174;
|
||||
TRACK_CLOUD_SECTION = 175;
|
||||
EPISODE_TOPICS = 176;
|
||||
VIDEO_THUMBNAIL = 177;
|
||||
IDENTITY_TRAIT = 178;
|
||||
VISUAL_IDENTITY_TRAIT = 179;
|
||||
CONTENT_TYPE_V2_TRAIT = 180;
|
||||
PREVIEW_PLAYBACK_TRAIT = 181;
|
||||
CONSUMPTION_EXPERIENCE_TRAIT = 182;
|
||||
PUBLISHING_METADATA_TRAIT = 183;
|
||||
DETAILED_EVALUATION_TRAIT = 184;
|
||||
ON_PLATFORM_REPUTATION_TRAIT = 185;
|
||||
CREDITS_V2_TRAIT = 186;
|
||||
HIGHLIGHT_PLAYABILITY_TRAIT = 187;
|
||||
SHOW_EPISODE_LIST = 188;
|
||||
AVAILABLE_RELEASES = 189;
|
||||
PLAYLIST_DESCRIPTORS = 190;
|
||||
LINK_CARD_WITH_ANIMATIONS_TRAIT = 191;
|
||||
RECAP = 192;
|
||||
AUDIOBOOK_COMPANION_CONTENT = 193;
|
||||
THREE_OH_THREE_PLAY_TRAIT = 194;
|
||||
ARTIST_WRAPPED_2024_VIDEO = 195;
|
||||
}
|
||||
Reference in New Issue
Block a user