1 # -*- coding: utf-8 -*-
3 # Copyright (c) 2014 Jack Kaliko <kaliko@azylum.org>
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 Consume EchoNest web service
25 __author__ = 'Jack Kaliko'
28 from datetime import datetime, timedelta
30 from requests import Session, Request, Timeout, ConnectionError
33 from sima.lib.meta import Artist
34 from sima.lib.track import Track
35 from sima.lib.httpcli.controller import CacheController
36 from sima.lib.httpcli.cache import FileCache
37 from sima.utils.utils import WSError, WSNotFound, WSTimeout, WSHTTPError
38 from sima.utils.utils import getws, Throttle
39 if len(ECH.get('apikey')) == 23: # simple hack allowing imp.reload
43 WAIT_BETWEEN_REQUESTS = timedelta(0, 1)
48 """EchoNest http client
50 root_url = 'http://{host}/api/{version}'.format(**ECH)
51 timestamp = datetime.utcnow()
54 cache = FileCache('/home/kaliko/.local/share/mpd_sima/http')
57 self._ressource = None
58 self.current_element = None
59 self.controller = CacheController(self.cache)
61 def _fetch(self, payload):
62 """Use cached elements or proceed http request"""
63 req = Request('GET', self._ressource, params=payload,
66 cached_response = self.controller.cached_request(req.url, req.headers)
68 return cached_response.json()
71 return self._fetch_ws(req)
73 raise WSTimeout('Failed to reach server within {0}s'.format(
75 except ConnectionError as err:
78 @Throttle(WAIT_BETWEEN_REQUESTS)
79 def _fetch_ws(self, prepreq):
80 """fetch from web service"""
82 resp = sess.send(prepreq, timeout=SOCKET_TIMEOUT)
83 self.__class__.ratelimit = resp.headers.get('x-ratelimit-remaining', None)
84 if resp.status_code is not 200:
85 raise WSHTTPError('{0.status_code}: {0.reason}'.format(resp))
87 self._controls_answer(ans)
89 self.controller.cache_response(resp.request, resp)
92 def _controls_answer(self, ans):
95 status = ans.get('response').get('status')
96 code = status.get('code')
100 raise WSNotFound('Artist not found')
101 raise WSError(status.get('message'))
103 def _forge_payload(self, artist, top=False):
106 payload = {'api_key': ECH.get('apikey')}
107 if not isinstance(artist, Artist):
108 raise TypeError('"{0!r}" not an Artist object'.format(artist))
111 id='musicbrainz:artist:{0}'.format(artist.mbid))
113 payload.update(name=artist.name)
114 payload.update(bucket='id:musicbrainz')
115 payload.update(results=100)
118 aid = payload.pop('id')
119 payload.update(artist_id=aid)
121 name = payload.pop('name')
122 payload.update(artist=name)
123 payload.update(results=100)
124 payload.update(sort='song_hotttnesss-desc')
127 def get_similar(self, artist=None):
128 """Fetch similar artists
130 payload = self._forge_payload(artist)
132 self._ressource = '{0}/artist/similar'.format(SimaEch.root_url)
133 ans = self._fetch(payload)
134 for art in ans.get('response').get('artists'):
137 if 'foreign_ids' in art:
138 for frgnid in art.get('foreign_ids'):
139 if frgnid.get('catalog') == 'musicbrainz':
140 mbid = frgnid.get('foreign_id'
141 ).lstrip('musicbrainz:artist:')
142 yield Artist(mbid=mbid, name=art.get('name'))
144 def get_toptrack(self, artist=None):
145 """Fetch artist top tracks
147 payload = self._forge_payload(artist, top=True)
149 self._ressource = '{0}/song/search'.format(SimaEch.root_url)
150 ans = self._fetch(payload)
153 'artist': artist.name,
154 'musicbrainz_artistid': artist.mbid,
156 for song in ans.get('response').get('songs'):
157 title = song.get('title')
158 if title not in titles:
160 yield Track(title=title, **artist)
164 # vim: ai ts=4 sw=4 sts=4 expandtab