]> kaliko git repositories - mpd-sima.git/blob - sima/lib/simaecho.py
Fixed blacklisting in track mode
[mpd-sima.git] / sima / lib / simaecho.py
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2014 Jack Kaliko <kaliko@azylum.org>
4 #
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.
9 #
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.
14 #
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/>.
17 #
18 #
19
20 """
21 Consume EchoNest web service
22 """
23
24 __version__ = '0.0.1'
25 __author__ = 'Jack Kaliko'
26
27
28 from datetime import datetime, timedelta
29
30 from requests import get, Request, Timeout, ConnectionError
31
32 from sima import ECH
33 from sima.lib.meta import Artist
34 from sima.lib.track import Track
35 from sima.utils.utils import WSError, WSNotFound, WSTimeout, WSHTTPError
36 from sima.utils.utils import getws, Throttle, Cache, purge_cache
37 if len(ECH.get('apikey')) == 23:  # simple hack allowing imp.reload
38     getws(ECH)
39
40 # Some definitions
41 WAIT_BETWEEN_REQUESTS = timedelta(0, 1)
42 SOCKET_TIMEOUT = 4
43
44
45 class SimaEch:
46     """EchoNest http client
47     """
48     root_url = 'http://{host}/api/{version}'.format(**ECH)
49     cache = {}
50     timestamp = datetime.utcnow()
51     ratelimit = None
52     name = 'EchoNest'
53
54     def __init__(self, cache=True):
55         self.artist = None
56         self._ressource = None
57         self.current_element = None
58         self.caching = cache
59         purge_cache(self.__class__)
60
61     def _fetch(self, payload):
62         """Use cached elements or proceed http request"""
63         url = Request('GET', self._ressource, params=payload,).prepare().url
64         if url in SimaEch.cache:
65             self.current_element = SimaEch.cache.get(url).elem
66             return
67         try:
68             self._fetch_ws(payload)
69         except Timeout:
70             raise WSTimeout('Failed to reach server within {0}s'.format(
71                                SOCKET_TIMEOUT))
72         except ConnectionError as err:
73             raise WSError(err)
74
75     @Throttle(WAIT_BETWEEN_REQUESTS)
76     def _fetch_ws(self, payload):
77         """fetch from web service"""
78         req = get(self._ressource, params=payload,
79                             timeout=SOCKET_TIMEOUT)
80         self.__class__.ratelimit = req.headers.get('x-ratelimit-remaining', None)
81         if req.status_code is not 200:
82             raise WSHTTPError('{0.status_code}: {0.reason}'.format(req))
83         self.current_element = req.json()
84         self._controls_answer()
85         if self.caching:
86             SimaEch.cache.update({req.url:
87                                  Cache(self.current_element)})
88
89     def _controls_answer(self):
90         """Controls answer.
91         """
92         status = self.current_element.get('response').get('status')
93         code = status.get('code')
94         if code is 0:
95             return True
96         if code is 5:
97             raise WSNotFound('Artist not found: "{0}"'.format(self.artist))
98         raise WSError(status.get('message'))
99
100     def _forge_payload(self, artist, top=False):
101         """Build payload
102         """
103         payload = {'api_key': ECH.get('apikey')}
104         if not isinstance(artist, Artist):
105             raise TypeError('"{0!r}" not an Artist object'.format(artist))
106         self.artist = artist
107         if artist.mbid:
108             payload.update(
109                     id='musicbrainz:artist:{0}'.format(artist.mbid))
110         else:
111             payload.update(name=artist.name)
112         payload.update(bucket='id:musicbrainz')
113         payload.update(results=100)
114         if top:
115             if artist.mbid:
116                 aid = payload.pop('id')
117                 payload.update(artist_id=aid)
118             else:
119                 name = payload.pop('name')
120                 payload.update(artist=name)
121             payload.update(results=100)
122             payload.update(sort='song_hotttnesss-desc')
123         return payload
124
125     def get_similar(self, artist=None):
126         """Fetch similar artists
127         """
128         payload = self._forge_payload(artist)
129         # Construct URL
130         self._ressource = '{0}/artist/similar'.format(SimaEch.root_url)
131         self._fetch(payload)
132         for art in self.current_element.get('response').get('artists'):
133             artist = {}
134             mbid = None
135             if 'foreign_ids' in art:
136                 for frgnid in art.get('foreign_ids'):
137                     if frgnid.get('catalog') == 'musicbrainz':
138                         mbid = frgnid.get('foreign_id'
139                                           ).lstrip('musicbrainz:artist:')
140             yield Artist(mbid=mbid, name=art.get('name'))
141
142     def get_toptrack(self, artist=None):
143         """Fetch artist top tracks
144         """
145         payload = self._forge_payload(artist, top=True)
146         # Construct URL
147         self._ressource = '{0}/song/search'.format(SimaEch.root_url)
148         self._fetch(payload)
149         titles = list()
150         artist = {
151                 'artist': artist.name,
152                 'musicbrainz_artistid': artist.mbid,
153                 }
154         for song in self.current_element.get('response').get('songs'):
155             title = song.get('title')
156             if title not in titles:
157                 titles.append(title)
158                 yield Track(title=title, **artist)
159
160
161 # VIM MODLINE
162 # vim: ai ts=4 sw=4 sts=4 expandtab