aboutsummaryrefslogtreecommitdiff
path: root/pandora/models/pandora.py
diff options
context:
space:
mode:
Diffstat (limited to 'pandora/models/pandora.py')
-rw-r--r--pandora/models/pandora.py475
1 files changed, 0 insertions, 475 deletions
diff --git a/pandora/models/pandora.py b/pandora/models/pandora.py
deleted file mode 100644
index 341b0be..0000000
--- a/pandora/models/pandora.py
+++ /dev/null
@@ -1,475 +0,0 @@
1from enum import Enum
2
3from ..client import BaseAPIClient
4from ..errors import ParameterMissing
5from ._base import Field, DateField, SyntheticField
6from ._base import PandoraModel, PandoraListModel, PandoraDictListModel
7
8
9class AdditionalAudioUrl(Enum):
10 HTTP_40_AAC_MONO = 'HTTP_40_AAC_MONO'
11 HTTP_64_AAC = 'HTTP_64_AAC'
12 HTTP_32_AACPLUS = 'HTTP_32_AACPLUS'
13 HTTP_64_AACPLUS = 'HTTP_64_AACPLUS'
14 HTTP_24_AACPLUS_ADTS = 'HTTP_24_AACPLUS_ADTS'
15 HTTP_32_AACPLUS_ADTS = 'HTTP_32_AACPLUS_ADTS'
16 HTTP_64_AACPLUS_ADTS = 'HTTP_64_AACPLUS_ADTS'
17 HTTP_128_MP3 = 'HTTP_128_MP3'
18 HTTP_32_WMA = 'HTTP_32_WMA'
19
20
21class PandoraType(Enum):
22
23 TRACK = "TR"
24 ARTIST = "AR"
25 GENRE = "GR"
26
27 @staticmethod
28 def from_model(client, value):
29 return PandoraType.from_string(value)
30
31 @staticmethod
32 def from_string(value):
33 return {
34 "TR": PandoraType.TRACK,
35 "AR": PandoraType.ARTIST,
36 }.get(value, PandoraType.GENRE)
37
38
39class Icon(PandoraModel):
40
41 dominant_color = Field("dominantColor")
42 art_url = Field("artUrl")
43
44
45class StationSeed(PandoraModel):
46
47 seed_id = Field("seedId")
48 music_token = Field("musicToken")
49 pandora_id = Field("pandoraId")
50 pandora_type = Field("pandoraType", formatter=PandoraType.from_model)
51
52 genre_name = Field("genreName")
53 song_name = Field("songName")
54 artist_name = Field("artistName")
55 art_url = Field("artUrl")
56 icon = Field("icon", model=Icon)
57
58
59class StationSeeds(PandoraModel):
60
61 genres = Field("genres", model=StationSeed)
62 songs = Field("songs", model=StationSeed)
63 artists = Field("artists", model=StationSeed)
64
65
66class SongFeedback(PandoraModel):
67
68 feedback_id = Field("feedbackId")
69 song_identity = Field("songIdentity")
70 is_positive = Field("isPositive")
71 pandora_id = Field("pandoraId")
72 album_art_url = Field("albumArtUrl")
73 music_token = Field("musicToken")
74 song_name = Field("songName")
75 artist_name = Field("artistName")
76 pandora_type = Field("pandoraType", formatter=PandoraType.from_model)
77 date_created = DateField("dateCreated")
78
79
80class StationFeedback(PandoraModel):
81
82 total_thumbs_up = Field("totalThumbsUp")
83 total_thumbs_down = Field("totalThumbsDown")
84 thumbs_up = Field("thumbsUp", model=SongFeedback)
85 thumbs_down = Field("thumbsDown", model=SongFeedback)
86
87
88class Station(PandoraModel):
89
90 can_add_music = Field("allowAddMusic")
91 can_delete = Field("allowDelete")
92 can_rename = Field("allowRename")
93 can_edit_description = Field("allowEditDescription")
94 process_skips = Field("processSkips")
95 is_shared = Field("isShared")
96 is_quickmix = Field("isQuickMix")
97 is_genre_station = Field("isGenreStation")
98 is_thumbprint_station = Field("isThumbprint")
99
100 art_url = Field("artUrl")
101 date_created = DateField("dateCreated")
102 detail_url = Field("stationDetailUrl")
103 id = Field("stationId")
104 name = Field("stationName")
105 sharing_url = Field("stationSharingUrl")
106 thumb_count = Field("thumbCount")
107 token = Field("stationToken")
108
109 genre = Field("genre", [])
110 quickmix_stations = Field("quickMixStationIds", [])
111
112 seeds = Field("music", model=StationSeeds)
113 feedback = Field("feedback", model=StationFeedback)
114
115 def get_playlist(self, additional_urls=None):
116 return iter(self._api_client.get_playlist(self.token,
117 additional_urls))
118
119
120class GenreStation(PandoraModel):
121
122 id = Field("stationId")
123 name = Field("stationName")
124 token = Field("stationToken")
125 category = Field("categoryName")
126
127 def get_playlist(self): # pragma: no cover
128 raise NotImplementedError("Genre stations do not have playlists. "
129 "Create a real station using the token.")
130
131
132class StationList(PandoraListModel):
133
134 checksum = Field("checksum")
135
136 __index_key__ = "id"
137 __list_key__ = "stations"
138 __list_model__ = Station
139
140 def has_changed(self):
141 checksum = self._api_client.get_station_list_checksum()
142 return checksum != self.checksum
143
144
145class AudioField(SyntheticField):
146
147 def formatter(self, api_client, data, value):
148 """Get audio-related fields
149
150 Try to find fields for the audio url for specified preferred quality
151 level, or next-lowest available quality url otherwise.
152 """
153 url_map = data.get("audioUrlMap")
154 audio_url = data.get("audioUrl")
155
156 # Only an audio URL, not a quality map. This happens for most of the
157 # mobile client tokens and some of the others now. In this case
158 # substitute the empirically determined default values in the format
159 # used by the rest of the function so downstream consumers continue to
160 # work.
161 if audio_url and not url_map:
162 url_map = {
163 BaseAPIClient.HIGH_AUDIO_QUALITY: {
164 "audioUrl": audio_url,
165 "bitrate": 64,
166 "encoding": "aacplus",
167 }
168 }
169 elif not url_map: # No audio url available (e.g. ad tokens)
170 return None
171
172 valid_audio_formats = [BaseAPIClient.HIGH_AUDIO_QUALITY,
173 BaseAPIClient.MED_AUDIO_QUALITY,
174 BaseAPIClient.LOW_AUDIO_QUALITY]
175
176 # Only iterate over sublist, starting at preferred audio quality, or
177 # from the beginning of the list if nothing is found. Ensures that the
178 # bitrate used will always be the same or lower quality than was
179 # specified to prevent audio from skipping for slow connections.
180 preferred_quality = api_client.default_audio_quality
181 if preferred_quality in valid_audio_formats:
182 i = valid_audio_formats.index(preferred_quality)
183 valid_audio_formats = valid_audio_formats[i:]
184
185 for quality in valid_audio_formats:
186 audio_url = url_map.get(quality)
187
188 if audio_url:
189 return audio_url[self.field]
190
191 return audio_url[self.field] if audio_url else None
192
193
194class AdditionalUrlField(SyntheticField):
195
196 def formatter(self, api_client, data, value):
197 """Parse additional url fields and map them to inputs
198
199 Attempt to create a dictionary with keys being user input, and
200 response being the returned URL
201 """
202 if value is None:
203 return None
204
205 user_param = data['_paramAdditionalUrls']
206 urls = {}
207 if isinstance(value, str):
208 urls[user_param[0]] = value
209 else:
210 for key, url in zip(user_param, value):
211 urls[key] = url
212 return urls
213
214
215class PlaylistModel(PandoraModel):
216
217 def get_is_playable(self):
218 if not self.audio_url:
219 return False
220 return self._api_client.transport.test_url(self.audio_url)
221
222 def prepare_playback(self):
223 """Prepare Track for Playback
224
225 This method must be called by clients before beginning playback
226 otherwise the track recieved may not be playable.
227 """
228 return self
229
230 def thumbs_up(self): # pragma: no cover
231 raise NotImplementedError
232
233 def thumbs_down(self): # pragma: no cover
234 raise NotImplementedError
235
236 def bookmark_song(self): # pragma: no cover
237 raise NotImplementedError
238
239 def bookmark_artist(self): # pragma: no cover
240 raise NotImplementedError
241
242 def sleep(self): # pragma: no cover
243 raise NotImplementedError
244
245
246class PlaylistItem(PlaylistModel):
247
248 artist_name = Field("artistName")
249 album_name = Field("albumName")
250 song_name = Field("songName")
251 song_rating = Field("songRating")
252 track_gain = Field("trackGain")
253 track_length = Field("trackLength")
254 track_token = Field("trackToken")
255 audio_url = AudioField("audioUrl")
256 bitrate = AudioField("bitrate")
257 encoding = AudioField("encoding")
258 album_art_url = Field("albumArtUrl")
259 allow_feedback = Field("allowFeedback")
260 station_id = Field("stationId")
261
262 ad_token = Field("adToken")
263
264 album_detail_url = Field("albumDetailUrl")
265 album_explore_url = Field("albumExplorerUrl")
266
267 amazon_album_asin = Field("amazonAlbumAsin")
268 amazon_album_digital_asin = Field("amazonAlbumDigitalAsin")
269 amazon_album_url = Field("amazonAlbumUrl")
270 amazon_song_digital_asin = Field("amazonSongDigitalAsin")
271
272 artist_detail_url = Field("artistDetailUrl")
273 artist_explore_url = Field("artistExplorerUrl")
274
275 itunes_song_url = Field("itunesSongUrl")
276
277 song_detail_url = Field("songDetailUrl")
278 song_explore_url = Field("songExplorerUrl")
279
280 additional_audio_urls = AdditionalUrlField("additionalAudioUrl")
281
282 @property
283 def is_ad(self):
284 return self.ad_token is not None
285
286 def thumbs_up(self): # pragma: no cover
287 return self._api_client.add_feedback(self.track_token, True)
288
289 def thumbs_down(self): # pragma: no cover
290 return self._api_client.add_feedback(self.track_token, False)
291
292 def bookmark_song(self): # pragma: no cover
293 return self._api_client.add_song_bookmark(self.track_token)
294
295 def bookmark_artist(self): # pragma: no cover
296 return self._api_client.add_artist_bookmark(self.track_token)
297
298 def sleep(self): # pragma: no cover
299 return self._api_client.sleep_song(self.track_token)
300
301
302class AdItem(PlaylistModel):
303
304 title = Field("title")
305 company_name = Field("companyName")
306 tracking_tokens = Field("adTrackingTokens")
307 audio_url = AudioField("audioUrl")
308 image_url = Field("imageUrl")
309 click_through_url = Field("clickThroughUrl")
310 station_id = None
311 ad_token = None
312
313 @property
314 def is_ad(self):
315 return True
316
317 def register_ad(self, station_id=None):
318 if not station_id:
319 station_id = self.station_id
320 if self.tracking_tokens:
321 self._api_client.register_ad(station_id, self.tracking_tokens)
322 else:
323 raise ParameterMissing('No ad tracking tokens provided for '
324 'registration.')
325
326 def prepare_playback(self):
327 try:
328 self.register_ad(self.station_id)
329 except ParameterMissing as exc:
330 if self.tracking_tokens:
331 raise exc
332 return super(AdItem, self).prepare_playback()
333
334
335class Playlist(PandoraListModel):
336
337 __list_key__ = "items"
338 __list_model__ = PlaylistItem
339
340
341class Bookmark(PandoraModel):
342
343 music_token = Field("musicToken")
344 artist_name = Field("artistName")
345 art_url = Field("artUrl")
346 bookmark_token = Field("bookmarkToken")
347 date_created = DateField("dateCreated")
348
349 # song only
350 sample_url = Field("sampleUrl")
351 sample_gain = Field("sampleGain")
352 album_name = Field("albumName")
353 song_name = Field("songName")
354
355 @property
356 def is_song_bookmark(self):
357 return self.song_name is not None
358
359 def delete(self):
360 if self.is_song_bookmark:
361 self._api_client.delete_song_bookmark(self.bookmark_token)
362 else:
363 self._api_client.delete_artist_bookmark(self.bookmark_token)
364
365
366class BookmarkList(PandoraModel):
367
368 songs = Field("songs", model=Bookmark)
369 artists = Field("artists", model=Bookmark)
370
371
372class SearchResultItem(PandoraModel):
373
374 score = Field("score")
375 token = Field("musicToken")
376
377 @property
378 def is_song(self):
379 return isinstance(self, SongSearchResultItem)
380
381 @property
382 def is_artist(self):
383 return isinstance(self, ArtistSearchResultItem) and \
384 self.token.startswith("R")
385
386 @property
387 def is_composer(self):
388 return isinstance(self, ArtistSearchResultItem) and \
389 self.token.startswith("C")
390
391 @property
392 def is_genre_station(self):
393 return isinstance(self, GenreStationSearchResultItem)
394
395 def create_station(self): # pragma: no cover
396 raise NotImplementedError
397
398 @classmethod
399 def from_json(cls, api_client, data):
400 if data["musicToken"].startswith("S"):
401 return SongSearchResultItem.from_json(api_client, data)
402 elif data["musicToken"].startswith(("R", "C")):
403 return ArtistSearchResultItem.from_json(api_client, data)
404 elif data["musicToken"].startswith("G"):
405 return GenreStationSearchResultItem.from_json(api_client, data)
406 else:
407 raise NotImplementedError("Unknown result token type '{}'"
408 .format(data["musicToken"]))
409
410
411class ArtistSearchResultItem(SearchResultItem):
412
413 score = Field("score")
414 token = Field("musicToken")
415 artist = Field("artistName")
416 likely_match = Field("likelyMatch", default=False)
417
418 def create_station(self):
419 self._api_client.create_station(artist_token=self.token)
420
421 @classmethod
422 def from_json(cls, api_client, data):
423 return super(SearchResultItem, cls).from_json(api_client, data)
424
425
426class SongSearchResultItem(SearchResultItem):
427
428 score = Field("score")
429 token = Field("musicToken")
430 artist = Field("artistName")
431 song_name = Field("songName")
432
433 def create_station(self):
434 self._api_client.create_station(track_token=self.token)
435
436 @classmethod
437 def from_json(cls, api_client, data):
438 return super(SearchResultItem, cls).from_json(api_client, data)
439
440
441class GenreStationSearchResultItem(SearchResultItem):
442
443 score = Field("score")
444 token = Field("musicToken")
445 station_name = Field("stationName")
446
447 def create_station(self):
448 self._api_client.create_station(search_token=self.token)
449
450 @classmethod
451 def from_json(cls, api_client, data):
452 return super(SearchResultItem, cls).from_json(api_client, data)
453
454
455class SearchResult(PandoraModel):
456
457 nearest_matches_available = Field("nearMatchesAvailable")
458 explanation = Field("explanation")
459 songs = Field("songs", model=SongSearchResultItem)
460 artists = Field("artists", model=ArtistSearchResultItem)
461 genre_stations = Field("genreStations", model=GenreStationSearchResultItem)
462
463
464class GenreStationList(PandoraDictListModel):
465
466 checksum = Field("checksum")
467
468 __dict_list_key__ = "categories"
469 __dict_key__ = "categoryName"
470 __list_key__ = "stations"
471 __list_model__ = GenreStation
472
473 def has_changed(self):
474 checksum = self._api_client.get_station_list_checksum()
475 return checksum != self.checksum