spotifywebapiPython 1.0.129__py3-none-any.whl → 1.0.131__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.

Potentially problematic release.


This version of spotifywebapiPython might be problematic. Click here for more details.

Files changed (22) hide show
  1. {spotifywebapiPython-1.0.129.dist-info → spotifywebapiPython-1.0.131.dist-info}/METADATA +2 -1
  2. {spotifywebapiPython-1.0.129.dist-info → spotifywebapiPython-1.0.131.dist-info}/RECORD +22 -22
  3. spotifywebapipython/const.py +4 -1
  4. spotifywebapipython/docs/__init__.html +15 -2
  5. spotifywebapipython/docs/index.html +2 -2
  6. spotifywebapipython/docs/search.js +1 -1
  7. spotifywebapipython/docs/spotifywebapipython/const.html +20 -3
  8. spotifywebapipython/docs/spotifywebapipython/saappmessages.html +2 -2
  9. spotifywebapipython/docs/spotifywebapipython/sautils.html +2 -2
  10. spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html +2 -2
  11. spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html +2 -2
  12. spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html +2 -2
  13. spotifywebapipython/docs/spotifywebapipython/spotifyclient.html +51 -51
  14. spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +2 -2
  15. spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +2 -2
  16. spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +2 -2
  17. spotifywebapipython/models/playerplaystate.py +19 -0
  18. spotifywebapipython/spotifyclient.py +135 -135
  19. spotifywebapipython/zeroconfapi/zeroconfconnect.py +55 -10
  20. {spotifywebapiPython-1.0.129.dist-info → spotifywebapiPython-1.0.131.dist-info}/LICENSE +0 -0
  21. {spotifywebapiPython-1.0.129.dist-info → spotifywebapiPython-1.0.131.dist-info}/WHEEL +0 -0
  22. {spotifywebapiPython-1.0.129.dist-info → spotifywebapiPython-1.0.131.dist-info}/top_level.txt +0 -0
@@ -2391,141 +2391,6 @@ class SpotifyClient:
2391
2391
  _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
2392
2392
 
2393
2393
 
2394
- def GetCoverImageFile(
2395
- self,
2396
- imageUrl:Union[str,list],
2397
- outputPath:str,
2398
- desiredWidth:int=None,
2399
- ) -> None:
2400
- """
2401
- Gets the contents of an image url and transfers the contents to the local file system.
2402
-
2403
- Args:
2404
- imageUrl (str | list[ImageObject]):
2405
- The cover image to retrieve - can be one of the following:
2406
- - a url string that points to the exact cover image to transfer.
2407
- - a list of `ImageObject` items that contain the cover art for the media in
2408
- various sizes, usually widest first.
2409
- outputPath (str):
2410
- Fully-qualified path to store the downloaded image to.
2411
- desiredWidth (int):
2412
- A desired resolution width to return (if found); if not found, then the
2413
- highest resolution is returned.
2414
- This argument is ignored if the `images` argument is not an `ImageObject` list.
2415
-
2416
- The output path supports the replacement of the following keyword parameters:
2417
- - `{dotfileextn}` - a "." followed by the file extension based on response content
2418
- type (for known types: JPG,PNG,APNG,BMP,GIF).
2419
- - `{imagewidth}` - the resolved width value, if `imageUrl` argument is of type list[ImageObject].
2420
-
2421
- The Spotify Web API normally returns a list of `ImageObject` items in its various
2422
- endpoints. The highest resolution image is usually the first list item, but the
2423
- order is not guarenteed (e.g. `GetShowFavorites`).
2424
-
2425
- This method should only be used to download images for playlists that contain
2426
- public domain images. It should not be used to download copyright protected images,
2427
- as that would violate the Spotify Web API Terms of Service.
2428
- """
2429
- apiMethodName:str = 'GetCoverImageFile'
2430
- apiMethodParms:SIMethodParmListContext = None
2431
- response:HTTPResponse = None
2432
- url:str = None
2433
- imageWidth:int = 0
2434
-
2435
- try:
2436
-
2437
- # trace.
2438
- apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
2439
- apiMethodParms.AppendKeyValue("imageUrl", imageUrl)
2440
- apiMethodParms.AppendKeyValue("outputPath", outputPath)
2441
- apiMethodParms.AppendKeyValue("desiredWidth", desiredWidth)
2442
- _logsi.LogMethodParmList(SILevel.Verbose, "Downloading cover image url content", apiMethodParms)
2443
-
2444
- # validations.
2445
- if (imageUrl is None):
2446
- raise SpotifyApiError(SAAppMessages.ARGUMENT_REQUIRED_ERROR % (apiMethodName, 'imageUrl'), logsi=_logsi)
2447
- if (outputPath is None):
2448
- raise SpotifyApiError(SAAppMessages.ARGUMENT_REQUIRED_ERROR % (apiMethodName, 'outputPath'), logsi=_logsi)
2449
-
2450
- # was an image list specified?
2451
- if (isinstance(imageUrl, list)):
2452
-
2453
- # return the highest resolution order image from a list of `ImageObject` items.
2454
- # this will return None if images argument was none or empty.
2455
- url = ImageObject.GetImageHighestResolution(imageUrl, desiredWidth)
2456
- imageWidth = ImageObject.GetImageHighestResolutionWidth(imageUrl, desiredWidth)
2457
- if (url) is None:
2458
- _logsi.LogVerbose("List of ImageObjects was not specified, or was empty")
2459
- return
2460
- imageUrl = url
2461
-
2462
- # download content from the selected image url.
2463
- _logsi.LogVerbose("Downloading cover image url (width=%s): \"%s\"" % (desiredWidth, imageUrl))
2464
- response = self._Manager.request("GET", imageUrl)
2465
-
2466
- # trace.
2467
- if _logsi.IsOn(SILevel.Debug):
2468
- _logsi.LogObject(SILevel.Debug, "SpotifyClient http response [%s-%s]: '%s' (response)" % (response.status, response.reason, imageUrl), response)
2469
- if (response.headers):
2470
- _logsi.LogCollection(SILevel.Debug, "SpotifyClient http response [%s-%s]: '%s' (headers)" % (response.status, response.reason, imageUrl), response.headers.items())
2471
-
2472
- # do response headers contain a content-type value?
2473
- # if so, then set file extension based on content type.
2474
- contentType:str = None
2475
- outputFileExtn:str = ".jpg"
2476
- if response.headers:
2477
- if 'content-type' in response.headers:
2478
- contentType = response.headers['content-type'] + ""
2479
- if (contentType.find("image/jpeg") != -1):
2480
- outputFileExtn = ".jpg"
2481
- elif (contentType.find("image/bmp") != -1):
2482
- outputFileExtn = ".bmp"
2483
- elif (contentType.find("image/png") != -1):
2484
- outputFileExtn = ".png"
2485
- elif (contentType.find("image/apng") != -1):
2486
- outputFileExtn = ".apng"
2487
- elif (contentType.find("image/gif") != -1):
2488
- outputFileExtn = ".gif"
2489
- else:
2490
- outputFileExtn = ".jpg"
2491
-
2492
- # do we have response data?
2493
- if len(response.data) == 0:
2494
-
2495
- # some requests will not return a response, which is ok.
2496
- _logsi.LogVerbose("SpotifyClient http response [%s-%s]: '%s' (no data)" % (response.status, response.reason, imageUrl))
2497
-
2498
- else:
2499
-
2500
- # override file extension based on content type (if desired).
2501
- outputPath = outputPath.replace("{dotfileextn}", outputFileExtn)
2502
- outputPath = outputPath.replace("{imagewidth}", str(imageWidth))
2503
-
2504
- # write data to output file.
2505
- f = open(outputPath,'wb')
2506
- f.write(response.data)
2507
- f.close()
2508
-
2509
- # response is raw image data.
2510
- _logsi.LogJpegFile(SILevel.Verbose, "SpotifyClient http response [%s-%s]: '%s' (imagefile)" % (response.status, response.reason, imageUrl), outputPath)
2511
-
2512
- except SpotifyApiError: raise # pass handled exceptions on thru
2513
- except Exception as ex:
2514
-
2515
- # format unhandled exception.
2516
- raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
2517
-
2518
- finally:
2519
-
2520
- # close the response (if needed).
2521
- if response is not None:
2522
- if response.closed == False:
2523
- response.close()
2524
-
2525
- # trace.
2526
- _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
2527
-
2528
-
2529
2394
  def FollowArtists(
2530
2395
  self,
2531
2396
  ids:str=None,
@@ -5883,6 +5748,141 @@ class SpotifyClient:
5883
5748
  _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
5884
5749
 
5885
5750
 
5751
+ def GetCoverImageFile(
5752
+ self,
5753
+ imageUrl:Union[str,list],
5754
+ outputPath:str,
5755
+ desiredWidth:int=None,
5756
+ ) -> None:
5757
+ """
5758
+ Gets the contents of an image url and transfers the contents to the local file system.
5759
+
5760
+ Args:
5761
+ imageUrl (str | list[ImageObject]):
5762
+ The cover image to retrieve - can be one of the following:
5763
+ - a url string that points to the exact cover image to transfer.
5764
+ - a list of `ImageObject` items that contain the cover art for the media in
5765
+ various sizes, usually widest first.
5766
+ outputPath (str):
5767
+ Fully-qualified path to store the downloaded image to.
5768
+ desiredWidth (int):
5769
+ A desired resolution width to return (if found); if not found, then the
5770
+ highest resolution is returned.
5771
+ This argument is ignored if the `images` argument is not an `ImageObject` list.
5772
+
5773
+ The output path supports the replacement of the following keyword parameters:
5774
+ - `{dotfileextn}` - a "." followed by the file extension based on response content
5775
+ type (for known types: JPG,PNG,APNG,BMP,GIF).
5776
+ - `{imagewidth}` - the resolved width value, if `imageUrl` argument is of type list[ImageObject].
5777
+
5778
+ The Spotify Web API normally returns a list of `ImageObject` items in its various
5779
+ endpoints. The highest resolution image is usually the first list item, but the
5780
+ order is not guarenteed (e.g. `GetShowFavorites`).
5781
+
5782
+ This method should only be used to download images for playlists that contain
5783
+ public domain images. It should not be used to download copyright protected images,
5784
+ as that would violate the Spotify Web API Terms of Service.
5785
+ """
5786
+ apiMethodName:str = 'GetCoverImageFile'
5787
+ apiMethodParms:SIMethodParmListContext = None
5788
+ response:HTTPResponse = None
5789
+ url:str = None
5790
+ imageWidth:int = 0
5791
+
5792
+ try:
5793
+
5794
+ # trace.
5795
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
5796
+ apiMethodParms.AppendKeyValue("imageUrl", imageUrl)
5797
+ apiMethodParms.AppendKeyValue("outputPath", outputPath)
5798
+ apiMethodParms.AppendKeyValue("desiredWidth", desiredWidth)
5799
+ _logsi.LogMethodParmList(SILevel.Verbose, "Downloading cover image url content", apiMethodParms)
5800
+
5801
+ # validations.
5802
+ if (imageUrl is None):
5803
+ raise SpotifyApiError(SAAppMessages.ARGUMENT_REQUIRED_ERROR % (apiMethodName, 'imageUrl'), logsi=_logsi)
5804
+ if (outputPath is None):
5805
+ raise SpotifyApiError(SAAppMessages.ARGUMENT_REQUIRED_ERROR % (apiMethodName, 'outputPath'), logsi=_logsi)
5806
+
5807
+ # was an image list specified?
5808
+ if (isinstance(imageUrl, list)):
5809
+
5810
+ # return the highest resolution order image from a list of `ImageObject` items.
5811
+ # this will return None if images argument was none or empty.
5812
+ url = ImageObject.GetImageHighestResolution(imageUrl, desiredWidth)
5813
+ imageWidth = ImageObject.GetImageHighestResolutionWidth(imageUrl, desiredWidth)
5814
+ if (url) is None:
5815
+ _logsi.LogVerbose("List of ImageObjects was not specified, or was empty")
5816
+ return
5817
+ imageUrl = url
5818
+
5819
+ # download content from the selected image url.
5820
+ _logsi.LogVerbose("Downloading cover image url (width=%s): \"%s\"" % (desiredWidth, imageUrl))
5821
+ response = self._Manager.request("GET", imageUrl)
5822
+
5823
+ # trace.
5824
+ if _logsi.IsOn(SILevel.Debug):
5825
+ _logsi.LogObject(SILevel.Debug, "SpotifyClient http response [%s-%s]: '%s' (response)" % (response.status, response.reason, imageUrl), response)
5826
+ if (response.headers):
5827
+ _logsi.LogCollection(SILevel.Debug, "SpotifyClient http response [%s-%s]: '%s' (headers)" % (response.status, response.reason, imageUrl), response.headers.items())
5828
+
5829
+ # do response headers contain a content-type value?
5830
+ # if so, then set file extension based on content type.
5831
+ contentType:str = None
5832
+ outputFileExtn:str = ".jpg"
5833
+ if response.headers:
5834
+ if 'content-type' in response.headers:
5835
+ contentType = response.headers['content-type'] + ""
5836
+ if (contentType.find("image/jpeg") != -1):
5837
+ outputFileExtn = ".jpg"
5838
+ elif (contentType.find("image/bmp") != -1):
5839
+ outputFileExtn = ".bmp"
5840
+ elif (contentType.find("image/png") != -1):
5841
+ outputFileExtn = ".png"
5842
+ elif (contentType.find("image/apng") != -1):
5843
+ outputFileExtn = ".apng"
5844
+ elif (contentType.find("image/gif") != -1):
5845
+ outputFileExtn = ".gif"
5846
+ else:
5847
+ outputFileExtn = ".jpg"
5848
+
5849
+ # do we have response data?
5850
+ if len(response.data) == 0:
5851
+
5852
+ # some requests will not return a response, which is ok.
5853
+ _logsi.LogVerbose("SpotifyClient http response [%s-%s]: '%s' (no data)" % (response.status, response.reason, imageUrl))
5854
+
5855
+ else:
5856
+
5857
+ # override file extension based on content type (if desired).
5858
+ outputPath = outputPath.replace("{dotfileextn}", outputFileExtn)
5859
+ outputPath = outputPath.replace("{imagewidth}", str(imageWidth))
5860
+
5861
+ # write data to output file.
5862
+ f = open(outputPath,'wb')
5863
+ f.write(response.data)
5864
+ f.close()
5865
+
5866
+ # response is raw image data.
5867
+ _logsi.LogJpegFile(SILevel.Verbose, "SpotifyClient http response [%s-%s]: '%s' (imagefile)" % (response.status, response.reason, imageUrl), outputPath)
5868
+
5869
+ except SpotifyApiError: raise # pass handled exceptions on thru
5870
+ except Exception as ex:
5871
+
5872
+ # format unhandled exception.
5873
+ raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
5874
+
5875
+ finally:
5876
+
5877
+ # close the response (if needed).
5878
+ if response is not None:
5879
+ if response.closed == False:
5880
+ response.close()
5881
+
5882
+ # trace.
5883
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
5884
+
5885
+
5886
5886
  def GetEpisode(
5887
5887
  self,
5888
5888
  episodeId:str=None,
@@ -717,7 +717,7 @@ class ZeroconfConnect:
717
717
  apiMethodParms:SIMethodParmListContext = None
718
718
  tracePrefix:str = 'SpotifyConnectAuthorizationCodeToken exchange'
719
719
 
720
- # based upon Fiddler trace, it the tokenType "authorization_code" flow exchanges an
720
+ # based upon Fiddler trace, the tokenType "authorization_code" flow exchanges an
721
721
  # access-token for an "authorization_code" token. it does this by calling
722
722
  # "accounts.spotify.com/api/token" to exchange the spotify "login5" "client-token" value
723
723
  # to an "authorization_code" token value, which is then passed on the Spotify Connect
@@ -1192,7 +1192,7 @@ class ZeroconfConnect:
1192
1192
  apiMethodParms.AppendKeyValue("CPath", self._CPath)
1193
1193
  apiMethodParms.AppendKeyValue("Version", self._Version)
1194
1194
  apiMethodParms.AppendKeyValue("Uri", self._Uri)
1195
- _logsi.LogMethodParmList(SILevel.Verbose, "Get information from Spotify Connect Zeroconf API (ip=%s)" % self._HostIpAddress, apiMethodParms)
1195
+ _logsi.LogMethodParmList(SILevel.Verbose, "Get information from Spotify Connect Zeroconf API (ip=%s:%s)" % (self._HostIpAddress, self._HostIpPort), apiMethodParms)
1196
1196
 
1197
1197
  # set request endpoint.
1198
1198
  endpoint:str = self.GetEndpoint("getInfo")
@@ -1211,14 +1211,59 @@ class ZeroconfConnect:
1211
1211
  }
1212
1212
  _logsi.LogDictionary(SILevel.Verbose, "ZeroconfConnect http request: '%s' (params)" % (endpoint), reqParams)
1213
1213
 
1214
- # execute spotify zeroconf api request.
1215
- response = requests.get(
1216
- self._Uri,
1217
- timeout=5,
1218
- headers=reqHeaders,
1219
- params=reqParams
1220
- )
1221
-
1214
+ # try connecting to the device until we timeout or there are no more "connection refused" errors.
1215
+ loopTotalDelay:float = 0
1216
+ LOOP_DELAY:float = 0.25
1217
+ LOOP_TIMEOUT:float = 10.0
1218
+ while True:
1219
+
1220
+ try:
1221
+
1222
+ # execute spotify zeroconf api request.
1223
+ response = requests.get(
1224
+ self._Uri,
1225
+ timeout=5,
1226
+ headers=reqHeaders,
1227
+ params=reqParams
1228
+ )
1229
+
1230
+ # TEST TODO - force a connection refused error for testing.
1231
+ # if (self._HostIpAddress == "192.168.1.82"):
1232
+ # if loopTotalDelay > 1:
1233
+ # pass
1234
+ # else:
1235
+ # raise Exception("Failed to establish a new connection: [Errno 111] Connection refused")
1236
+ # #raise ValueError("Some other request exception test")
1237
+
1238
+ # if device connection was previously delayed, then log the info
1239
+ # as it may be helpful in debugging other issues.
1240
+ if (loopTotalDelay > 0):
1241
+ _logsi.LogVerbose("Spotify Connect device (ip=%s:%s) started accepting connections after %f seconds from initial request" % (self._HostIpAddress, self._HostIpPort, loopTotalDelay))
1242
+
1243
+ # no exception; break out of retry loop.
1244
+ break
1245
+
1246
+ except Exception as ex:
1247
+
1248
+ # check for connection refused error.
1249
+ exMsg:str = str(ex).lower()
1250
+ if (exMsg.find("connection refused") > -1):
1251
+
1252
+ # retry after a slight delay.
1253
+ _logsi.LogVerbose("Spotify Connect device (ip=%s:%s) connection refused exception; delaying for %s seconds to allow the device to start accepting connections" % (self._HostIpAddress, self._HostIpPort, LOOP_DELAY))
1254
+ time.sleep(LOOP_DELAY)
1255
+ loopTotalDelay = loopTotalDelay + LOOP_DELAY
1256
+
1257
+ # only check so many times before we give up.
1258
+ if (loopTotalDelay >= LOOP_TIMEOUT):
1259
+ _logsi.LogWarning("Spotify Connect device (ip=%s:%s) wait time exceeded for device to start accepting connections; gave up after %f seconds from initial request" % (self._HostIpAddress, self._HostIpPort, loopTotalDelay))
1260
+ raise
1261
+
1262
+ else:
1263
+
1264
+ # some other exception occured - stop retrying the connection.
1265
+ raise
1266
+
1222
1267
  # check response for initial errors, and return json response.
1223
1268
  responseData:dict = self._CheckResponseForErrors(response, apiMethodName, endpoint)
1224
1269