aboutsummaryrefslogtreecommitdiff
path: root/pandora/models/playlist.py
diff options
context:
space:
mode:
Diffstat (limited to 'pandora/models/playlist.py')
-rw-r--r--pandora/models/playlist.py197
1 files changed, 197 insertions, 0 deletions
diff --git a/pandora/models/playlist.py b/pandora/models/playlist.py
new file mode 100644
index 0000000..08510f1
--- /dev/null
+++ b/pandora/models/playlist.py
@@ -0,0 +1,197 @@
1from enum import Enum
2
3from ..client import BaseAPIClient
4from ._base import Field, SyntheticField, PandoraModel, PandoraListModel
5
6
7class AdditionalAudioUrl(Enum):
8 HTTP_40_AAC_MONO = 'HTTP_40_AAC_MONO'
9 HTTP_64_AAC = 'HTTP_64_AAC'
10 HTTP_32_AACPLUS = 'HTTP_32_AACPLUS'
11 HTTP_64_AACPLUS = 'HTTP_64_AACPLUS'
12 HTTP_24_AACPLUS_ADTS = 'HTTP_24_AACPLUS_ADTS'
13 HTTP_32_AACPLUS_ADTS = 'HTTP_32_AACPLUS_ADTS'
14 HTTP_64_AACPLUS_ADTS = 'HTTP_64_AACPLUS_ADTS'
15 HTTP_128_MP3 = 'HTTP_128_MP3'
16 HTTP_32_WMA = 'HTTP_32_WMA'
17
18
19class PandoraType(Enum):
20
21 TRACK = "TR"
22 ARTIST = "AR"
23 GENRE = "GR"
24
25 @staticmethod
26 def from_model(client, value):
27 return PandoraType.from_string(value)
28
29 @staticmethod
30 def from_string(value):
31 return {
32 "TR": PandoraType.TRACK,
33 "AR": PandoraType.ARTIST,
34 }.get(value, PandoraType.GENRE)
35
36
37class AudioField(SyntheticField):
38
39 def formatter(self, api_client, data, value):
40 """Get audio-related fields
41
42 Try to find fields for the audio url for specified preferred quality
43 level, or next-lowest available quality url otherwise.
44 """
45 url_map = data.get("audioUrlMap")
46 audio_url = data.get("audioUrl")
47
48 # Only an audio URL, not a quality map. This happens for most of the
49 # mobile client tokens and some of the others now. In this case
50 # substitute the empirically determined default values in the format
51 # used by the rest of the function so downstream consumers continue to
52 # work.
53 if audio_url and not url_map:
54 url_map = {
55 BaseAPIClient.HIGH_AUDIO_QUALITY: {
56 "audioUrl": audio_url,
57 "bitrate": 64,
58 "encoding": "aacplus",
59 }
60 }
61 elif not url_map: # No audio url available (e.g. ad tokens)
62 return None
63
64 valid_audio_formats = [BaseAPIClient.HIGH_AUDIO_QUALITY,
65 BaseAPIClient.MED_AUDIO_QUALITY,
66 BaseAPIClient.LOW_AUDIO_QUALITY]
67
68 # Only iterate over sublist, starting at preferred audio quality, or
69 # from the beginning of the list if nothing is found. Ensures that the
70 # bitrate used will always be the same or lower quality than was
71 # specified to prevent audio from skipping for slow connections.
72 preferred_quality = api_client.default_audio_quality
73 if preferred_quality in valid_audio_formats:
74 i = valid_audio_formats.index(preferred_quality)
75 valid_audio_formats = valid_audio_formats[i:]
76
77 for quality in valid_audio_formats:
78 audio_url = url_map.get(quality)
79
80 if audio_url:
81 return audio_url[self.field]
82
83 return audio_url[self.field] if audio_url else None
84
85
86class AdditionalUrlField(SyntheticField):
87
88 def formatter(self, api_client, data, value):
89 """Parse additional url fields and map them to inputs
90
91 Attempt to create a dictionary with keys being user input, and
92 response being the returned URL
93 """
94 if value is None:
95 return None
96
97 user_param = data['_paramAdditionalUrls']
98 urls = {}
99 if isinstance(value, str):
100 urls[user_param[0]] = value
101 else:
102 for key, url in zip(user_param, value):
103 urls[key] = url
104 return urls
105
106
107class PlaylistModel(PandoraModel):
108
109 def get_is_playable(self):
110 if not self.audio_url:
111 return False
112 return self._api_client.transport.test_url(self.audio_url)
113
114 def prepare_playback(self):
115 """Prepare Track for Playback
116
117 This method must be called by clients before beginning playback
118 otherwise the track recieved may not be playable.
119 """
120 return self
121
122 def thumbs_up(self): # pragma: no cover
123 raise NotImplementedError
124
125 def thumbs_down(self): # pragma: no cover
126 raise NotImplementedError
127
128 def bookmark_song(self): # pragma: no cover
129 raise NotImplementedError
130
131 def bookmark_artist(self): # pragma: no cover
132 raise NotImplementedError
133
134 def sleep(self): # pragma: no cover
135 raise NotImplementedError
136
137
138class PlaylistItem(PlaylistModel):
139
140 artist_name = Field("artistName")
141 album_name = Field("albumName")
142 song_name = Field("songName")
143 song_rating = Field("songRating")
144 track_gain = Field("trackGain")
145 track_length = Field("trackLength")
146 track_token = Field("trackToken")
147 audio_url = AudioField("audioUrl")
148 bitrate = AudioField("bitrate")
149 encoding = AudioField("encoding")
150 album_art_url = Field("albumArtUrl")
151 allow_feedback = Field("allowFeedback")
152 station_id = Field("stationId")
153
154 ad_token = Field("adToken")
155
156 album_detail_url = Field("albumDetailUrl")
157 album_explore_url = Field("albumExplorerUrl")
158
159 amazon_album_asin = Field("amazonAlbumAsin")
160 amazon_album_digital_asin = Field("amazonAlbumDigitalAsin")
161 amazon_album_url = Field("amazonAlbumUrl")
162 amazon_song_digital_asin = Field("amazonSongDigitalAsin")
163
164 artist_detail_url = Field("artistDetailUrl")
165 artist_explore_url = Field("artistExplorerUrl")
166
167 itunes_song_url = Field("itunesSongUrl")
168
169 song_detail_url = Field("songDetailUrl")
170 song_explore_url = Field("songExplorerUrl")
171
172 additional_audio_urls = AdditionalUrlField("additionalAudioUrl")
173
174 @property
175 def is_ad(self):
176 return self.ad_token is not None
177
178 def thumbs_up(self): # pragma: no cover
179 return self._api_client.add_feedback(self.track_token, True)
180
181 def thumbs_down(self): # pragma: no cover
182 return self._api_client.add_feedback(self.track_token, False)
183
184 def bookmark_song(self): # pragma: no cover
185 return self._api_client.add_song_bookmark(self.track_token)
186
187 def bookmark_artist(self): # pragma: no cover
188 return self._api_client.add_artist_bookmark(self.track_token)
189
190 def sleep(self): # pragma: no cover
191 return self._api_client.sleep_song(self.track_token)
192
193
194class Playlist(PandoraListModel):
195
196 __list_key__ = "items"
197 __list_model__ = PlaylistItem