Package pyechonest :: Module track
[hide private]
[frames] | no frames]

Source Code for Module pyechonest.track

  1  import urllib2 
  2  try: 
  3      import json 
  4  except ImportError: 
  5      import simplejson as json 
  6   
  7  import hashlib 
  8  from proxies import TrackProxy 
  9  import util 
 10  import time 
 11   
 12  # Seconds to wait for asynchronous track/upload or track/analyze jobs to complete. 
 13  # Previously the socket timeouts on the synchronous calls were 300 secs. 
 14  DEFAULT_ASYNC_TIMEOUT = 300 
 15   
16 -class Track(TrackProxy):
17 """ 18 Represents an audio analysis from The Echo Nest. 19 20 All methods in this module return Track objects. 21 22 Attributes: 23 24 analysis_channels int: the number of audio channels used during analysis 25 26 analysis_sample_rate float: the sample rate used during analysis 27 28 analyzer_version str: e.g. '3.01a' 29 30 artist str or None: artist name 31 32 bars list of dicts: timing of each measure 33 34 beats list of dicts: timing of each beat 35 36 bitrate int: the bitrate of the input mp3 (or other file) 37 38 danceability float: relative danceability (0 to 1) 39 40 duration float: length of track in seconds 41 42 energy float: relative energy (0 to 1) 43 44 end_of_fade_in float: time in seconds track where fade-in ends 45 46 id str: Echo Nest Track ID, e.g. 'TRTOBXJ1296BCDA33B' 47 48 key int: between 0 (key of C) and 11 (key of B flat) inclusive 49 50 key_confidence float: confidence that key detection was accurate 51 52 loudness float: overall loudness in decibels (dB) 53 54 md5 str: 32-character checksum of the input mp3 55 56 meta dict: other track metainfo 57 58 mode int: 0 (major) or 1 (minor) 59 60 mode_confidence float: confidence that mode detection was accurate 61 62 num_samples int: total samples in the decoded track 63 64 release str or None: the album name 65 66 sample_md5 str: 32-character checksum of the decoded audio file 67 68 samplerate int: sample rate of input mp3 69 70 sections list of dicts: larger sections of song (chorus, bridge, solo, etc.) 71 72 segments list of dicts: timing, pitch, loudness and timbre for each segment 73 74 speechiness float: relative speechiness (0 to 1) 75 76 start_of_fade_out float: time in seconds where fade out begins 77 78 status str: analysis status, e.g. 'complete', 'pending', 'error' 79 80 tatums list of dicts: the smallest metrical unit (subdivision of a beat) 81 82 tempo float: overall BPM (beats per minute) 83 84 tempo_confidence float: confidence that tempo detection was accurate 85 86 title str or None: song title 87 88 Each bar, beat, section, segment and tatum has a start time, a duration, and a confidence, 89 in addition to whatever other data is given. 90 91 Examples: 92 93 >>> t = track.track_from_id('TRXXHTJ1294CD8F3B3') 94 >>> t 95 <track - Neverwas Restored (from Neverwas Soundtrack)> 96 >>> t = track.track_from_md5('b8abf85746ab3416adabca63141d8c2d') 97 >>> t 98 <track - Neverwas Restored (from Neverwas Soundtrack)> 99 >>> 100 """ 101
102 - def __repr__(self):
103 try: 104 return "<%s - %s>" % (self._object_type.encode('utf-8'), self.title.encode('utf-8')) 105 except AttributeError: 106 # the title is None 107 return "< Track >"
108
109 - def __str__(self):
110 return self.title.encode('utf-8')
111
112 -def _wait_for_pending_track(trid, timeout):
113 # timeout is ignored for now 114 status = 'pending' 115 while status == 'pending': 116 time.sleep(1) 117 param_dict = {'id': trid} # dict(id = identifier) 118 param_dict['format'] = 'json' 119 param_dict['bucket'] = 'audio_summary' 120 result = util.callm('track/profile', param_dict) 121 status = result['response']['track']['status'].lower() 122 # TODO: timeout if necessary 123 return result
124
125 -def _track_from_response(result, timeout=DEFAULT_ASYNC_TIMEOUT):
126 """ 127 This is the function that actually creates the track object 128 """ 129 response = result['response'] 130 status = response['track']['status'].lower() 131 132 if status == 'pending': 133 # Need to wait for async upload or analyze call to finish. 134 result = _wait_for_pending_track(response['track']['id'], timeout) 135 response = result['response'] 136 status = response['track']['status'].lower() 137 138 if not status == 'complete': 139 if status == 'pending': 140 # Operation didn't complete by timeout above. 141 raise Exception('the operation didn\'t complete before the timeout (%d secs)' % timeout) 142 elif status == 'error': 143 raise Exception('there was an error analyzing the track') 144 elif status == 'forbidden': 145 raise Exception('analysis of this track is forbidden') 146 if status == 'unavailable': 147 return track_from_reanalyzing_id(result['track']['id']) 148 else: 149 track = response['track'] 150 identifier = track.pop('id') 151 md5 = track.pop('md5', None) # tracks from song api calls will not have an md5 152 audio_summary = track.pop('audio_summary') 153 energy = audio_summary.get('energy', 0) 154 danceability = audio_summary.get('danceability', 0) 155 speechiness = audio_summary.get('speechiness', 0) 156 json_url = audio_summary.get('analysis_url') 157 if json_url: 158 try: 159 json_string = urllib2.urlopen(json_url).read() 160 analysis = json.loads(json_string) 161 except: #pylint: disable=W0702 162 analysis = {} 163 else: 164 analysis = {} 165 nested_track = analysis.pop('track', {}) 166 track.update(analysis) 167 track.update(nested_track) 168 track.update({'analysis_url': json_url, 'energy': energy, 169 'danceability': danceability, 170 'speechiness': speechiness}) 171 return Track(identifier, md5, track)
172
173 -def _upload(param_dict, timeout, data = None):
174 """ 175 Calls upload either with a local audio file, 176 or a url. Returns a track object. 177 """ 178 param_dict['format'] = 'json' 179 param_dict['wait'] = 'true' 180 param_dict['bucket'] = 'audio_summary' 181 result = util.callm('track/upload', param_dict, POST = True, socket_timeout = 300, data = data) 182 return _track_from_response(result, timeout)
183
184 -def _profile(param_dict):
185 param_dict['format'] = 'json' 186 param_dict['bucket'] = 'audio_summary' 187 result = util.callm('track/profile', param_dict) 188 return _track_from_response(result)
189
190 -def _analyze(param_dict, timeout):
191 param_dict['format'] = 'json' 192 param_dict['bucket'] = 'audio_summary' 193 param_dict['wait'] = 'true' 194 result = util.callm('track/analyze', param_dict, POST = True, socket_timeout = 300) 195 return _track_from_response(result, timeout)
196 197 198 """ Below are convenience functions for creating Track objects, you should use them """ 199
200 -def _track_from_data(audio_data, filetype, timeout):
201 param_dict = {} 202 param_dict['filetype'] = filetype 203 return _upload(param_dict, timeout, data = audio_data)
204
205 -def track_from_file(file_object, filetype, timeout=DEFAULT_ASYNC_TIMEOUT):
206 """ 207 Create a track object from a file-like object. 208 209 Args: 210 file_object: a file-like Python object 211 filetype: the file type (ex. mp3, ogg, wav) 212 213 Example: 214 >>> f = open("Miaow-01-Tempered-song.mp3") 215 >>> t = track.track_from_file(f, 'mp3') 216 >>> t 217 < Track > 218 >>> 219 """ 220 try: 221 hash = hashlib.md5(file_object.read()).hexdigest() 222 return track_from_md5(hash) 223 except util.EchoNestAPIError: 224 file_object.seek(0) 225 return _track_from_data(file_object.read(), filetype, timeout)
226
227 -def track_from_filename(filename, filetype = None, timeout=DEFAULT_ASYNC_TIMEOUT):
228 """ 229 Create a track object from a filename. 230 231 Args: 232 filename: A string containing the path to the input file. 233 filetype: A string indicating the filetype; Defaults to None (type determined by file extension). 234 235 Example: 236 >>> t = track.track_from_filename("Miaow-01-Tempered-song.mp3") 237 >>> t 238 < Track > 239 >>> 240 """ 241 filetype = filetype or filename.split('.')[-1] 242 try: 243 md5 = hashlib.md5(open(filename, 'rb').read()).hexdigest() 244 return track_from_md5(md5) 245 except util.EchoNestAPIError: 246 return _track_from_data(open(filename, 'rb').read(), filetype, timeout)
247
248 -def track_from_url(url, timeout=DEFAULT_ASYNC_TIMEOUT):
249 """ 250 Create a track object from a public http URL. 251 252 Args: 253 url: A string giving the URL to read from. This must be on a public machine accessible by HTTP. 254 255 Example: 256 >>> t = track.track_from_url("http://www.miaowmusic.com/mp3/Miaow-01-Tempered-song.mp3") 257 >>> t 258 < Track > 259 >>> 260 261 """ 262 param_dict = dict(url = url) 263 return _upload(param_dict, timeout)
264
265 -def track_from_id(identifier):
266 """ 267 Create a track object from an Echo Nest track ID. 268 269 Args: 270 identifier: A string containing the ID of a previously analyzed track. 271 272 Example: 273 >>> t = track.track_from_id("TRWFIDS128F92CC4CA") 274 >>> t 275 <track - Let The Spirit> 276 >>> 277 """ 278 param_dict = dict(id = identifier) 279 return _profile(param_dict)
280
281 -def track_from_md5(md5):
282 """ 283 Create a track object from an md5 hash. 284 285 Args: 286 md5: A string 32 characters long giving the md5 checksum of a track already analyzed. 287 288 Example: 289 >>> t = track.track_from_md5('b8abf85746ab3416adabca63141d8c2d') 290 >>> t 291 <track - Neverwas Restored (from Neverwas Soundtrack)> 292 >>> 293 """ 294 param_dict = dict(md5 = md5) 295 return _profile(param_dict)
296
297 -def track_from_reanalyzing_id(identifier, timeout=DEFAULT_ASYNC_TIMEOUT):
298 """ 299 Create a track object from an Echo Nest track ID, reanalyzing the track first. 300 301 Args: 302 identifier (str): A string containing the ID of a previously analyzed track 303 304 Example: 305 >>> t = track.track_from_reanalyzing_id('TRXXHTJ1294CD8F3B3') 306 >>> t 307 <track - Neverwas Restored> 308 >>> 309 """ 310 param_dict = dict(id = identifier) 311 return _analyze(param_dict, timeout)
312
313 -def track_from_reanalyzing_md5(md5, timeout=DEFAULT_ASYNC_TIMEOUT):
314 """ 315 Create a track object from an md5 hash, reanalyzing the track first. 316 317 Args: 318 md5 (str): A string containing the md5 of a previously analyzed track 319 320 Example: 321 >>> t = track.track_from_reanalyzing_md5('b8abf85746ab3416adabca63141d8c2d') 322 >>> t 323 <track - Neverwas Restored> 324 >>> 325 """ 326 param_dict = dict(md5 = md5) 327 return _analyze(param_dict, timeout)
328