PageRenderTime 355ms CodeModel.GetById 191ms app.highlight 15ms RepoModel.GetById 146ms app.codeStats 0ms

/src/pyechonest/pyechonest/track.py

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