/src/pyechonest/pyechonest/track.py

http://echo-nest-remix.googlecode.com/ · Python · 293 lines · 280 code · 2 blank · 11 comment · 2 complexity · 63ef67d3acc17eacd2c99f8b0ef5bcd9 MD5 · raw file

  1. import urllib2
  2. try:
  3. import json
  4. except ImportError:
  5. import simplejson as json
  6. import hashlib
  7. from proxies import TrackProxy
  8. import util
  9. class Track(TrackProxy):
  10. """
  11. Represents an audio analysis from The Echo Nest.
  12. All methods in this module return Track objects.
  13. Attributes:
  14. analysis_channels int: the number of audio channels used during analysis
  15. analysis_sample_rate float: the sample rate used during analysis
  16. analyzer_version str: e.g. '3.01a'
  17. artist str or None: artist name
  18. bars list of dicts: timing of each measure
  19. beats list of dicts: timing of each beat
  20. bitrate int: the bitrate of the input mp3 (or other file)
  21. danceability float: relative danceability (0 to 1)
  22. duration float: length of track in seconds
  23. energy float: relative energy (0 to 1)
  24. end_of_fade_in float: time in seconds track where fade-in ends
  25. id str: Echo Nest Track ID, e.g. 'TRTOBXJ1296BCDA33B'
  26. key int: between 0 (key of C) and 11 (key of B flat) inclusive
  27. key_confidence float: confidence that key detection was accurate
  28. loudness float: overall loudness in decibels (dB)
  29. md5 str: 32-character checksum of the input mp3
  30. meta dict: other track metainfo
  31. mode int: 0 (major) or 1 (minor)
  32. mode_confidence float: confidence that mode detection was accurate
  33. num_samples int: total samples in the decoded track
  34. release str or None: the album name
  35. sample_md5 str: 32-character checksum of the decoded audio file
  36. samplerate int: sample rate of input mp3
  37. sections list of dicts: larger sections of song (chorus, bridge, solo, etc.)
  38. segments list of dicts: timing, pitch, loudness and timbre for each segment
  39. start_of_fade_out float: time in seconds where fade out begins
  40. status str: analysis status, e.g. 'complete', 'pending', 'error'
  41. tatums list of dicts: the smallest metrical unit (subdivision of a beat)
  42. tempo float: overall BPM (beats per minute)
  43. tempo_confidence float: confidence that tempo detection was accurate
  44. title str or None: song title
  45. Each bar, beat, section, segment and tatum has a start time, a duration, and a confidence,
  46. in addition to whatever other data is given.
  47. Examples:
  48. >>> t = track.track_from_id('TRXXHTJ1294CD8F3B3')
  49. >>> t
  50. <track - Neverwas Restored (from Neverwas Soundtrack)>
  51. >>> t = track.track_from_md5('b8abf85746ab3416adabca63141d8c2d')
  52. >>> t
  53. <track - Neverwas Restored (from Neverwas Soundtrack)>
  54. >>>
  55. """
  56. def __repr__(self):
  57. try:
  58. return "<%s - %s>" % (self._object_type.encode('utf-8'), self.title.encode('utf-8'))
  59. except AttributeError:
  60. # the title is None
  61. return "< Track >"
  62. def __str__(self):
  63. return self.title.encode('utf-8')
  64. def _track_from_response(response):
  65. """
  66. This is the function that actually creates the track object
  67. """
  68. result = response['response']
  69. status = result['track']['status'].lower()
  70. if not status == 'complete':
  71. """
  72. pyechonest only supports wait = true for now, so this should not be pending
  73. """
  74. if status == 'error':
  75. raise Exception('there was an error analyzing the track')
  76. if status == 'pending':
  77. raise Exception('the track is still being analyzed')
  78. if status == 'forbidden':
  79. raise Exception('analysis of this track is forbidden')
  80. if status == 'unavailable':
  81. return track_from_reanalyzing_id(result['track']['id'])
  82. else:
  83. track = result['track']
  84. identifier = track.pop('id')
  85. md5 = track.pop('md5', None) # tracks from song api calls will not have an md5
  86. audio_summary = track.pop('audio_summary')
  87. energy = audio_summary.get('energy', 0)
  88. danceability = audio_summary.get('danceability', 0)
  89. json_url = audio_summary['analysis_url']
  90. json_string = urllib2.urlopen(json_url).read()
  91. analysis = json.loads(json_string)
  92. nested_track = analysis.pop('track')
  93. track.update(analysis)
  94. track.update(nested_track)
  95. track.update({'analysis_url': json_url, 'energy': energy, 'danceability': danceability})
  96. return Track(identifier, md5, track)
  97. def _upload(param_dict, data = None):
  98. """
  99. Calls upload either with a local audio file,
  100. or a url. Returns a track object.
  101. """
  102. param_dict['format'] = 'json'
  103. param_dict['wait'] = 'true'
  104. param_dict['bucket'] = 'audio_summary'
  105. result = util.callm('track/upload', param_dict, POST = True, socket_timeout = 300, data = data)
  106. return _track_from_response(result)
  107. def _profile(param_dict):
  108. param_dict['format'] = 'json'
  109. param_dict['bucket'] = 'audio_summary'
  110. result = util.callm('track/profile', param_dict)
  111. return _track_from_response(result)
  112. def _analyze(param_dict):
  113. param_dict['format'] = 'json'
  114. param_dict['bucket'] = 'audio_summary'
  115. param_dict['wait'] = 'true'
  116. result = util.callm('track/analyze', param_dict, POST = True, socket_timeout = 300)
  117. return _track_from_response(result)
  118. """ Below are convenience functions for creating Track objects, you should use them """
  119. def _track_from_string(audio_data, filetype):
  120. param_dict = {}
  121. param_dict['filetype'] = filetype
  122. return _upload(param_dict, data = audio_data)
  123. def track_from_file(file_object, filetype):
  124. """
  125. Create a track object from a file-like object.
  126. Args:
  127. file_object: a file-like Python object
  128. filetype: the file type (ex. mp3, ogg, wav)
  129. Example:
  130. >>> f = open("Miaow-01-Tempered-song.mp3")
  131. >>> t = track.track_from_file(f, 'mp3')
  132. >>> t
  133. < Track >
  134. >>>
  135. """
  136. try:
  137. hash = hashlib.md5(file_object.read()).hexdigest()
  138. return track_from_md5(hash)
  139. except util.EchoNestAPIError:
  140. file_object.seek(0)
  141. return _track_from_string(file_object.read(), filetype)
  142. def track_from_filename(filename, filetype = None):
  143. """
  144. Create a track object from a filename.
  145. Args:
  146. filename: A string containing the path to the input file.
  147. filetype: A string indicating the filetype; Defaults to None (type determined by file extension).
  148. Example:
  149. >>> t = track.track_from_filename("Miaow-01-Tempered-song.mp3")
  150. >>> t
  151. < Track >
  152. >>>
  153. """
  154. filetype = filetype or filename.split('.')[-1]
  155. try:
  156. hash = hashlib.md5(open(filename, 'rb').read()).hexdigest()
  157. return track_from_md5(hash)
  158. except util.EchoNestAPIError:
  159. return track_from_file(open(filename, 'rb'), filetype)
  160. def track_from_url(url):
  161. """
  162. Create a track object from a public http URL.
  163. Args:
  164. url: A string giving the URL to read from. This must be on a public machine accessible by HTTP.
  165. Example:
  166. >>> t = track.track_from_url("http://www.miaowmusic.com/mp3/Miaow-01-Tempered-song.mp3")
  167. >>> t
  168. < Track >
  169. >>>
  170. """
  171. param_dict = dict(url = url)
  172. return _upload(param_dict)
  173. def track_from_id(identifier):
  174. """
  175. Create a track object from an Echo Nest track ID.
  176. Args:
  177. identifier: A string containing the ID of a previously analyzed track.
  178. Example:
  179. >>> t = track.track_from_id("TRWFIDS128F92CC4CA")
  180. >>> t
  181. <track - Let The Spirit>
  182. >>>
  183. """
  184. param_dict = dict(id = identifier)
  185. return _profile(param_dict)
  186. def track_from_md5(md5):
  187. """
  188. Create a track object from an md5 hash.
  189. Args:
  190. md5: A string 32 characters long giving the md5 checksum of a track already analyzed.
  191. Example:
  192. >>> t = track.track_from_md5('b8abf85746ab3416adabca63141d8c2d')
  193. >>> t
  194. <track - Neverwas Restored (from Neverwas Soundtrack)>
  195. >>>
  196. """
  197. param_dict = dict(md5 = md5)
  198. return _profile(param_dict)
  199. def track_from_reanalyzing_id(identifier):
  200. """
  201. Create a track object from an Echo Nest track ID, reanalyzing the track first.
  202. Args:
  203. identifier (str): A string containing the ID of a previously analyzed track
  204. Example:
  205. >>> t = track.track_from_reanalyzing_id('TRXXHTJ1294CD8F3B3')
  206. >>> t
  207. <track - Neverwas Restored>
  208. >>>
  209. """
  210. param_dict = dict(id = identifier)
  211. return _analyze(param_dict)
  212. def track_from_reanalyzing_md5(md5):
  213. """
  214. Create a track object from an md5 hash, reanalyzing the track first.
  215. Args:
  216. md5 (str): A string containing the md5 of a previously analyzed track
  217. Example:
  218. >>> t = track.track_from_reanalyzing_md5('b8abf85746ab3416adabca63141d8c2d')
  219. >>> t
  220. <track - Neverwas Restored>
  221. >>>
  222. """
  223. param_dict = dict(md5 = md5)
  224. return _analyze(param_dict)