]> kaliko git repositories - mpd-sima.git/blob - sima/lib/simaecho.py
More robust Meta Object
[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.5'
25 __author__ = 'Jack Kaliko'
26
27
28
29 from sima import ECH
30 from sima.lib.meta import Artist
31 from sima.lib.track import Track
32 from sima.lib.http import HttpClient
33 from sima.utils.utils import WSError, WSNotFound
34 from sima.utils.utils import getws
35 if len(ECH.get('apikey')) == 23:  # simple hack allowing imp.reload
36     getws(ECH)
37
38
39 def get_mbid(obj, foreign='foreign_ids'):
40     if foreign in obj:
41         for frgnid in obj.get(foreign):
42             if frgnid.get('catalog') == 'musicbrainz':
43                 return frgnid.get('foreign_id').split(':')[2]
44     return None
45
46
47 class SimaEch:
48     """EchoNest http client
49     """
50     root_url = 'http://{host}/api/{version}'.format(**ECH)
51     name = 'EchoNest'
52     cache = False
53     """HTTP cache to use, in memory or persitent.
54
55     :param BaseCache cache: Set a cache, defaults to `False`.
56     """
57     stats = {'etag':0,
58              'ccontrol':0,
59              'minrl':120,
60              'total':0}
61
62     def __init__(self):
63         self.http = HttpClient(cache=self.cache, stats=self.stats)
64
65     def _controls_answer(self, ans):
66         """Controls answer.
67         """
68         status = ans.get('response').get('status')
69         code = status.get('code')
70         if code is 0:
71             return True
72         if code is 5:
73             raise WSNotFound('Artist not found')
74         raise WSError(status.get('message'))
75
76     def _forge_payload(self, artist, top=False):
77         """Build payload
78         """
79         payload = {'api_key': ECH.get('apikey')}
80         if not isinstance(artist, Artist):
81             raise TypeError('"{0!r}" not an Artist object'.format(artist))
82         if artist.mbid:
83             payload.update(id='musicbrainz:artist:{0}'.format(artist.mbid))
84         else:
85             payload.update(name=artist.name)
86         payload.update(bucket='id:musicbrainz')
87         payload.update(results=100)
88         if top:
89             if artist.mbid:
90                 aid = payload.pop('id')
91                 payload.update(artist_id=aid)
92             else:
93                 name = payload.pop('name')
94                 payload.update(artist=name)
95             payload.update(results=100)
96             payload.update(sort='song_hotttnesss-desc')
97         # > hashing the URL into a cache key
98         # return a sorted list of 2-tuple to have consistent cache
99         return sorted(payload.items(), key=lambda param: param[0])
100
101     def get_similar(self, artist):
102         """Fetch similar artists
103
104         :param sima.lib.meta.Artist artist: `Artist` to fetch similar artists from
105         :returns: generator of :class:`sima.lib.meta.Artist`
106         """
107         payload = self._forge_payload(artist)
108         # Construct URL
109         ressource = '{0}/artist/similar'.format(SimaEch.root_url)
110         ans = self.http(ressource, payload)
111         self._controls_answer(ans.json())  # pylint: disable=no-member
112         for art in ans.json().get('response').get('artists'):  # pylint: disable=no-member
113             mbid = get_mbid(art)
114             yield Artist(mbid=mbid, name=art.get('name'))
115
116     def get_toptrack(self, artist):
117         """Fetch artist top tracks
118
119         :param sima.lib.meta.Artist artist: `Artist` to fetch top tracks from
120         :returns: generator of :class:`sima.lib.track.Track`
121         """
122         payload = self._forge_payload(artist, top=True)
123         # Construct URL
124         ressource = '{0}/song/search'.format(SimaEch.root_url)
125         ans = self.http(ressource, payload)
126         self._controls_answer(ans.json())  # pylint: disable=no-member
127         titles = list()
128         art = {'artist': artist.name,
129                'musicbrainz_artistid': artist.mbid,}
130         for song in ans.json().get('response').get('songs'):  # pylint: disable=no-member
131             title = song.get('title')
132             if not art.get('musicbrainz_artistid'):
133                 art['musicbrainz_artistid'] = get_mbid(song, 'artist_foreign_ids')
134             if title not in titles:
135                 titles.append(title)
136                 yield Track(title=title, **art)
137
138
139 # VIM MODLINE
140 # vim: ai ts=4 sw=4 sts=4 expandtab