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