spotifywebapiPython 1.0.242__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spotifywebapipython/__init__.py +35 -0
- spotifywebapipython/const.py +114 -0
- spotifywebapipython/docs/__init__.html +2156 -0
- spotifywebapipython/docs/codecopy.js +70 -0
- spotifywebapipython/docs/index.html +514 -0
- spotifywebapipython/docs/search.js +46 -0
- spotifywebapipython/docs/spotifywebapipython/const.html +636 -0
- spotifywebapipython/docs/spotifywebapipython/saappmessages.html +540 -0
- spotifywebapipython/docs/spotifywebapipython/sautils.html +620 -0
- spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html +341 -0
- spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html +598 -0
- spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html +612 -0
- spotifywebapipython/docs/spotifywebapipython/spotifyclient.html +16557 -0
- spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +518 -0
- spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +421 -0
- spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +400 -0
- spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html +635 -0
- spotifywebapipython/docs/spotifywebapipython.ico +0 -0
- spotifywebapipython/docs/spotifywebapipython_OLD.ico +0 -0
- spotifywebapipython/models/__init__.py +121 -0
- spotifywebapipython/models/album.py +170 -0
- spotifywebapipython/models/albumpagesaved.py +85 -0
- spotifywebapipython/models/albumpagesimplified.py +88 -0
- spotifywebapipython/models/albumsaved.py +102 -0
- spotifywebapipython/models/albumsimplified.py +308 -0
- spotifywebapipython/models/artist.py +146 -0
- spotifywebapipython/models/artistinfo.py +282 -0
- spotifywebapipython/models/artistinfotourevent.py +127 -0
- spotifywebapipython/models/artistpage.py +70 -0
- spotifywebapipython/models/artistsimplified.py +163 -0
- spotifywebapipython/models/audiobook.py +87 -0
- spotifywebapipython/models/audiobookpagesimplified.py +88 -0
- spotifywebapipython/models/audiobooksimplified.py +362 -0
- spotifywebapipython/models/audiofeatures.py +333 -0
- spotifywebapipython/models/author.py +67 -0
- spotifywebapipython/models/category.py +168 -0
- spotifywebapipython/models/categorypage.py +70 -0
- spotifywebapipython/models/chapter.py +84 -0
- spotifywebapipython/models/chapterpagesimplified.py +88 -0
- spotifywebapipython/models/chaptersimplified.py +387 -0
- spotifywebapipython/models/context.py +113 -0
- spotifywebapipython/models/copyright.py +79 -0
- spotifywebapipython/models/device.py +301 -0
- spotifywebapipython/models/episode.py +84 -0
- spotifywebapipython/models/episodepagesaved.py +85 -0
- spotifywebapipython/models/episodepagesimplified.py +88 -0
- spotifywebapipython/models/episodesaved.py +99 -0
- spotifywebapipython/models/episodesimplified.py +370 -0
- spotifywebapipython/models/explicitcontent.py +80 -0
- spotifywebapipython/models/externalids.py +91 -0
- spotifywebapipython/models/externalurls.py +104 -0
- spotifywebapipython/models/followers.py +81 -0
- spotifywebapipython/models/imageobject.py +193 -0
- spotifywebapipython/models/imagevibrantcolors.py +182 -0
- spotifywebapipython/models/linkedfrom.py +165 -0
- spotifywebapipython/models/narrator.py +67 -0
- spotifywebapipython/models/owner.py +162 -0
- spotifywebapipython/models/pageobject.py +343 -0
- spotifywebapipython/models/playeractions.py +196 -0
- spotifywebapipython/models/playerlastplayedinfo.py +334 -0
- spotifywebapipython/models/playerplaystate.py +467 -0
- spotifywebapipython/models/playerqueueinfo.py +207 -0
- spotifywebapipython/models/playhistory.py +136 -0
- spotifywebapipython/models/playhistorypage.py +85 -0
- spotifywebapipython/models/playlist.py +125 -0
- spotifywebapipython/models/playlistpage.py +85 -0
- spotifywebapipython/models/playlistpagesimplified.py +107 -0
- spotifywebapipython/models/playlistsimplified.py +334 -0
- spotifywebapipython/models/playlisttrack.py +143 -0
- spotifywebapipython/models/playlisttracksummary.py +80 -0
- spotifywebapipython/models/recommendationseed.py +135 -0
- spotifywebapipython/models/restrictions.py +86 -0
- spotifywebapipython/models/resumepoint.py +79 -0
- spotifywebapipython/models/searchresponse.py +391 -0
- spotifywebapipython/models/searchresultbase.py +299 -0
- spotifywebapipython/models/show.py +87 -0
- spotifywebapipython/models/showpagesaved.py +85 -0
- spotifywebapipython/models/showpagesimplified.py +88 -0
- spotifywebapipython/models/showsaved.py +99 -0
- spotifywebapipython/models/showsimplified.py +320 -0
- spotifywebapipython/models/spotifyconnectdevice.py +321 -0
- spotifywebapipython/models/spotifyconnectdevices.py +441 -0
- spotifywebapipython/models/track.py +142 -0
- spotifywebapipython/models/trackpage.py +110 -0
- spotifywebapipython/models/trackpagesaved.py +85 -0
- spotifywebapipython/models/trackpagesimplified.py +88 -0
- spotifywebapipython/models/trackrecommendations.py +88 -0
- spotifywebapipython/models/tracksaved.py +99 -0
- spotifywebapipython/models/tracksimplified.py +398 -0
- spotifywebapipython/models/userprofile.py +184 -0
- spotifywebapipython/models/userprofilesimplified.py +206 -0
- spotifywebapipython/models/zeroconfdiscoveryresult.py +592 -0
- spotifywebapipython/models/zeroconfproperty.py +93 -0
- spotifywebapipython/oauthcli/__init__.py +7 -0
- spotifywebapipython/oauthcli/authclient.py +1229 -0
- spotifywebapipython/saappmessages.py +86 -0
- spotifywebapipython/sautils.py +285 -0
- spotifywebapipython/spotifyapierror.py +66 -0
- spotifywebapipython/spotifyapimessage.py +269 -0
- spotifywebapipython/spotifyauthtoken.py +264 -0
- spotifywebapipython/spotifyclient.py +17885 -0
- spotifywebapipython/spotifyconnect/__init__.py +19 -0
- spotifywebapipython/spotifyconnect/spotifyconnectdeviceeventargs.py +67 -0
- spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py +2564 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastapptask.py +467 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastcontroller.py +701 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastlistener.py +359 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastmultizonelistener.py +212 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfexceptions.py +20 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconflistener.py +335 -0
- spotifywebapipython/spotifydiscovery.py +473 -0
- spotifywebapipython/spotifymediatypes.py +53 -0
- spotifywebapipython/spotifywebapiauthenticationerror.py +122 -0
- spotifywebapipython/spotifywebapierror.py +106 -0
- spotifywebapipython/spotifywebplayertoken.py +743 -0
- spotifywebapipython/vibrant/__init__.py +17 -0
- spotifywebapipython/vibrant/generator.py +261 -0
- spotifywebapipython/vibrant/image.py +100 -0
- spotifywebapipython/vibrant/main.py +34 -0
- spotifywebapipython/vibrant/models.py +93 -0
- spotifywebapipython/vibrant/utils.py +56 -0
- spotifywebapipython/zeroconfapi/__init__.py +23 -0
- spotifywebapipython/zeroconfapi/authenticationtypes.py +38 -0
- spotifywebapipython/zeroconfapi/blobbuilder.py +370 -0
- spotifywebapipython/zeroconfapi/credentials.py +37 -0
- spotifywebapipython/zeroconfapi/cryptodiffiehellman.py +89 -0
- spotifywebapipython/zeroconfapi/helpers.py +64 -0
- spotifywebapipython/zeroconfapi/spotifyzeroconfapierror.py +108 -0
- spotifywebapipython/zeroconfapi/zeroconfconnect.py +1327 -0
- spotifywebapipython/zeroconfapi/zeroconfgetinfo.py +572 -0
- spotifywebapipython/zeroconfapi/zeroconfgetinfoalias.py +109 -0
- spotifywebapipython/zeroconfapi/zeroconfgetinfodrmmediaformat.py +90 -0
- spotifywebapipython/zeroconfapi/zeroconfresponse.py +195 -0
- spotifywebapipython-1.0.242.dist-info/LICENSE +22 -0
- spotifywebapipython-1.0.242.dist-info/METADATA +219 -0
- spotifywebapipython-1.0.242.dist-info/RECORD +138 -0
- spotifywebapipython-1.0.242.dist-info/WHEEL +5 -0
- spotifywebapipython-1.0.242.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# external package imports.
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Iterator
|
|
4
|
+
|
|
5
|
+
# our package imports.
|
|
6
|
+
from ..sautils import export
|
|
7
|
+
from .device import Device
|
|
8
|
+
from .spotifyconnectdevice import SpotifyConnectDevice
|
|
9
|
+
from .playerplaystate import PlayerPlayState
|
|
10
|
+
from .zeroconfdiscoveryresult import ZeroconfDiscoveryResult
|
|
11
|
+
from spotifywebapipython.zeroconfapi import ZeroconfGetInfo, ZeroconfGetInfoAlias
|
|
12
|
+
|
|
13
|
+
# get smartinspect logger reference; create a new session for this module name.
|
|
14
|
+
from smartinspectpython.siauto import SIAuto, SILevel, SISession, SIMethodParmListContext, SIColors
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
_logsi:SISession = SIAuto.Si.GetSession(__name__)
|
|
18
|
+
if (_logsi == None):
|
|
19
|
+
_logsi = SIAuto.Si.AddSession(__name__, True)
|
|
20
|
+
_logsi.SystemLogger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@export
|
|
24
|
+
class SpotifyConnectDevices():
|
|
25
|
+
"""
|
|
26
|
+
Spotify Connect Devices collection.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Initializes a new instance of the class.
|
|
32
|
+
"""
|
|
33
|
+
self._DateLastRefreshed:int = 0
|
|
34
|
+
self._Items:list[SpotifyConnectDevice] = []
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def __getitem__(self, key) -> SpotifyConnectDevice:
|
|
38
|
+
return self._Items[key]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def __iter__(self) -> Iterator:
|
|
42
|
+
return iter(self._Items)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def __len__(self) -> int:
|
|
46
|
+
return len(self._Items)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def __repr__(self) -> str:
|
|
50
|
+
return self.ToString()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def __str__(self) -> str:
|
|
54
|
+
return self.ToString()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def AgeLastRefreshed(self) -> float:
|
|
59
|
+
"""
|
|
60
|
+
Number of seconds between the current date time and the `DateLastRefreshed` property value.
|
|
61
|
+
"""
|
|
62
|
+
return (datetime.utcnow().timestamp() - self._DateLastRefreshed)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def DateLastRefreshed(self) -> float:
|
|
67
|
+
"""
|
|
68
|
+
Date and time the device list was last refreshed, in unix epoch format (e.g. 1669123919.331225).
|
|
69
|
+
"""
|
|
70
|
+
return self._DateLastRefreshed
|
|
71
|
+
|
|
72
|
+
@DateLastRefreshed.setter
|
|
73
|
+
def DateLastRefreshed(self, value:float):
|
|
74
|
+
"""
|
|
75
|
+
Sets the DateLastRefreshed property value.
|
|
76
|
+
"""
|
|
77
|
+
if isinstance(value, float):
|
|
78
|
+
self._DateLastRefreshed = value
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def Items(self) -> list[SpotifyConnectDevice]:
|
|
83
|
+
"""
|
|
84
|
+
Array of `SpotifyConnectDevice` objects.
|
|
85
|
+
"""
|
|
86
|
+
return self._Items
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def ItemsCount(self) -> int:
|
|
91
|
+
"""
|
|
92
|
+
Number of objects in the `Items` property array.
|
|
93
|
+
"""
|
|
94
|
+
if self._Items is not None:
|
|
95
|
+
return len(self._Items)
|
|
96
|
+
return 0
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def ContainsDeviceId(self, value:str) -> bool:
|
|
100
|
+
"""
|
|
101
|
+
Returns True if the `Items` collection contains the specified device id value;
|
|
102
|
+
otherwise, False.
|
|
103
|
+
|
|
104
|
+
Alias entries (if any) are also compared.
|
|
105
|
+
"""
|
|
106
|
+
scDevice:SpotifyConnectDevice = self.GetDeviceById(value)
|
|
107
|
+
if scDevice is not None:
|
|
108
|
+
return True
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def ContainsDeviceName(self, value:str) -> bool:
|
|
113
|
+
"""
|
|
114
|
+
Returns True if the `Items` collection contains the specified device name value;
|
|
115
|
+
otherwise, False.
|
|
116
|
+
|
|
117
|
+
Alias entries (if any) are also compared.
|
|
118
|
+
"""
|
|
119
|
+
scDevice:SpotifyConnectDevice = self.GetDeviceByName(value)
|
|
120
|
+
if scDevice is not None:
|
|
121
|
+
return True
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def ContainsZeroconfEndpointGetInformation(self, value:str) -> bool:
|
|
126
|
+
"""
|
|
127
|
+
Returns True if the `Items` collection contains the specified Zeroconf getInfo Endpoint url value;
|
|
128
|
+
otherwise, False.
|
|
129
|
+
"""
|
|
130
|
+
result:bool = False
|
|
131
|
+
if value is None:
|
|
132
|
+
return result
|
|
133
|
+
|
|
134
|
+
# convert case for comparison.
|
|
135
|
+
value = value.lower()
|
|
136
|
+
|
|
137
|
+
# process all discovered devices.
|
|
138
|
+
scDevice:SpotifyConnectDevice
|
|
139
|
+
for scDevice in self._Items:
|
|
140
|
+
if (scDevice.DiscoveryResult.ZeroconfApiEndpointGetInformation.lower() == value):
|
|
141
|
+
result = True
|
|
142
|
+
break
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def GetDeviceByDiscoveryKey(self, value:str) -> SpotifyConnectDevice:
|
|
147
|
+
"""
|
|
148
|
+
Returns a `SpotifyConnectDevice` instance if the `Items` collection contains the specified
|
|
149
|
+
device zeroconf discovery results key value; otherwise, None.
|
|
150
|
+
"""
|
|
151
|
+
result:SpotifyConnectDevice = None
|
|
152
|
+
if value is None:
|
|
153
|
+
return result
|
|
154
|
+
|
|
155
|
+
# convert case for comparison.
|
|
156
|
+
value = value.lower()
|
|
157
|
+
|
|
158
|
+
# process all devices, comparing discovery keys.
|
|
159
|
+
for i in range(len(self._Items)):
|
|
160
|
+
if (self._Items[i].DiscoveryResult is not None):
|
|
161
|
+
if (self._Items[i].DiscoveryResult.Key is not None):
|
|
162
|
+
if (self._Items[i].DiscoveryResult.Key.lower() == value):
|
|
163
|
+
result = self._Items[i]
|
|
164
|
+
break
|
|
165
|
+
return result
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def GetDeviceById(self, value:str) -> SpotifyConnectDevice:
|
|
169
|
+
"""
|
|
170
|
+
Returns a `SpotifyConnectDevice` instance if the `Items` collection contains the specified
|
|
171
|
+
device id value; otherwise, None.
|
|
172
|
+
|
|
173
|
+
All Spotify Connect Zeroconf GetInfo response `DeviceId` entries are checked
|
|
174
|
+
first; if not resolved, then base device `Id` entries are checked. We do it
|
|
175
|
+
this way in case a Spotify Connect Zeroconf GetInfo response has not been
|
|
176
|
+
recevied for a device yet.
|
|
177
|
+
"""
|
|
178
|
+
result:SpotifyConnectDevice = None
|
|
179
|
+
if value is None:
|
|
180
|
+
return result
|
|
181
|
+
if value == "":
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
# convert case for comparison.
|
|
185
|
+
value = value.lower()
|
|
186
|
+
|
|
187
|
+
# process all discovered devices.
|
|
188
|
+
scDevice:SpotifyConnectDevice
|
|
189
|
+
for scDevice in self._Items:
|
|
190
|
+
|
|
191
|
+
# check for id in the Spotify Connect Zeroconf GetInfo response.
|
|
192
|
+
if (scDevice.DeviceInfo.DeviceId is not None):
|
|
193
|
+
if (scDevice.DeviceInfo.DeviceId.lower() == value):
|
|
194
|
+
result = scDevice
|
|
195
|
+
_logsi.LogVerbose("GetDeviceById found SpotifyConnectDevices collection entry: %s (DeviceInfo.DeviceId)" % scDevice.Title)
|
|
196
|
+
break
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# if not resolved, then check for id in the base device definition.
|
|
200
|
+
if (result is None):
|
|
201
|
+
|
|
202
|
+
# process all discovered devices.
|
|
203
|
+
scDevice:SpotifyConnectDevice
|
|
204
|
+
for scDevice in self._Items:
|
|
205
|
+
|
|
206
|
+
# check for id in the base device definition.
|
|
207
|
+
if (scDevice.Id is not None):
|
|
208
|
+
if (scDevice.Id.lower() == value):
|
|
209
|
+
result = scDevice
|
|
210
|
+
_logsi.LogVerbose("GetDeviceById found SpotifyConnectDevices collection entry: %s (Device.Id)" % scDevice.Title)
|
|
211
|
+
break
|
|
212
|
+
|
|
213
|
+
# trace.
|
|
214
|
+
if (result is None):
|
|
215
|
+
_logsi.LogVerbose("GetDeviceById could not find SpotifyConnectDevices collection entry: \"%s\"" % value)
|
|
216
|
+
|
|
217
|
+
return result
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def GetDeviceByName(self, value:str) -> SpotifyConnectDevice:
|
|
221
|
+
"""
|
|
222
|
+
Returns a `SpotifyConnectDevice` instance if the `Items` collection contains the specified
|
|
223
|
+
device name value; otherwise, None.
|
|
224
|
+
|
|
225
|
+
All Spotify Connect Zeroconf GetInfo response `RemoteName` / Zeroconf DiscoveryResult
|
|
226
|
+
`DeviceName` entries are checked first; if not resolved, then Spotify Connect Zeroconf
|
|
227
|
+
GetInfo `Alias` entries are checked. We do it this way in case an alias name is defined
|
|
228
|
+
for a Spotify Connect Zeroconf entry that already exists with the same name (e.g. the
|
|
229
|
+
RemoteName entry will take precedence).
|
|
230
|
+
"""
|
|
231
|
+
result:SpotifyConnectDevice = None
|
|
232
|
+
if value is None:
|
|
233
|
+
return result
|
|
234
|
+
if value == "":
|
|
235
|
+
return result
|
|
236
|
+
|
|
237
|
+
# convert case for comparison.
|
|
238
|
+
value = value.lower()
|
|
239
|
+
|
|
240
|
+
# process all discovered devices.
|
|
241
|
+
scDevice:SpotifyConnectDevice
|
|
242
|
+
for scDevice in self._Items:
|
|
243
|
+
|
|
244
|
+
# match on `getInfo` RemoteName value (if defined).
|
|
245
|
+
if (scDevice.DeviceInfo.RemoteName is not None):
|
|
246
|
+
if (scDevice.DeviceInfo.RemoteName.lower() == value):
|
|
247
|
+
result = scDevice
|
|
248
|
+
_logsi.LogVerbose("GetDeviceByName found SpotifyConnectDevices collection entry: %s (DeviceInfo.RemoteName)" % scDevice.Title)
|
|
249
|
+
break
|
|
250
|
+
|
|
251
|
+
# match on Zeroconf DeviceName value (if defined).
|
|
252
|
+
if (scDevice.DiscoveryResult.DeviceName is not None):
|
|
253
|
+
if (scDevice.DiscoveryResult.DeviceName.lower() == value):
|
|
254
|
+
result = scDevice
|
|
255
|
+
_logsi.LogVerbose("GetDeviceByName found SpotifyConnectDevices collection entry: %s (DiscoveryResult.DeviceName)" % scDevice.Title)
|
|
256
|
+
break
|
|
257
|
+
|
|
258
|
+
# if not resolved, then search by alias name.
|
|
259
|
+
if (result is None):
|
|
260
|
+
|
|
261
|
+
# process all discovered devices.
|
|
262
|
+
scDevice:SpotifyConnectDevice
|
|
263
|
+
for scDevice in self._Items:
|
|
264
|
+
|
|
265
|
+
# search aliases (if defined).
|
|
266
|
+
if (scDevice.DeviceInfo.HasAliases):
|
|
267
|
+
scAlias:ZeroconfGetInfoAlias
|
|
268
|
+
for scAlias in scDevice.DeviceInfo.Aliases:
|
|
269
|
+
if (scAlias.Name is not None):
|
|
270
|
+
if (scAlias.Name.lower() == value):
|
|
271
|
+
result = scDevice
|
|
272
|
+
_logsi.LogVerbose("GetDeviceByName found SpotifyConnectDevices collection entry: %s (DeviceInfo.Aliases)" % scDevice.Title)
|
|
273
|
+
break
|
|
274
|
+
if result is not None:
|
|
275
|
+
break
|
|
276
|
+
|
|
277
|
+
# trace.
|
|
278
|
+
if (result is None):
|
|
279
|
+
_logsi.LogVerbose("GetDeviceByName could not find SpotifyConnectDevices collection entry: \"%s\"" % value)
|
|
280
|
+
|
|
281
|
+
return result
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def GetDeviceByNameAndId(self, deviceName:str, deviceId:str) -> int:
|
|
285
|
+
"""
|
|
286
|
+
Returns a `SpotifyConnectDevice` instance if the `Items` collection contains the specified
|
|
287
|
+
device name and id value; otherwise, None.
|
|
288
|
+
|
|
289
|
+
Alias entries (if any) are NOT compared.
|
|
290
|
+
"""
|
|
291
|
+
result:SpotifyConnectDevice = None
|
|
292
|
+
if deviceName is None:
|
|
293
|
+
return result
|
|
294
|
+
if deviceId is None:
|
|
295
|
+
return deviceId
|
|
296
|
+
|
|
297
|
+
# convert case for comparison.
|
|
298
|
+
deviceName = deviceName.lower()
|
|
299
|
+
deviceId = deviceId.lower()
|
|
300
|
+
|
|
301
|
+
# process all devices, comparing device name and id.
|
|
302
|
+
scDevice:SpotifyConnectDevice
|
|
303
|
+
for scDevice in self._Items:
|
|
304
|
+
if (scDevice.Name is not None) and (scDevice.Id is not None):
|
|
305
|
+
if (scDevice.Name.lower() == deviceName) and (scDevice.Id.lower() == deviceId):
|
|
306
|
+
result = scDevice
|
|
307
|
+
_logsi.LogVerbose("GetDeviceByNameAndId found SpotifyConnectDevices collection entry: %s" % scDevice.Title)
|
|
308
|
+
break
|
|
309
|
+
|
|
310
|
+
# trace.
|
|
311
|
+
if (result is None):
|
|
312
|
+
_logsi.LogVerbose("GetDeviceByNameAndId could not find SpotifyConnectDevices collection entry: \"%s\" (%s)" % (deviceName, deviceId))
|
|
313
|
+
|
|
314
|
+
return result
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def GetDeviceIndexByDiscoveryKey(self, value:str) -> int:
|
|
318
|
+
"""
|
|
319
|
+
Returns the index of the `Items` collection entry that contains
|
|
320
|
+
the specified device zeroconf discovery results key value if found;
|
|
321
|
+
otherwise, -1.
|
|
322
|
+
"""
|
|
323
|
+
result:int = -1
|
|
324
|
+
if value is None:
|
|
325
|
+
return result
|
|
326
|
+
|
|
327
|
+
# convert case for comparison.
|
|
328
|
+
value = value.lower()
|
|
329
|
+
|
|
330
|
+
# process all devices, comparing discovery keys.
|
|
331
|
+
for i in range(len(self._Items)):
|
|
332
|
+
if (self._Items[i].DiscoveryResult is not None):
|
|
333
|
+
if (self._Items[i].DiscoveryResult.Key is not None):
|
|
334
|
+
if (self._Items[i].DiscoveryResult.Key.lower() == value):
|
|
335
|
+
result = i
|
|
336
|
+
break
|
|
337
|
+
return result
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def GetDeviceIndexByDiscoveryName(self, value:str) -> int:
|
|
341
|
+
"""
|
|
342
|
+
Returns the index of the `Items` collection entry that contains
|
|
343
|
+
the specified device zeroconf discovery results name value if found;
|
|
344
|
+
otherwise, -1.
|
|
345
|
+
"""
|
|
346
|
+
result:int = -1
|
|
347
|
+
if value is None:
|
|
348
|
+
return result
|
|
349
|
+
|
|
350
|
+
# convert case for comparison.
|
|
351
|
+
value = value.lower()
|
|
352
|
+
|
|
353
|
+
# process all devices, comparing discovery keys.
|
|
354
|
+
for i in range(len(self._Items)):
|
|
355
|
+
if (self._Items[i].DiscoveryResult is not None):
|
|
356
|
+
if (self._Items[i].DiscoveryResult.Name is not None):
|
|
357
|
+
if (self._Items[i].DiscoveryResult.Name.lower() == value):
|
|
358
|
+
result = i
|
|
359
|
+
break
|
|
360
|
+
return result
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def GetDeviceList(self) -> list[Device]:
|
|
364
|
+
"""
|
|
365
|
+
Returns a list of `Device` objects that can be used to build a selection list
|
|
366
|
+
of available devices.
|
|
367
|
+
|
|
368
|
+
Note that the `Device` object has the following properties populated:
|
|
369
|
+
Id, Name, IsActive, Type.
|
|
370
|
+
"""
|
|
371
|
+
result:list[Device] = []
|
|
372
|
+
|
|
373
|
+
# process all discovered devices.
|
|
374
|
+
scDevice:SpotifyConnectDevice
|
|
375
|
+
for scDevice in self._Items:
|
|
376
|
+
|
|
377
|
+
# map device information details.
|
|
378
|
+
info:ZeroconfGetInfo = scDevice.DeviceInfo
|
|
379
|
+
|
|
380
|
+
# create new mock device.
|
|
381
|
+
device = Device()
|
|
382
|
+
device.Type = info.DeviceType
|
|
383
|
+
|
|
384
|
+
# are aliases being used (RemoteName is null if so)?
|
|
385
|
+
if info.RemoteName is None:
|
|
386
|
+
|
|
387
|
+
# if aliases are defined, then use the alias details.
|
|
388
|
+
infoAlias:ZeroconfGetInfoAlias
|
|
389
|
+
for infoAlias in info.Aliases:
|
|
390
|
+
device.Id = infoAlias.Id
|
|
391
|
+
device.Name = infoAlias.Name
|
|
392
|
+
|
|
393
|
+
else:
|
|
394
|
+
|
|
395
|
+
# if no aliases then use the remote name and id.
|
|
396
|
+
device.Id = info.DeviceId
|
|
397
|
+
device.Name = info.RemoteName
|
|
398
|
+
|
|
399
|
+
# append device to results.
|
|
400
|
+
result.append(device)
|
|
401
|
+
|
|
402
|
+
# sort items on Name property, ascending order.
|
|
403
|
+
if len(result) > 0:
|
|
404
|
+
result.sort(key=lambda x: (x.Name or "").lower(), reverse=False)
|
|
405
|
+
|
|
406
|
+
return result
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def ToDictionary(self) -> dict:
|
|
410
|
+
"""
|
|
411
|
+
Returns a dictionary representation of the class.
|
|
412
|
+
"""
|
|
413
|
+
result:dict = \
|
|
414
|
+
{
|
|
415
|
+
'AgeLastRefreshed': self.AgeLastRefreshed,
|
|
416
|
+
'DateLastRefreshed': self._DateLastRefreshed,
|
|
417
|
+
'ItemsCount': self.ItemsCount,
|
|
418
|
+
'Items': [ item.ToDictionary() for item in self._Items ],
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return result
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def ToString(self, includeItems:bool=False) -> str:
|
|
425
|
+
"""
|
|
426
|
+
Returns a displayable string representation of the class.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
includeItems (bool):
|
|
430
|
+
True to include the Items collection of objects; otherwise, False
|
|
431
|
+
to only return base properties.
|
|
432
|
+
"""
|
|
433
|
+
msg:str = 'Items Count=%s' % (str(self.ItemsCount))
|
|
434
|
+
msg:str = '%s, DateLastRefreshed=%s' % (msg, str(self._DateLastRefreshed))
|
|
435
|
+
|
|
436
|
+
if (includeItems):
|
|
437
|
+
item:SpotifyConnectDevice
|
|
438
|
+
for item in self._Items:
|
|
439
|
+
msg = "%s\n- %s" % (msg, item.ToString(True))
|
|
440
|
+
|
|
441
|
+
return msg
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# external package imports.
|
|
2
|
+
|
|
3
|
+
# our package imports.
|
|
4
|
+
from ..sautils import export
|
|
5
|
+
from .album import Album
|
|
6
|
+
from .externalids import ExternalIds
|
|
7
|
+
from .tracksimplified import TrackSimplified
|
|
8
|
+
|
|
9
|
+
@export
|
|
10
|
+
class Track(TrackSimplified):
|
|
11
|
+
"""
|
|
12
|
+
Spotify Web API Track object.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, root:dict=None) -> None:
|
|
16
|
+
"""
|
|
17
|
+
Initializes a new instance of the class.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
root (dict):
|
|
21
|
+
Spotify Web API JSON response in dictionary format, used to load object
|
|
22
|
+
attributes; otherwise, None to not load attributes.
|
|
23
|
+
"""
|
|
24
|
+
# initialize base class.
|
|
25
|
+
super().__init__(root=root)
|
|
26
|
+
|
|
27
|
+
self._Album:Album = None
|
|
28
|
+
self._ExternalIds:ExternalIds = None
|
|
29
|
+
self._Popularity:int = None
|
|
30
|
+
|
|
31
|
+
if (root is None):
|
|
32
|
+
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
else:
|
|
36
|
+
|
|
37
|
+
self._Popularity = root.get('popularity', None)
|
|
38
|
+
|
|
39
|
+
# process all collections and objects.
|
|
40
|
+
item:dict = root.get('album',None)
|
|
41
|
+
if item is not None:
|
|
42
|
+
self._Album = Album(root=item)
|
|
43
|
+
|
|
44
|
+
item:dict = root.get('external_ids',None)
|
|
45
|
+
if item is not None:
|
|
46
|
+
self._ExternalIds = ExternalIds(root=item)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def __repr__(self) -> str:
|
|
50
|
+
return self.ToString()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def __str__(self) -> str:
|
|
54
|
+
return self.ToString()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def Album(self) -> Album:
|
|
59
|
+
"""
|
|
60
|
+
The album on which the track appears.
|
|
61
|
+
|
|
62
|
+
The album object includes a link in href to full information about the album.
|
|
63
|
+
"""
|
|
64
|
+
return self._Album
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def ExternalIds(self) -> ExternalIds:
|
|
69
|
+
"""
|
|
70
|
+
Known external ID's for the track.
|
|
71
|
+
"""
|
|
72
|
+
return self._ExternalIds
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def ImageUrl(self) -> str:
|
|
77
|
+
"""
|
|
78
|
+
Gets the first image url in the album `Images` list, if images are defined;
|
|
79
|
+
otherwise, null.
|
|
80
|
+
"""
|
|
81
|
+
if self._Album is not None:
|
|
82
|
+
return self._Album.ImageUrl
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def Popularity(self) -> int:
|
|
88
|
+
"""
|
|
89
|
+
The popularity of the track. The value will be between 0 and 100, with 100 being the most popular.
|
|
90
|
+
|
|
91
|
+
The popularity of a track is a value between 0 and 100, with 100 being the most popular.
|
|
92
|
+
The popularity is calculated by algorithm and is based, in the most part, on the total number
|
|
93
|
+
of plays the track has had and how recent those plays are. Generally speaking, songs that are
|
|
94
|
+
being played a lot now will have a higher popularity than songs that were played a lot in the past.
|
|
95
|
+
|
|
96
|
+
Duplicate tracks (e.g. the same track from a single and an album) are rated independently.
|
|
97
|
+
Artist and album popularity is derived mathematically from track popularity.
|
|
98
|
+
|
|
99
|
+
Note: the popularity value may lag actual popularity by a few days: the value is not updated in real time.
|
|
100
|
+
"""
|
|
101
|
+
return self._Popularity
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def ToDictionary(self) -> dict:
|
|
105
|
+
"""
|
|
106
|
+
Returns a dictionary representation of the class.
|
|
107
|
+
"""
|
|
108
|
+
# get base class result.
|
|
109
|
+
resultBase:dict = super().ToDictionary()
|
|
110
|
+
|
|
111
|
+
album:dict = {}
|
|
112
|
+
if self._Album is not None:
|
|
113
|
+
album = self._Album.ToDictionary()
|
|
114
|
+
|
|
115
|
+
externalIds:dict = {}
|
|
116
|
+
if self._ExternalIds is not None:
|
|
117
|
+
externalIds = self._ExternalIds.ToDictionary()
|
|
118
|
+
|
|
119
|
+
result:dict = \
|
|
120
|
+
{
|
|
121
|
+
'album': album,
|
|
122
|
+
'external_ids': externalIds,
|
|
123
|
+
'popularity': self._Popularity,
|
|
124
|
+
'image_url': self.ImageUrl,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# combine base class results with these results.
|
|
128
|
+
resultBase.update(result)
|
|
129
|
+
|
|
130
|
+
# return a sorted dictionary.
|
|
131
|
+
return dict(sorted(resultBase.items()))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def ToString(self) -> str:
|
|
135
|
+
"""
|
|
136
|
+
Returns a displayable string representation of the class.
|
|
137
|
+
"""
|
|
138
|
+
msg:str = 'Track: %s' % super().ToString(False)
|
|
139
|
+
if self._Album is not None: msg = '%s\n Album="%s"' % (msg, str(self._Album.Name))
|
|
140
|
+
#if self._ExternalIds is not None: msg = '%s\n %s' % (msg, str(self._ExternalIds))
|
|
141
|
+
if self._Popularity is not None: msg = '%s\n Popularity="%s"' % (msg, str(self._Popularity))
|
|
142
|
+
return msg
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# external package imports.
|
|
2
|
+
|
|
3
|
+
# our package imports.
|
|
4
|
+
from ..sautils import export
|
|
5
|
+
from .track import Track
|
|
6
|
+
from .pageobject import PageObject
|
|
7
|
+
|
|
8
|
+
@export
|
|
9
|
+
class TrackPage(PageObject):
|
|
10
|
+
"""
|
|
11
|
+
Spotify Web API TrackPage object.
|
|
12
|
+
|
|
13
|
+
This allows for multiple pages of `Track` objects to be navigated.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, root:dict=None) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Initializes a new instance of the class.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
root (dict):
|
|
22
|
+
Spotify Web API JSON response in dictionary format, used to load object
|
|
23
|
+
attributes; otherwise, None to not load attributes.
|
|
24
|
+
"""
|
|
25
|
+
# initialize base class.
|
|
26
|
+
super().__init__(root=root)
|
|
27
|
+
|
|
28
|
+
# initialize storage.
|
|
29
|
+
self._Items:list[Track] = []
|
|
30
|
+
|
|
31
|
+
if (root is None):
|
|
32
|
+
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
else:
|
|
36
|
+
|
|
37
|
+
# process all collections and objects.
|
|
38
|
+
items:list = root.get('items',None)
|
|
39
|
+
if items is not None:
|
|
40
|
+
for item in items:
|
|
41
|
+
if item is not None:
|
|
42
|
+
self._Items.append(Track(root=item))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def __repr__(self) -> str:
|
|
46
|
+
return self.ToString()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def __str__(self) -> str:
|
|
50
|
+
return self.ToString()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def Items(self) -> list[Track]:
|
|
55
|
+
"""
|
|
56
|
+
Array of `Track` objects.
|
|
57
|
+
"""
|
|
58
|
+
return self._Items
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def GetUris(self, afterUri:str=None) -> str:
|
|
62
|
+
"""
|
|
63
|
+
Gets a comma-delimited list of uri's contained in the underlying `Items` list.
|
|
64
|
+
|
|
65
|
+
args:
|
|
66
|
+
afterUri (str):
|
|
67
|
+
If specified, the process will look for this uri before adding subsequent
|
|
68
|
+
uri's to the returned results. This allows you to skip uri's that have
|
|
69
|
+
recently been played.
|
|
70
|
+
|
|
71
|
+
This is a convenience method so one does not have to loop through the `Items`
|
|
72
|
+
array of PlayHistory objects to get the list of uri's.
|
|
73
|
+
"""
|
|
74
|
+
result:str = ""
|
|
75
|
+
addUri:bool = False
|
|
76
|
+
item:Track
|
|
77
|
+
|
|
78
|
+
# add uri's from the beginning if no `afterUri` argument was passed.
|
|
79
|
+
if (afterUri is None) or (len(afterUri.strip()) == 0):
|
|
80
|
+
addUri = True
|
|
81
|
+
|
|
82
|
+
for item in self._Items:
|
|
83
|
+
|
|
84
|
+
if (not addUri):
|
|
85
|
+
|
|
86
|
+
# check for `afterUri` match; if found, we will start adding uri's
|
|
87
|
+
# to the list with the next item.
|
|
88
|
+
if (afterUri == item.Uri):
|
|
89
|
+
addUri = True
|
|
90
|
+
|
|
91
|
+
elif (addUri):
|
|
92
|
+
|
|
93
|
+
# add item uri to the list.
|
|
94
|
+
if (len(result) > 0):
|
|
95
|
+
result += ","
|
|
96
|
+
result += item.Uri
|
|
97
|
+
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def ToString(self, includeItems:bool=False) -> str:
|
|
102
|
+
"""
|
|
103
|
+
Returns a displayable string representation of the class.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
includeItems (bool):
|
|
107
|
+
True to include the Items collection of objects; otherwise, False
|
|
108
|
+
to only return base properties.
|
|
109
|
+
"""
|
|
110
|
+
return 'TrackPage: %s' % super().ToString(includeItems)
|