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.
Files changed (138) hide show
  1. spotifywebapipython/__init__.py +35 -0
  2. spotifywebapipython/const.py +114 -0
  3. spotifywebapipython/docs/__init__.html +2156 -0
  4. spotifywebapipython/docs/codecopy.js +70 -0
  5. spotifywebapipython/docs/index.html +514 -0
  6. spotifywebapipython/docs/search.js +46 -0
  7. spotifywebapipython/docs/spotifywebapipython/const.html +636 -0
  8. spotifywebapipython/docs/spotifywebapipython/saappmessages.html +540 -0
  9. spotifywebapipython/docs/spotifywebapipython/sautils.html +620 -0
  10. spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html +341 -0
  11. spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html +598 -0
  12. spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html +612 -0
  13. spotifywebapipython/docs/spotifywebapipython/spotifyclient.html +16557 -0
  14. spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +518 -0
  15. spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +421 -0
  16. spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +400 -0
  17. spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html +635 -0
  18. spotifywebapipython/docs/spotifywebapipython.ico +0 -0
  19. spotifywebapipython/docs/spotifywebapipython_OLD.ico +0 -0
  20. spotifywebapipython/models/__init__.py +121 -0
  21. spotifywebapipython/models/album.py +170 -0
  22. spotifywebapipython/models/albumpagesaved.py +85 -0
  23. spotifywebapipython/models/albumpagesimplified.py +88 -0
  24. spotifywebapipython/models/albumsaved.py +102 -0
  25. spotifywebapipython/models/albumsimplified.py +308 -0
  26. spotifywebapipython/models/artist.py +146 -0
  27. spotifywebapipython/models/artistinfo.py +282 -0
  28. spotifywebapipython/models/artistinfotourevent.py +127 -0
  29. spotifywebapipython/models/artistpage.py +70 -0
  30. spotifywebapipython/models/artistsimplified.py +163 -0
  31. spotifywebapipython/models/audiobook.py +87 -0
  32. spotifywebapipython/models/audiobookpagesimplified.py +88 -0
  33. spotifywebapipython/models/audiobooksimplified.py +362 -0
  34. spotifywebapipython/models/audiofeatures.py +333 -0
  35. spotifywebapipython/models/author.py +67 -0
  36. spotifywebapipython/models/category.py +168 -0
  37. spotifywebapipython/models/categorypage.py +70 -0
  38. spotifywebapipython/models/chapter.py +84 -0
  39. spotifywebapipython/models/chapterpagesimplified.py +88 -0
  40. spotifywebapipython/models/chaptersimplified.py +387 -0
  41. spotifywebapipython/models/context.py +113 -0
  42. spotifywebapipython/models/copyright.py +79 -0
  43. spotifywebapipython/models/device.py +301 -0
  44. spotifywebapipython/models/episode.py +84 -0
  45. spotifywebapipython/models/episodepagesaved.py +85 -0
  46. spotifywebapipython/models/episodepagesimplified.py +88 -0
  47. spotifywebapipython/models/episodesaved.py +99 -0
  48. spotifywebapipython/models/episodesimplified.py +370 -0
  49. spotifywebapipython/models/explicitcontent.py +80 -0
  50. spotifywebapipython/models/externalids.py +91 -0
  51. spotifywebapipython/models/externalurls.py +104 -0
  52. spotifywebapipython/models/followers.py +81 -0
  53. spotifywebapipython/models/imageobject.py +193 -0
  54. spotifywebapipython/models/imagevibrantcolors.py +182 -0
  55. spotifywebapipython/models/linkedfrom.py +165 -0
  56. spotifywebapipython/models/narrator.py +67 -0
  57. spotifywebapipython/models/owner.py +162 -0
  58. spotifywebapipython/models/pageobject.py +343 -0
  59. spotifywebapipython/models/playeractions.py +196 -0
  60. spotifywebapipython/models/playerlastplayedinfo.py +334 -0
  61. spotifywebapipython/models/playerplaystate.py +467 -0
  62. spotifywebapipython/models/playerqueueinfo.py +207 -0
  63. spotifywebapipython/models/playhistory.py +136 -0
  64. spotifywebapipython/models/playhistorypage.py +85 -0
  65. spotifywebapipython/models/playlist.py +125 -0
  66. spotifywebapipython/models/playlistpage.py +85 -0
  67. spotifywebapipython/models/playlistpagesimplified.py +107 -0
  68. spotifywebapipython/models/playlistsimplified.py +334 -0
  69. spotifywebapipython/models/playlisttrack.py +143 -0
  70. spotifywebapipython/models/playlisttracksummary.py +80 -0
  71. spotifywebapipython/models/recommendationseed.py +135 -0
  72. spotifywebapipython/models/restrictions.py +86 -0
  73. spotifywebapipython/models/resumepoint.py +79 -0
  74. spotifywebapipython/models/searchresponse.py +391 -0
  75. spotifywebapipython/models/searchresultbase.py +299 -0
  76. spotifywebapipython/models/show.py +87 -0
  77. spotifywebapipython/models/showpagesaved.py +85 -0
  78. spotifywebapipython/models/showpagesimplified.py +88 -0
  79. spotifywebapipython/models/showsaved.py +99 -0
  80. spotifywebapipython/models/showsimplified.py +320 -0
  81. spotifywebapipython/models/spotifyconnectdevice.py +321 -0
  82. spotifywebapipython/models/spotifyconnectdevices.py +441 -0
  83. spotifywebapipython/models/track.py +142 -0
  84. spotifywebapipython/models/trackpage.py +110 -0
  85. spotifywebapipython/models/trackpagesaved.py +85 -0
  86. spotifywebapipython/models/trackpagesimplified.py +88 -0
  87. spotifywebapipython/models/trackrecommendations.py +88 -0
  88. spotifywebapipython/models/tracksaved.py +99 -0
  89. spotifywebapipython/models/tracksimplified.py +398 -0
  90. spotifywebapipython/models/userprofile.py +184 -0
  91. spotifywebapipython/models/userprofilesimplified.py +206 -0
  92. spotifywebapipython/models/zeroconfdiscoveryresult.py +592 -0
  93. spotifywebapipython/models/zeroconfproperty.py +93 -0
  94. spotifywebapipython/oauthcli/__init__.py +7 -0
  95. spotifywebapipython/oauthcli/authclient.py +1229 -0
  96. spotifywebapipython/saappmessages.py +86 -0
  97. spotifywebapipython/sautils.py +285 -0
  98. spotifywebapipython/spotifyapierror.py +66 -0
  99. spotifywebapipython/spotifyapimessage.py +269 -0
  100. spotifywebapipython/spotifyauthtoken.py +264 -0
  101. spotifywebapipython/spotifyclient.py +17885 -0
  102. spotifywebapipython/spotifyconnect/__init__.py +19 -0
  103. spotifywebapipython/spotifyconnect/spotifyconnectdeviceeventargs.py +67 -0
  104. spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py +2564 -0
  105. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastapptask.py +467 -0
  106. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastcontroller.py +701 -0
  107. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastlistener.py +359 -0
  108. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastmultizonelistener.py +212 -0
  109. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfexceptions.py +20 -0
  110. spotifywebapipython/spotifyconnect/spotifyconnectzeroconflistener.py +335 -0
  111. spotifywebapipython/spotifydiscovery.py +473 -0
  112. spotifywebapipython/spotifymediatypes.py +53 -0
  113. spotifywebapipython/spotifywebapiauthenticationerror.py +122 -0
  114. spotifywebapipython/spotifywebapierror.py +106 -0
  115. spotifywebapipython/spotifywebplayertoken.py +743 -0
  116. spotifywebapipython/vibrant/__init__.py +17 -0
  117. spotifywebapipython/vibrant/generator.py +261 -0
  118. spotifywebapipython/vibrant/image.py +100 -0
  119. spotifywebapipython/vibrant/main.py +34 -0
  120. spotifywebapipython/vibrant/models.py +93 -0
  121. spotifywebapipython/vibrant/utils.py +56 -0
  122. spotifywebapipython/zeroconfapi/__init__.py +23 -0
  123. spotifywebapipython/zeroconfapi/authenticationtypes.py +38 -0
  124. spotifywebapipython/zeroconfapi/blobbuilder.py +370 -0
  125. spotifywebapipython/zeroconfapi/credentials.py +37 -0
  126. spotifywebapipython/zeroconfapi/cryptodiffiehellman.py +89 -0
  127. spotifywebapipython/zeroconfapi/helpers.py +64 -0
  128. spotifywebapipython/zeroconfapi/spotifyzeroconfapierror.py +108 -0
  129. spotifywebapipython/zeroconfapi/zeroconfconnect.py +1327 -0
  130. spotifywebapipython/zeroconfapi/zeroconfgetinfo.py +572 -0
  131. spotifywebapipython/zeroconfapi/zeroconfgetinfoalias.py +109 -0
  132. spotifywebapipython/zeroconfapi/zeroconfgetinfodrmmediaformat.py +90 -0
  133. spotifywebapipython/zeroconfapi/zeroconfresponse.py +195 -0
  134. spotifywebapipython-1.0.242.dist-info/LICENSE +22 -0
  135. spotifywebapipython-1.0.242.dist-info/METADATA +219 -0
  136. spotifywebapipython-1.0.242.dist-info/RECORD +138 -0
  137. spotifywebapipython-1.0.242.dist-info/WHEEL +5 -0
  138. spotifywebapipython-1.0.242.dist-info/top_level.txt +1 -0
@@ -0,0 +1,17 @@
1
+ # "vibrant" namespace was imported from "vibrant-python" namespace (https://github.com/totallynotadi/vibrant-python)
2
+ # had to import it, as the "vibrant-python" version has a dependency that was not compatible with current version of Home Assistant:
3
+ # - "vibrant-python==0.1.6 depends on pillow>=10.1.0,<11.0.0"
4
+
5
+ # import all classes from the namespace.
6
+ from .image import VibrantImage
7
+ from .main import Vibrant
8
+ from .models import Palette, Props, Swatch
9
+ from .utils import hsl_to_rgb, rgb_to_hsl
10
+
11
+ # all classes to import when "import *" is specified.
12
+ __all__ = [
13
+ 'VibrantImage',
14
+ 'Vibrant',
15
+ 'Palette',
16
+ 'Swatch',
17
+ ]
@@ -0,0 +1,261 @@
1
+ from typing import List
2
+
3
+ from .models import GeneratorOpts, Palette, Swatch, hsl_to_rgb
4
+
5
+ generator_opts = GeneratorOpts(
6
+ target_dark_luma=0.26,
7
+ max_dark_luma=0.45,
8
+ min_light_luma=0.55,
9
+ target_light_luma=0.74,
10
+ min_normal_luma=0.3,
11
+ target_normal_luma=0.5,
12
+ max_normal_luma=0.7,
13
+ target_muted_saturation=0.3,
14
+ max_muted_saturation=0.4,
15
+ target_vibrant_saturation=1.0,
16
+ min_vibrant_saturation=0.35,
17
+ weight_saturation=3,
18
+ weight_luma=6.5,
19
+ weight_population=0.5,
20
+ )
21
+
22
+
23
+ def find_max_population(swatches: List[Swatch]) -> int:
24
+ p = 0
25
+ for swatch in swatches:
26
+ p = max(p, swatch.population)
27
+ return p
28
+
29
+
30
+ def is_already_selected(palette: Palette, swatch: Swatch) -> bool:
31
+ return (
32
+ palette.vibrant == swatch
33
+ or palette.dark_vibrant == swatch
34
+ or palette.light_vibrant == swatch
35
+ or palette.muted == swatch
36
+ or palette.dark_muted == swatch
37
+ or palette.light_muted == swatch
38
+ )
39
+
40
+
41
+ def create_comparison_value(
42
+ saturation: int,
43
+ targetSaturation: int,
44
+ luma: int,
45
+ targetLuma: int,
46
+ population: int,
47
+ maxPopulation: int,
48
+ opts: GeneratorOpts,
49
+ ) -> int:
50
+ def weighted_mean(values: List[int]):
51
+ sum = 0
52
+ weight_sum = 0
53
+ for i in range(0, len(values), 2):
54
+ value = values[i]
55
+ weight = values[i + 1]
56
+ sum += value * weight
57
+ weight_sum += weight
58
+
59
+ return sum / weight_sum
60
+
61
+ def invert_diff(value: int, target_value: int) -> int:
62
+ return 1 - abs(value - target_value)
63
+
64
+ return weighted_mean(
65
+ [
66
+ invert_diff(saturation, targetSaturation),
67
+ opts.weight_saturation,
68
+ invert_diff(luma, targetLuma),
69
+ opts.weight_luma,
70
+ population // maxPopulation,
71
+ opts.weight_population,
72
+ ]
73
+ )
74
+
75
+
76
+ def find_color_variation(
77
+ palette: Palette,
78
+ swatches: List[Swatch],
79
+ max_population: int,
80
+ target_luma: int,
81
+ min_luma: int,
82
+ max_luma: int,
83
+ target_saturation: int,
84
+ min_saturation: int,
85
+ max_saturation: int,
86
+ opts: GeneratorOpts,
87
+ ) -> Swatch:
88
+ max_swatch: Swatch = None
89
+ max_value = 0
90
+
91
+ for swatch in swatches:
92
+ h, s, l = swatch.hsl
93
+ if (
94
+ s >= min_saturation
95
+ and s <= max_saturation
96
+ and l >= min_luma
97
+ and l <= max_luma
98
+ and not is_already_selected(palette, swatch)
99
+ ):
100
+ value = create_comparison_value(
101
+ s,
102
+ target_saturation,
103
+ l,
104
+ target_luma,
105
+ swatch.population,
106
+ max_population,
107
+ opts,
108
+ )
109
+ if max_swatch == None or value > max_value:
110
+ max_swatch = swatch
111
+ max_value = value
112
+
113
+ return max_swatch
114
+
115
+
116
+ def generate_variation_colors(
117
+ swatches: List[Swatch], maxPopulation: int, opts: GeneratorOpts
118
+ ) -> Palette:
119
+ palette: Palette = Palette()
120
+
121
+ palette.vibrant = find_color_variation(
122
+ palette,
123
+ swatches,
124
+ maxPopulation,
125
+ opts.target_normal_luma,
126
+ opts.min_normal_luma,
127
+ opts.max_normal_luma,
128
+ opts.target_vibrant_saturation,
129
+ opts.min_vibrant_saturation,
130
+ 1,
131
+ opts,
132
+ )
133
+
134
+ palette.light_vibrant = find_color_variation(
135
+ palette,
136
+ swatches,
137
+ maxPopulation,
138
+ opts.target_light_luma,
139
+ opts.min_light_luma,
140
+ 1,
141
+ opts.target_vibrant_saturation,
142
+ opts.min_vibrant_saturation,
143
+ 1,
144
+ opts,
145
+ )
146
+
147
+ palette.dark_vibrant = find_color_variation(
148
+ palette,
149
+ swatches,
150
+ maxPopulation,
151
+ opts.target_dark_luma,
152
+ 0,
153
+ opts.max_dark_luma,
154
+ opts.target_vibrant_saturation,
155
+ opts.min_vibrant_saturation,
156
+ 1,
157
+ opts,
158
+ )
159
+
160
+ palette.muted = find_color_variation(
161
+ palette,
162
+ swatches,
163
+ maxPopulation,
164
+ opts.target_normal_luma,
165
+ opts.min_normal_luma,
166
+ opts.max_normal_luma,
167
+ opts.target_muted_saturation,
168
+ 0,
169
+ opts.max_muted_saturation,
170
+ opts,
171
+ )
172
+
173
+ palette.light_muted = find_color_variation(
174
+ palette,
175
+ swatches,
176
+ maxPopulation,
177
+ opts.target_light_luma,
178
+ opts.min_light_luma,
179
+ 1,
180
+ opts.target_muted_saturation,
181
+ 0,
182
+ opts.max_muted_saturation,
183
+ opts,
184
+ )
185
+
186
+ palette.dark_muted = find_color_variation(
187
+ palette,
188
+ swatches,
189
+ maxPopulation,
190
+ opts.target_dark_luma,
191
+ 0,
192
+ opts.max_dark_luma,
193
+ opts.target_muted_saturation,
194
+ 0,
195
+ opts.max_muted_saturation,
196
+ opts,
197
+ )
198
+
199
+ return palette
200
+
201
+
202
+ def generate_empty_swatches(palette: Palette, opts: GeneratorOpts) -> None:
203
+ if (
204
+ palette.vibrant is None
205
+ and palette.dark_vibrant is None
206
+ and palette.light_vibrant is None
207
+ ):
208
+ if palette.dark_vibrant is None and palette.dark_muted is not None:
209
+ [h, s, l] = palette.dark_muted.hsl
210
+ l = opts.target_dark_luma
211
+ palette.dark_vibrant = Swatch(hsl_to_rgb([h, s, l]), 0)
212
+
213
+ if palette.light_vibrant is None and palette.light_muted is not None:
214
+ [h, s, l] = palette.light_muted.hsl
215
+ l = opts.target_dark_luma
216
+ palette.dark_vibrant = Swatch(hsl_to_rgb([h, s, l]), 0)
217
+
218
+ if palette.vibrant is None and palette.dark_vibrant is not None:
219
+ [h, s, l] = palette.dark_vibrant.hsl
220
+ l = opts.target_normal_luma
221
+ palette.vibrant = Swatch(hsl_to_rgb([h, s, l]), 0)
222
+ elif palette.vibrant is None and palette.light_vibrant is not None:
223
+ [h, s, l] = palette.light_vibrant.hsl
224
+ l = opts.target_normal_luma
225
+ palette.vibrant = Swatch(hsl_to_rgb([h, s, l]), 0)
226
+
227
+ if palette.dark_vibrant is None and palette.vibrant is not None:
228
+ [h, s, l] = palette.vibrant.hsl
229
+ l = opts.target_dark_luma
230
+ palette.dark_vibrant = Swatch(hsl_to_rgb([h, s, l]), 0)
231
+
232
+ if palette.light_vibrant is None and palette.vibrant is not None:
233
+ [h, s, l] = palette.vibrant.hsl
234
+ l = opts.target_light_luma
235
+ palette.light_vibrant = Swatch(hsl_to_rgb([h, s, l]), 0)
236
+
237
+ if palette.muted is None and palette.vibrant is not None:
238
+ [h, s, l] = palette.vibrant.hsl
239
+ l = opts.target_muted_saturation
240
+ palette.muted = Swatch(hsl_to_rgb([h, s, l]), 0)
241
+
242
+ if palette.dark_muted is None and palette.dark_vibrant is not None:
243
+ h, s, l = palette.dark_vibrant.hsl
244
+ l = opts.target_muted_saturation
245
+ palette.dark_muted = Swatch(hsl_to_rgb([h, s, l]), 0)
246
+
247
+ if palette.light_muted is None and palette.light_vibrant is not None:
248
+ h, s, l = palette.light_vibrant.hsl
249
+ l = opts.target_muted_saturation
250
+ palette.light_muted = Swatch(hsl=[h, s, l], population=0)
251
+
252
+
253
+ def generate(swatches: List[Swatch]) -> Palette:
254
+ max_poplation = find_max_population(swatches)
255
+
256
+ palette: Palette = generate_variation_colors(
257
+ swatches, max_poplation, generator_opts
258
+ )
259
+ generate_empty_swatches(palette, generator_opts)
260
+
261
+ return palette
@@ -0,0 +1,100 @@
1
+ import io
2
+ import os
3
+ from typing import List, Optional, Tuple, Union
4
+ import requests
5
+ from PIL import Image
6
+ from PIL.Image import Image as PILImage
7
+
8
+ from .models import Props, Swatch
9
+
10
+
11
+ class VibrantImage:
12
+ def __init__(
13
+ self,
14
+ src: Union[
15
+ bytes,
16
+ str,
17
+ io.BytesIO,
18
+ io.BufferedReader,
19
+ PILImage,
20
+ "VibrantImage",
21
+ ],
22
+ props: Optional[Props] = Props(),
23
+ ) -> None:
24
+ self.image: Image = None
25
+ self.props: Props = props
26
+
27
+ if isinstance(src, str):
28
+ if src.startswith("http"):
29
+ src = requests.get(src).content
30
+ if not os.path.exists(src):
31
+ raise FileNotFoundError("Image doesn't exist at given path - %s." % src)
32
+
33
+ if isinstance(src, bytes):
34
+ src = io.BytesIO(src)
35
+
36
+ if isinstance(src, PILImage):
37
+ self.image = src
38
+ else:
39
+ self.image = Image.open(src)
40
+
41
+ @classmethod
42
+ def from_url(cls, src: str) -> "VibrantImage":
43
+ src = requests.get(src).content
44
+ src = io.BytesIO(src)
45
+ return cls(Image.open(src))
46
+
47
+ @classmethod
48
+ def from_path(cls, src: str) -> "VibrantImage":
49
+ if os.path.exists(src):
50
+ return cls(Image.open(src))
51
+ raise FileNotFoundError("Image doesn't exist at given path - %s." % src)
52
+
53
+ @classmethod
54
+ def from_bytes(cls, src: bytes) -> "VibrantImage":
55
+ src = io.BytesIO(src)
56
+ return cls(Image.open(src))
57
+
58
+ @classmethod
59
+ def from_fp(cls, fp: io.BufferedReader) -> "VibrantImage":
60
+ return cls(Image.open(fp))
61
+
62
+ def scale_down(self):
63
+ ...
64
+
65
+ def _swatch_filter(self, swatch: List[int]) -> bool:
66
+ r, g, b = swatch.rgb
67
+ return not (r > 250 and g > 250 and b > 250)
68
+
69
+ def _parse_swatches(
70
+ self,
71
+ raw_swatches: List,
72
+ swatch_populations: List[Tuple[int, int]],
73
+ ) -> List[Swatch]:
74
+ swatches = []
75
+ curr_idx = 0
76
+ for idx in range(0, len(raw_swatches), 3):
77
+ if idx + 2 <= (len(raw_swatches) - 1):
78
+ swatches.append(
79
+ Swatch(
80
+ rgb=[
81
+ raw_swatches[idx],
82
+ raw_swatches[idx + 1],
83
+ raw_swatches[idx + 2],
84
+ ],
85
+ population=swatch_populations[curr_idx][0],
86
+ )
87
+ )
88
+ curr_idx += 1
89
+ return swatches
90
+
91
+ def quantize(self) -> List[Swatch]:
92
+ self.image = self.image.quantize(self.props.color_count)
93
+ raw_swatches = self.image.getpalette()
94
+ raw_swatches = list(filter(lambda x: x != 0, raw_swatches))
95
+ swatch_populations = self.image.getcolors(self.props.color_count)
96
+ swatches = self._parse_swatches(
97
+ raw_swatches=raw_swatches,
98
+ swatch_populations=swatch_populations,
99
+ )
100
+ return swatches
@@ -0,0 +1,34 @@
1
+ import io
2
+ from typing import Union
3
+
4
+ from PIL.Image import Image as PILImage
5
+
6
+ from .generator import generate
7
+ from .image import VibrantImage
8
+ from .models import Palette, Props
9
+
10
+
11
+ class Vibrant:
12
+ props: Props
13
+
14
+ def __init__(self, color_count=64, quality=5) -> None:
15
+ self.props = Props(color_count=color_count, quality=quality)
16
+
17
+ def get_palette(
18
+ self,
19
+ src: Union[
20
+ bytes,
21
+ str,
22
+ io.BytesIO,
23
+ io.BufferedReader,
24
+ PILImage,
25
+ VibrantImage,
26
+ ],
27
+ ) -> Palette:
28
+ image = src
29
+ if not isinstance(src, VibrantImage):
30
+ image = VibrantImage(src, self.props)
31
+ image.scale_down()
32
+ swatches = image.quantize()
33
+ palette = generate(swatches)
34
+ return palette
@@ -0,0 +1,93 @@
1
+ from dataclasses import dataclass
2
+ from typing import List, Optional
3
+
4
+ from .utils import hsl_to_rgb, rgb_to_hsl
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class Swatch:
9
+ rgb: List[int]
10
+ population: int
11
+ hsl: Optional[List[float]]
12
+
13
+ def __init__(self, rgb=[], population=0, hsl=[]):
14
+ super().__init__()
15
+ if rgb and not hsl:
16
+ object.__setattr__(self, "rgb", rgb)
17
+ object.__setattr__(self, "hsl", rgb_to_hsl(rgb))
18
+ elif hsl and not rgb:
19
+ object.__setattr__(self, "hsl", hsl)
20
+ object.__setattr__(self, "rgb", hsl_to_rgb(hsl))
21
+ object.__setattr__(self, "population", population)
22
+
23
+ @property
24
+ def rgb_hex(self) -> str:
25
+ """
26
+ The RGB value expressed in displayable hex string format (e.g. "#ff560a").
27
+ """
28
+ return f"#{self.rgb[0]:02x}{self.rgb[1]:02x}{self.rgb[2]:02x}"
29
+
30
+ def ToDictionary(self) -> dict:
31
+ """
32
+ Returns a dictionary representation of the class.
33
+ """
34
+ result:dict = \
35
+ {
36
+ 'rgb_hex': self.rgb_hex,
37
+ 'rgb': self.rgb,
38
+ 'population': self.population,
39
+ 'hsl': self.hsl
40
+ }
41
+ return result
42
+
43
+ def ToString(self, includeTitle:bool=False) -> str:
44
+ """
45
+ Returns a displayable string representation of the class.
46
+
47
+ Args:
48
+ includeTitle (str):
49
+ True to include the class name title prefix.
50
+ """
51
+ msg:str = ''
52
+ if includeTitle:
53
+ msg = 'Swatch:'
54
+
55
+ msg = '%s RGBHex="%s"' % (msg, self.rgb_hex)
56
+ msg = '%s RGB=%s' % (msg, str(self.rgb))
57
+ msg = '%s Population=%s' % (msg, str(self.population))
58
+ msg = '%s HSL=%s' % (msg, str(self.hsl))
59
+ return msg
60
+
61
+
62
+ @dataclass
63
+ class Palette:
64
+ vibrant: Swatch = None
65
+ dark_vibrant: Swatch = None
66
+ light_vibrant: Swatch = None
67
+ muted: Swatch = None
68
+ dark_muted: Swatch = None
69
+ light_muted: Swatch = None
70
+
71
+
72
+ @dataclass
73
+ class Props:
74
+ color_count: int = 64
75
+ quality: int = 5
76
+
77
+
78
+ @dataclass
79
+ class GeneratorOpts:
80
+ target_dark_luma: int
81
+ max_dark_luma: int
82
+ min_light_luma: int
83
+ target_light_luma: int
84
+ min_normal_luma: int
85
+ target_normal_luma: int
86
+ max_normal_luma: int
87
+ target_muted_saturation: int
88
+ max_muted_saturation: int
89
+ target_vibrant_saturation: int
90
+ min_vibrant_saturation: int
91
+ weight_saturation: int
92
+ weight_luma: int
93
+ weight_population: int
@@ -0,0 +1,56 @@
1
+ from math import ceil
2
+ from typing import List
3
+
4
+
5
+ def rgb_to_hsl(rgb: List[int]) -> List[float]:
6
+ r, g, b = rgb
7
+ r /= 255
8
+ g /= 255
9
+ b /= 255
10
+ min_val = min(r, g, b)
11
+ max_val = max(r, g, b)
12
+
13
+ l = (max_val + min_val) / 2
14
+
15
+ if min_val == max_val:
16
+ s = 0
17
+ elif l <= 0.5:
18
+ s = (max_val - min_val) / (max_val + min_val)
19
+ elif l > 0.5:
20
+ s = (max_val - min_val) / (2.0 - max_val - min_val)
21
+
22
+ if max_val - min_val == 0:
23
+ h = 0
24
+ elif max_val == r:
25
+ h = ((g - b) / (max_val - min_val)) % 6
26
+ elif max_val == g:
27
+ h = (2.0) + (b - r) / (max_val - min_val)
28
+ else:
29
+ h = 4.0 + (r - g) / (max_val - min_val)
30
+ h = h * 60
31
+ h = h + 360 if h < 0 else h
32
+
33
+ return [round(val, 3) for val in (h, s, l)]
34
+
35
+
36
+ def hsl_to_rgb(hsl: List[float]) -> List[int]:
37
+ h, s, l = hsl
38
+ c = (1 - abs((2 * l) - 1)) * s
39
+ x = c * (1 - abs(((h / 60) % 2) - 1))
40
+ m = l - c / 2
41
+
42
+ if 0 <= h < 60:
43
+ r, g, b = c, x, 0
44
+ elif 60 <= h < 120:
45
+ r, g, b = x, c, 0
46
+ elif 120 <= h < 180:
47
+ r, g, b = 0, c, x
48
+ elif 180 <= h < 240:
49
+ r, g, b = 0, x, c
50
+ elif 240 <= h < 300:
51
+ r, g, b = x, 0, c
52
+ elif 300 <= h < 360:
53
+ r, g, b = c, 0, x
54
+
55
+ r, g, b = (r + m) * 255, (g + m) * 255, (b + m) * 255
56
+ return [ceil(val) for val in (r, g, b)]
@@ -0,0 +1,23 @@
1
+ # import all classes from the namespace.
2
+ from .authenticationtypes import AuthenticationTypes
3
+ from .blobbuilder import BlobBuilder
4
+ from .credentials import Credentials
5
+ from .spotifyzeroconfapierror import SpotifyZeroconfApiError
6
+ from .zeroconfconnect import ZeroconfConnect
7
+ from .zeroconfresponse import ZeroconfResponse
8
+ from .zeroconfgetinfo import ZeroconfGetInfo
9
+ from .zeroconfgetinfoalias import ZeroconfGetInfoAlias
10
+ from .zeroconfgetinfodrmmediaformat import ZeroconfGetInfoDrmMediaFormat
11
+
12
+ # all classes to import when "import *" is specified.
13
+ __all__ = [
14
+ 'AuthenticationTypes',
15
+ 'BlobBuilder',
16
+ 'Credentials',
17
+ 'SpotifyZeroconfApiError',
18
+ 'ZeroconfConnect',
19
+ 'ZeroconfGetInfo',
20
+ 'ZeroconfGetInfoAlias',
21
+ 'ZeroconfGetInfoDrmMediaFormat',
22
+ 'ZeroconfResponse',
23
+ ]
@@ -0,0 +1,38 @@
1
+ # external package imports.
2
+ from enum import IntEnum
3
+
4
+ # our package imports.
5
+ from ..sautils import export
6
+
7
+
8
+ @export
9
+ class AuthenticationTypes(IntEnum):
10
+ """
11
+ Represents the type of authentication used.
12
+ """
13
+
14
+ USER_PASS = 0
15
+ """
16
+ UserId and Password (0x00).
17
+ """
18
+
19
+ STORED_SPOTIFY_CREDENTIALS = 1
20
+ """
21
+ Spotify credentials (0x01).
22
+ """
23
+
24
+ STORED_FACEBOOK_CREDENTIALS = 2
25
+ """
26
+ Facebook credentials (0x02).
27
+ """
28
+
29
+ SPOTIFY_TOKEN = 3
30
+ """
31
+ Spotify authorization token (0x03).
32
+ """
33
+
34
+ FACEBOOK_TOKEN = 4
35
+ """
36
+ Facebook authorization token (0x04).
37
+ """
38
+