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
13
14 DEFAULT_ASYNC_TIMEOUT = 300
15
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
103 try:
104 return "<%s - %s>" % (self._object_type.encode('utf-8'), self.title.encode('utf-8'))
105 except AttributeError:
106
107 return "< Track >"
108
110 return self.title.encode('utf-8')
111
113
114 status = 'pending'
115 while status == 'pending':
116 time.sleep(1)
117 param_dict = {'id': trid}
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
123 return result
124
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
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
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)
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:
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
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
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
201 param_dict = {}
202 param_dict['filetype'] = filetype
203 return _upload(param_dict, timeout, data = audio_data)
204
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
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
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
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
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
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
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