1
2
3
4
5
6
7
8 """
9
10 Covers by Amazon
11
12 methods to retrieve covers/album art via the
13 Amazon E-Commerce WebService v4
14 http://docs.amazonwebservices.com/AWSECommerceService/2007-04-04/DG/
15
16 The licence agreement says something about only
17 one request per second, so we need to serialize
18 and delay the calls a bit.
19
20 The AWSAccessKeyId supplied is _ONLY_ for the use
21 in conjunction with Coherence, http://coherence.beebits.net
22
23 If you use this library in your own software please
24 apply for your own key @ http://www.amazon.com/webservices
25 and follow the rules of their license.
26
27 Especially you must add the following disclaimer in a place
28 that is reasonably viewable by the user of your application:
29
30 PLEASE KEEP IN MIND THAT SOME OF THE CONTENT THAT WE
31 MAKE AVAILABLE TO YOU THROUGH THIS APPLICATION COMES
32 FROM AMAZON WEB SERVICES. ALL SUCH CONTENT IS PROVIDED
33 TO YOU "AS IS." THIS CONTENT AND YOUR USE OF IT
34 ARE SUBJECT TO CHANGE AND/OR REMOVAL AT ANY TIME.
35
36 Furthermore if you save any of the cover images you
37 have to take care that they are stored no longer than
38 a maximum of one month and requested then from Amazon
39 again.
40
41 """
42
43 import os
44 import urllib
45 import StringIO
46
47 from twisted.internet import reactor
48 from twisted.internet import defer
49 from twisted.web import client
50
51 from et import parse_xml
52
53 aws_server = { 'de': 'de',
54 'jp': 'jp',
55 'ca': 'ca',
56 'uk': 'co.uk',
57 'fr': 'fr'}
58
59
60 aws_artist_query = '&Operation=ItemSearch' \
61 '&SearchIndex=Music'
62
63 aws_asin_query = '&Operation=ItemLookup'
64
65 aws_response_group = '&ResponseGroup=Images'
66
67 aws_ns = 'http://webservices.amazon.com/AWSECommerceService/2005-10-05'
68
69 aws_image_size = { 'large': 'LargeImage',
70 'medium': 'MediumImage',
71 'small': 'SmallImage'}
72
74
75 _instance_ = None
76
78 obj = getattr(cls,'_instance_',None)
79 if obj is not None:
80 return obj
81 else:
82 obj = super(WorkQueue, cls).__new__(cls, *args, **kwargs)
83 cls._instance_ = obj
84 obj.max_workers = kwargs.get('max_workers', 1)
85 obj.queue = []
86 obj.workers = []
87 return obj
88
89 - def __init__(self, method, *args, **kwargs):
92
94 if len(self.queue) == 0:
95 return
96 if len(self.workers) >= self.max_workers:
97
98 return
99 work = self.queue.pop()
100 d = defer.maybeDeferred(work[0], *work[1],**work[2])
101 self.workers.append(d)
102 d.addCallback(self.remove_from_workers, d)
103 d.addErrback(self.remove_from_workers, d)
104
108
110
111 """
112 retrieve a cover image for a given ASIN,
113 a TITLE or
114 an ARTIST/TITLE combo
115
116 parameters are:
117
118 filename: where to save a received image
119 if NONE the image will be passed to the callback
120 callback: a method to call with the filename
121 or the image as a parameter
122 after the image request and save was successful
123 can be:
124 - only a callable
125 - a tuple with a callable,
126 - optional an argument or a tuple of arguments
127 - optional a dict with keyword arguments
128 not_found_callback: a method to call when the search at Amazon failed
129 can be:
130 - only a callable
131 - a tuple with a callable,
132 - optional an argument or a tuple of arguments
133 - optional a dict with keyword arguments
134 locale: which Amazon Webservice Server to use, defaults to .com
135 image_size: request the cover as large|medium|small image
136 resolution seems to be in pixels for
137 large: 500x500, medium: 160x160 and small: 75x75
138 asin: the Amazon Store Identification Number
139 artist: the artists name
140 title: the album title
141
142 if the filename extension and the received image extension differ,
143 the image is converted with PIL to the desired format
144 http://www.pythonware.com/products/pil/index.htm
145
146 """
147
148 - def __init__(self, filename, aws_key, callback=None, not_found_callback=None,
149 locale=None,
150 image_size='large',
151 title=None, artist=None, asin=None):
152 self.aws_base_query = '/onca/xml?Service=AWSECommerceService' \
153 '&AWSAccessKeyId=%s' % aws_key
154
155 self.filename = filename
156 self.callback = callback
157 self._errcall = not_found_callback
158 self.server = 'http://ecs.amazonaws.%s' % aws_server.get(locale,'com')
159 self.image_size = image_size
160
161 def sanitize(s):
162 if s is not None:
163 s = unicode(s.lower())
164 s = s.replace(unicode(u'ä'),unicode('ae'))
165 s = s.replace(unicode(u'ö'),unicode('oe'))
166 s = s.replace(unicode(u'ü'),unicode('ue'))
167 s = s.replace(unicode(u'ß'),unicode('ss'))
168 if isinstance(s,unicode):
169 s = s.encode('ascii','ignore')
170 else:
171 s = s.decode('utf-8').encode('ascii','ignore')
172 return s
173
174 if asin != None:
175 query = aws_asin_query + '&ItemId=%s' % urllib.quote(asin)
176 elif (artist is not None or title is not None):
177 query = aws_artist_query
178 if artist is not None:
179 artist = sanitize(artist)
180 query = '&'.join((query, 'Artist=%s' % urllib.quote(artist)))
181 if title is not None:
182 title = sanitize(title)
183 query = '&'.join((query, 'Title=%s' % urllib.quote(title)))
184 else:
185 raise KeyError, "Please supply either asin, title or artist and title arguments"
186 url = self.server+self.aws_base_query+aws_response_group+query
187 WorkQueue(self.send_request, url)
188
190
191 d= client.getPage(url)
192 d.addCallback(self.got_response)
193 d.addErrback(self.got_error, url)
194 return d
195
196 - def got_image(self, result, convert_from='', convert_to=''):
197
198 if(len(convert_from) and len(convert_to)):
199
200 try:
201 import Image
202
203 im = Image.open(StringIO.StringIO(result))
204 name,file_ext = os.path.splitext(self.filename)
205 self.filename = name + convert_to
206
207 im.save(self.filename)
208 except ImportError:
209 print "we need the Python Imaging Library to do image conversion"
210
211 if self.filename == None:
212 data = result
213 else:
214 data = self.filename
215
216 if self.callback is not None:
217
218 if isinstance(self.callback,tuple):
219 if len(self.callback) == 3:
220 c,a,kw = self.callback
221 if not isinstance(a,tuple):
222 a = (a,)
223 a=(data,) + a
224 c(*a,**kw)
225 if len(self.callback) == 2:
226 c,a = self.callback
227 if isinstance(a,dict):
228 c(data,**a)
229 else:
230 if not isinstance(a,tuple):
231 a = (a,)
232 a=(data,) + a
233 c(*a)
234 if len(self.callback) == 1:
235 c = self.callback
236 c(data)
237 else:
238 self.callback(data)
239
241 convert_from = convert_to = ''
242 result = parse_xml(result, encoding='utf-8')
243 image_tag = result.find('.//{%s}%s' % (aws_ns,aws_image_size.get(self.image_size,'large')))
244 if image_tag != None:
245 image_url = image_tag.findtext('{%s}URL' % aws_ns)
246 if self.filename == None:
247 d = client.getPage(image_url)
248 else:
249 _,file_ext = os.path.splitext(self.filename)
250 if file_ext == '':
251 _,image_ext = os.path.splitext(image_url)
252 if image_ext != '':
253 self.filename = ''.join((self.filename, image_ext))
254 else:
255 _,image_ext = os.path.splitext(image_url)
256 if image_ext != '' and file_ext != image_ext:
257
258 convert_from = image_ext
259 convert_to = file_ext
260 if len(convert_to):
261 d = client.getPage(image_url)
262 else:
263 d = client.downloadPage(image_url, self.filename)
264 d.addCallback(self.got_image, convert_from=convert_from, convert_to=convert_to)
265 d.addErrback(self.got_error, image_url)
266 else:
267 if self._errcall is not None:
268 if isinstance(self._errcall,tuple):
269 if len(self._errcall) == 3:
270 c,a,kw = self._errcall
271 if not isinstance(a,tuple):
272 a = (a,)
273 c(*a,**kw)
274 if len(self._errcall) == 2:
275 c,a = self._errcall
276 if isinstance(a,dict):
277 c(**a)
278 else:
279 if not isinstance(a,tuple):
280 a = (a,)
281 c(*a)
282 if len(self._errcall) == 1:
283 c = self._errcall
284 c()
285 else:
286 self._errcall()
287
289 print "got_error", failure, url
290
291 if __name__ == '__main__':
292
293 from twisted.python import usage
294
296 optParameters = [['artist', 'a', '', 'artist name'],
297 ['title', 't', '', 'title'],
298 ['asin', 's', '', 'ASIN'],
299 ['filename', 'f', 'cover.jpg', 'filename'],
300 ]
301
302 options = Options()
303 try:
304 options.parseOptions()
305 except usage.UsageError, errortext:
306 import sys
307 print '%s: %s' % (sys.argv[0], errortext)
308 print '%s: Try --help for usage details.' % (sys.argv[0])
309 sys.exit(1)
310
311 - def got_it(filename, *args, **kwargs):
312 print "Mylady, it is an image and its name is", filename, args, kwargs
313
314 aws_key = '1XHSE4FQJ0RK0X3S9WR2'
315 print options['asin'],options['artist'],options['title']
316 if len(options['asin']):
317 reactor.callWhenRunning(CoverGetter,options['filename'],aws_key, callback=got_it,asin=options['asin'])
318 elif len(options['artist']) and len(options['title']):
319 reactor.callWhenRunning(CoverGetter,options['filename'],aws_key, callback=got_it,artist=options['artist'],title=options['title'])
320
321 reactor.run()
322