1
2
3
4 """
5 Copyright (c) 2010 The Echo Nest. All rights reserved.
6 Created by Tyler Williams on 2010-04-25.
7
8 The Song module loosely covers http://developer.echonest.com/docs/v4/song.html
9 Refer to the official api documentation if you are unsure about something.
10 """
11 import os
12 import util
13 from proxies import SongProxy
14
15 try:
16 import json
17 except ImportError:
18 import simplejson as json
19
20 -class Song(SongProxy):
21 """
22 A Song object
23
24 Attributes:
25 id (str): Echo Nest Song ID
26
27 title (str): Song Title
28
29 artist_name (str): Artist Name
30
31 artist_id (str): Artist ID
32
33 audio_summary (dict): An Audio Summary dict
34
35 song_hotttnesss (float): A float representing a song's hotttnesss
36
37 artist_hotttnesss (float): A float representing a song's parent artist's hotttnesss
38
39 artist_familiarity (float): A float representing a song's parent artist's familiarity
40
41 artist_location (dict): A dictionary of strings specifying a song's parent artist's location, lattitude and longitude
42
43 Create a song object like so:
44
45 >>> s = song.Song('SOPEXHZ12873FD2AC7')
46
47 """
48 - def __init__(self, id, buckets=None, **kwargs):
49 """
50 Song class
51
52 Args:
53 id (str): a song ID
54
55 Kwargs:
56 buckets (list): A list of strings specifying which buckets to retrieve
57
58 Returns:
59 A Song object
60
61 Example:
62
63 >>> s = song.Song('SOPEXHZ12873FD2AC7', buckets=['song_hotttnesss', 'artist_hotttnesss'])
64 >>> s.song_hotttnesss
65 0.58602500000000002
66 >>> s.artist_hotttnesss
67 0.80329715999999995
68 >>>
69
70 """
71 buckets = buckets or []
72 super(Song, self).__init__(id, buckets, **kwargs)
73
75 return "<%s - %s>" % (self._object_type.encode('utf-8'), self.title.encode('utf-8'))
76
78 return self.title.encode('utf-8')
79
80
82 """Get an audio summary of a song containing mode, tempo, key, duration, time signature, loudness, danceability, energy, and analysis_url.
83
84 Args:
85
86 Kwargs:
87 cache (bool): A boolean indicating whether or not the cached value should be used (if available). Defaults to True.
88
89 Returns:
90 A dictionary containing mode, tempo, key, duration, time signature, loudness, danceability, energy and analysis_url keys.
91
92 Example:
93 >>> s = song.Song('SOGNMKX12B0B806320')
94 >>> s.audio_summary
95 {u'analysis_url': u'https://echonest-analysis.s3.amazonaws.com:443/TR/TRCPUOG123E85891F2/3/full.json?Signature=wcML1ZKsl%2F2FU4k68euHJcF7Jbc%3D&Expires=1287518562&AWSAccessKeyId=AKIAIAFEHLM3KJ2XMHRA',
96 u'danceability': 0.20964757782725996,
97 u'duration': 472.63301999999999,
98 u'energy': 0.64265230549809549,
99 u'key': 0,
100 u'loudness': -9.6820000000000004,
101 u'mode': 1,
102 u'tempo': 126.99299999999999,
103 u'time_signature': 4}
104 >>>
105
106 """
107 if not (cache and ('audio_summary' in self.cache)):
108 response = self.get_attribute('profile', bucket='audio_summary')
109 if response['songs'] and 'audio_summary' in response['songs'][0]:
110 self.cache['audio_summary'] = response['songs'][0]['audio_summary']
111 else:
112 self.cache['audio_summary'] = {}
113 return self.cache['audio_summary']
114
115 audio_summary = property(get_audio_summary)
116
118 """Get our numerical description of how hottt a song currently is
119
120 Args:
121
122 Kwargs:
123 cache (bool): A boolean indicating whether or not the cached value should be used (if available). Defaults to True.
124
125 Returns:
126 A float representing hotttnesss.
127
128 Example:
129 >>> s = song.Song('SOLUHKP129F0698D49')
130 >>> s.get_song_hotttnesss()
131 0.57344379999999995
132 >>> s.song_hotttnesss
133 0.57344379999999995
134 >>>
135
136 """
137 if not (cache and ('song_hotttnesss' in self.cache)):
138 response = self.get_attribute('profile', bucket='song_hotttnesss')
139 self.cache['song_hotttnesss'] = response['songs'][0]['song_hotttnesss']
140 return self.cache['song_hotttnesss']
141
142 song_hotttnesss = property(get_song_hotttnesss)
143
145 """Get our numerical description of how hottt a song's artist currently is
146
147 Args:
148
149 Kwargs:
150 cache (bool): A boolean indicating whether or not the cached value should be used (if available). Defaults to True.
151
152 Returns:
153 A float representing hotttnesss.
154
155 Example:
156 >>> s = song.Song('SOOLGAZ127F3E1B87C')
157 >>> s.artist_hotttnesss
158 0.45645633000000002
159 >>> s.get_artist_hotttnesss()
160 0.45645633000000002
161 >>>
162
163 """
164 if not (cache and ('artist_hotttnesss' in self.cache)):
165 response = self.get_attribute('profile', bucket='artist_hotttnesss')
166 self.cache['artist_hotttnesss'] = response['songs'][0]['artist_hotttnesss']
167 return self.cache['artist_hotttnesss']
168
169 artist_hotttnesss = property(get_artist_hotttnesss)
170
172 """Get our numerical estimation of how familiar a song's artist currently is to the world
173
174 Args:
175 cache (bool): A boolean indicating whether or not the cached value should be used (if available). Defaults to True.
176
177 Returns:
178 A float representing familiarity.
179
180 Example:
181 >>> s = song.Song('SOQKVPH12A58A7AF4D')
182 >>> s.get_artist_familiarity()
183 0.639626025843539
184 >>> s.artist_familiarity
185 0.639626025843539
186 >>>
187 """
188 if not (cache and ('artist_familiarity' in self.cache)):
189 response = self.get_attribute('profile', bucket='artist_familiarity')
190 self.cache['artist_familiarity'] = response['songs'][0]['artist_familiarity']
191 return self.cache['artist_familiarity']
192
193 artist_familiarity = property(get_artist_familiarity)
194
196 """Get the location of a song's artist.
197
198 Args:
199 cache (bool): A boolean indicating whether or not the cached value should be used (if available). Defaults to True.
200
201 Returns:
202 An artist location object.
203
204 Example:
205 >>> s = song.Song('SOQKVPH12A58A7AF4D')
206 >>> s.artist_location
207 {u'latitude': 34.053489999999996, u'location': u'Los Angeles, CA', u'longitude': -118.24532000000001}
208 >>>
209
210 """
211 if not (cache and ('artist_location' in self.cache)):
212 response = self.get_attribute('profile', bucket='artist_location')
213 self.cache['artist_location'] = response['songs'][0]['artist_location']
214 return self.cache['artist_location']
215
216 artist_location = property(get_artist_location)
217
219 """Get the foreign id for this song for a specific id space
220
221 Args:
222
223 Kwargs:
224 idspace (str): A string indicating the idspace to fetch a foreign id for.
225
226 Returns:
227 A foreign ID string
228
229 Example:
230
231 >>> s = song.Song('SOYRVMR12AF729F8DC')
232 >>> s.get_foreign_id('CAGPXKK12BB06F9DE9')
233
234 >>>
235 """
236 if not (cache and ('foreign_ids' in self.cache) and filter(lambda d: d.get('catalog') == idspace, self.cache['foreign_ids'])):
237 response = self.get_attribute('profile', bucket=['id:'+idspace])
238 rsongs = response['songs']
239 if len(rsongs) == 0:
240 return None
241 foreign_ids = rsongs[0].get("foreign_ids", [])
242 self.cache['foreign_ids'] = self.cache.get('foreign_ids', []) + foreign_ids
243 cval = filter(lambda d: d.get('catalog') == idspace, self.cache.get('foreign_ids'))
244 return cval[0].get('foreign_id') if cval else None
245
247 """Get the tracks for a song given a catalog.
248
249 Args:
250 catalog (str): a string representing the catalog whose track you want to retrieve.
251
252 Returns:
253 A list of Track dicts.
254
255 Example:
256 >>> s = song.Song('SOWDASQ12A6310F24F')
257 >>> s.get_tracks('7digital')[0]
258 {u'catalog': u'7digital',
259 u'foreign_id': u'7digital:track:8445818',
260 u'id': u'TRJGNNY12903CC625C',
261 u'preview_url': u'http://previews.7digital.com/clips/34/8445818.clip.mp3',
262 u'release_image': u'http://cdn.7static.com/static/img/sleeveart/00/007/628/0000762838_200.jpg'}
263 >>>
264
265 """
266 if not (cache and ('tracks' in self.cache) and (catalog in [td['catalog'] for td in self.cache['tracks']])):
267 kwargs = {
268 'bucket':['tracks', 'id:%s' % catalog],
269 }
270
271 response = self.get_attribute('profile', **kwargs)
272 if not 'tracks' in self.cache:
273 self.cache['tracks'] = []
274
275 potential_tracks = response['songs'][0].get('tracks', [])
276 existing_track_ids = [tr['foreign_id'] for tr in self.cache['tracks']]
277 new_tds = filter(lambda tr: tr['foreign_id'] not in existing_track_ids, potential_tracks)
278 self.cache['tracks'].extend(new_tds)
279 return filter(lambda tr: tr['catalog']==catalog, self.cache['tracks'])
280
281
283 """Get the types of a song.
284
285 Args:
286 cache (boolean): A boolean indicating whether or not the cached value should be used (if available). Defaults to True.
287
288 Returns:
289 A list of strings, each representing a song type: 'christmas', for example.
290
291 Example:
292 >>> s = song.Song('SOQKVPH12A58A7AF4D')
293 >>> s.song_type
294 [u'christmas']
295 >>>
296
297 """
298 if not (cache and ('song_type' in self.cache)):
299 response = self.get_attribute('profile', bucket='song_type')
300 self.cache['song_type'] = response['songs'][0]['song_type']
301 return self.cache['song_type']
302
303 song_type = property(get_song_type)
304
305 -def identify(filename=None, query_obj=None, code=None, artist=None, title=None, release=None, duration=None, genre=None, buckets=None, version=None, codegen_start=0, codegen_duration=30):
306 """Identify a song.
307
308 Args:
309
310 Kwargs:
311 filename (str): The path of the file you want to analyze (requires codegen binary!)
312
313 query_obj (dict or list): A dict or list of dicts containing a 'code' element with an fp code
314
315 code (str): A fingerprinter code
316
317 artist (str): An artist name
318
319 title (str): A song title
320
321 release (str): A release name
322
323 duration (int): A song duration
324
325 genre (str): A string representing the genre
326
327 buckets (list): A list of strings specifying which buckets to retrieve
328
329 version (str): The version of the code generator used to generate the code
330
331 codegen_start (int): The point (in seconds) where the codegen should start
332
333 codegen_duration (int): The duration (in seconds) the codegen should analyze
334
335 Example:
336 >>> qo
337 {'code': 'eJxlldehHSEMRFsChAjlAIL-S_CZvfaXXxAglEaBTen300Qu__lAyoJYhVQdXTvXrmvXdTsKZOqoU1q63QNydBGfOd1cGX3scpb1jEiWRLaPcJureC6RVkXE69jL8pGHjpP48pLI1m7r9oiEyBXvoVv45Q-5IhylYLkIRxGO4rp18ZpEOmpFPopwfJjL0u3WceO3HB1DIvJRnkQeO1PCLIsIjBWEzYaShq4pV9Z0KzDiQ8SbSNuSyBZPOOxIJKR7dauEmXwotxDCqllEAVZlrX6F8Y-IJ0e169i_HQaqslaVtTq1W-1vKeupImzrxWWVI5cPlw-XDxckN-3kyeXDm3jKmqv6PtB1gfH1Eey5qu8qvAuMC4zLfPv1l3aqviylJhytFhF0mzqs6aYpYU04mlqgKWtNjppwNKWubR2FowlHUws0gWmPi668dSHq6rOuPuhqgRcVKKM8s-fZS937nBe23iz3Uctx9607z-kLph1i8YZ8f_TfzLXseBh7nXy9nn1YBAg4Nwjp4AzTL23M_U3Rh0-sdDFtyspNOb1bYeZZqz2Y6TaHmXeuNmfFdTueLuvdsbOU9luvtIkl4vI5F_92PVprM1-sdJ_o9_Guc0b_WimpD_Rt1DFg0sY3wyw08e6jlqhjH3o76naYvzWqhX9rOv15Y7Ww_MIF8dXzw30s_uHO5PPDfUonnzq_NJ8J93mngAkIz5jA29SqxGwwvxQsih-sozX0zVk__RFaf_qyG9hb8dktZZXd4a8-1ljB-c5bllXOe1HqHplzeiN4E7q9ZRdmJuI73gBEJ_HcAxUm74PAVDNL47D6OAfzTHI0mHpXAmY60QNmlqjDfIPzwUDYhVnoXqtvZGrBdMi3ClQUQ8D8rX_1JE_In94CBXER4lrrw0H867ei8x-OVz8c-Osh5plzTOySpKIROmFkbn5xVuK784vTyPpS3OlcSjHpL16saZnm4Bk66hte9sd80Dcj02f7xDVrExjk32cssKXjmflU_SxXmn4Y9Ttued10YM552h5Wtt_WeVR4U6LPWfbIdW31J4JOXnpn4qhH7yE_pdBH9E_sMwbNFr0z0IW5NA8aOZhLmOh3zSVNRZwxiZc5pb8fikGzIf-ampJnCSb3r-ZPfjPuvLm7CY_Vfa_k7SCzdwHNg5mICTSHDxyBWmaOSyLQpPmCSXyF-eL7MHo7zNd668JMb_N-AJJRuMwrX0jNx7a8-Rj5oN6nyWoL-jRv4pu7Ue821TzU3MhvpD9Fo-XI',
338 'code_count': 151,
339 'low_rank': 0,
340 'metadata': {'artist': 'Harmonic 313',
341 'bitrate': 198,
342 'codegen_time': 0.57198400000000005,
343 'decode_time': 0.37954599999999999,
344 'duration': 226,
345 'filename': 'koln.mp3',
346 'genre': 'Electronic',
347 'given_duration': 30,
348 'release': 'When Machines Exceed Human Intelligence',
349 'sample_rate': 44100,
350 'samples_decoded': 661816,
351 'start_offset': 0,
352 'title': 'kln',
353 'version': 3.1499999999999999},
354 'tag': 0}
355 >>> song.identify(query_obj=qo)
356 [<song - Köln>]
357 >>>
358
359
360 """
361 post, has_data, data = False, False, False
362
363 if filename:
364 if os.path.exists(filename):
365 query_obj = util.codegen(filename, start=codegen_start, duration=codegen_duration)
366 if query_obj is None:
367 raise Exception("The filename specified: %s could not be decoded." % filename)
368 else:
369 raise Exception("The filename specified: %s does not exist." % filename)
370 if query_obj and not isinstance(query_obj, list):
371 query_obj = [query_obj]
372
373 if filename:
374
375 for q in query_obj:
376 if 'error' in q:
377 raise Exception(q['error'] + ": " + q.get('metadata', {}).get('filename', ''))
378
379 if not (filename or query_obj or code):
380 raise Exception("Not enough information to identify song.")
381
382 kwargs = {}
383 if code:
384 has_data = True
385 kwargs['code'] = code
386 if title:
387 kwargs['title'] = title
388 if release:
389 kwargs['release'] = release
390 if duration:
391 kwargs['duration'] = duration
392 if genre:
393 kwargs['genre'] = genre
394 if buckets:
395 kwargs['bucket'] = buckets
396 if version:
397 kwargs['version'] = version
398
399 if query_obj and any(query_obj):
400 has_data = True
401 data = {'query':json.dumps(query_obj)}
402 post = True
403
404 if has_data:
405 result = util.callm("%s/%s" % ('song', 'identify'), kwargs, POST=post, data=data)
406 return [Song(**util.fix(s_dict)) for s_dict in result['response'].get('songs',[])]
407
408
409 -def search(title=None, artist=None, artist_id=None, combined=None, description=None, style=None, mood=None, \
410 results=None, start=None, max_tempo=None, min_tempo=None, \
411 max_duration=None, min_duration=None, max_loudness=None, min_loudness=None, \
412 artist_max_familiarity=None, artist_min_familiarity=None, artist_max_hotttnesss=None, \
413 artist_min_hotttnesss=None, song_max_hotttnesss=None, song_min_hotttnesss=None, mode=None, \
414 min_energy=None, max_energy=None, min_danceability=None, max_danceability=None, \
415 key=None, max_latitude=None, min_latitude=None, max_longitude=None, min_longitude=None, \
416 sort=None, buckets = None, limit=False, test_new_things=None, rank_type=None,
417 artist_start_year_after=None, artist_start_year_before=None, artist_end_year_after=None, artist_end_year_before=None,song_type=None):
418 """Search for songs by name, description, or constraint.
419
420 Args:
421
422 Kwargs:
423 title (str): the name of a song
424
425 artist (str): the name of an artist
426
427 artist_id (str): the artist_id
428
429 combined (str): the artist name and song title
430
431 description (str): A string describing the artist and song
432
433 style (str): A string describing the style/genre of the artist and song
434
435 mood (str): A string describing the mood of the artist and song
436
437 results (int): An integer number of results to return
438
439 max_tempo (float): The max tempo of song results
440
441 min_tempo (float): The min tempo of song results
442
443 max_duration (float): The max duration of song results
444
445 min_duration (float): The min duration of song results
446
447 max_loudness (float): The max loudness of song results
448
449 min_loudness (float): The min loudness of song results
450
451 artist_max_familiarity (float): A float specifying the max familiarity of artists to search for
452
453 artist_min_familiarity (float): A float specifying the min familiarity of artists to search for
454
455 artist_max_hotttnesss (float): A float specifying the max hotttnesss of artists to search for
456
457 artist_min_hotttnesss (float): A float specifying the max hotttnesss of artists to search for
458
459 song_max_hotttnesss (float): A float specifying the max hotttnesss of songs to search for
460
461 song_min_hotttnesss (float): A float specifying the max hotttnesss of songs to search for
462
463 max_energy (float): The max energy of song results
464
465 min_energy (float): The min energy of song results
466
467 max_dancibility (float): The max dancibility of song results
468
469 min_dancibility (float): The min dancibility of song results
470
471 mode (int): 0 or 1 (minor or major)
472
473 key (int): 0-11 (c, c-sharp, d, e-flat, e, f, f-sharp, g, a-flat, a, b-flat, b)
474
475 max_latitude (float): A float specifying the max latitude of artists to search for
476
477 min_latitude (float): A float specifying the min latitude of artists to search for
478
479 max_longitude (float): A float specifying the max longitude of artists to search for
480
481 min_longitude (float): A float specifying the min longitude of artists to search for
482
483 sort (str): A string indicating an attribute and order for sorting the results
484
485 buckets (list): A list of strings specifying which buckets to retrieve
486
487 limit (bool): A boolean indicating whether or not to limit the results to one of the id spaces specified in buckets
488
489 rank_type (str): A string denoting the desired ranking for description searches, either 'relevance' or 'familiarity
490
491 artist_start_year_before (int): Returned songs's artists will have started recording music before this year.
492
493 artist_start_year_after (int): Returned songs's artists will have started recording music after this year.
494
495 artist_end_year_before (int): Returned songs's artists will have stopped recording music before this year.
496
497 artist_end_year_after (int): Returned songs's artists will have stopped recording music after this year.
498
499 song_type (string): A string or list of strings specifiying the type of song to search for.
500
501 Returns:
502 A list of Song objects
503
504 Example:
505
506 >>> results = song.search(artist='shakira', title='she wolf', buckets=['id:7digital', 'tracks'], limit=True, results=1)
507 >>> results
508 [<song - She Wolf>]
509 >>> results[0].get_tracks('7digital')[0]
510 {u'catalog': u'7digital',
511 u'foreign_id': u'7digital:track:7854109',
512 u'id': u'TRTOBSE12903CACEC4',
513 u'preview_url': u'http://previews.7digital.com/clips/34/7854109.clip.mp3',
514 u'release_image': u'http://cdn.7static.com/static/img/sleeveart/00/007/081/0000708184_200.jpg'}
515 >>>
516 """
517
518 limit = str(limit).lower()
519 kwargs = locals()
520 kwargs['bucket'] = buckets
521 del kwargs['buckets']
522
523 result = util.callm("%s/%s" % ('song', 'search'), kwargs)
524 return [Song(**util.fix(s_dict)) for s_dict in result['response']['songs']]
525
526 -def profile(ids=None, track_ids=None, buckets=None, limit=False):
527 """get the profiles for multiple songs at once
528
529 Args:
530 ids (str or list): a song ID or list of song IDs
531
532 Kwargs:
533 buckets (list): A list of strings specifying which buckets to retrieve
534
535 limit (bool): A boolean indicating whether or not to limit the results to one of the id spaces specified in buckets
536
537 Returns:
538 A list of term document dicts
539
540 Example:
541
542 >>> song_ids = [u'SOGNMKX12B0B806320', u'SOLUHKP129F0698D49', u'SOOLGAZ127F3E1B87C', u'SOQKVPH12A58A7AF4D', u'SOHKEEM1288D3ED9F5']
543 >>> songs = song.profile(song_ids, buckets=['audio_summary'])
544 [<song - chickfactor>,
545 <song - One Step Closer>,
546 <song - And I Am Telling You I'm Not Going (Glee Cast Version)>,
547 <song - In This Temple As In The Hearts Of Man For Whom He Saved The Earth>,
548 <song - Octet>]
549 >>> songs[0].audio_summary
550 {u'analysis_url': u'https://echonest-analysis.s3.amazonaws.com:443/TR/TRKHTDL123E858AC4B/3/full.json?Signature=sE6OwAzg6UvrtiX6nJJW1t7E6YI%3D&Expires=1287585351&AWSAccessKeyId=AKIAIAFEHLM3KJ2XMHRA',
551 u'danceability': None,
552 u'duration': 211.90485000000001,
553 u'energy': None,
554 u'key': 7,
555 u'loudness': -16.736999999999998,
556 u'mode': 1,
557 u'tempo': 94.957999999999998,
558 u'time_signature': 4}
559 >>>
560
561 """
562 kwargs = {}
563
564 if ids:
565 if not isinstance(ids, list):
566 ids = [ids]
567 kwargs['id'] = ids
568
569 if track_ids:
570 if not isinstance(track_ids, list):
571 track_ids = [track_ids]
572 kwargs['track_id'] = track_ids
573
574 buckets = buckets or []
575 if buckets:
576 kwargs['bucket'] = buckets
577
578 if limit:
579 kwargs['limit'] = 'true'
580
581 result = util.callm("%s/%s" % ('song', 'profile'), kwargs)
582 return [Song(**util.fix(s_dict)) for s_dict in result['response']['songs']]
583