Package elisa :: Package extern :: Package coherence :: Module covers_by_amazon
[hide private]
[frames] | no frames]

Source Code for Module elisa.extern.coherence.covers_by_amazon

  1  # -*- coding: utf-8 -*- 
  2   
  3  # Licensed under the MIT license 
  4  # http://opensource.org/licenses/mit-license.php 
  5   
  6  # Copyright 2007, Frank Scholz <coherence@beebits.net> 
  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   
73 -class WorkQueue(object):
74 75 _instance_ = None # Singleton 76
77 - def __new__(cls, *args, **kwargs):
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):
90 self.queue.append((method,args,kwargs)) 91 self.queue_run()
92
93 - def queue_run(self):
94 if len(self.queue) == 0: 95 return 96 if len(self.workers) >= self.max_workers: 97 #print "WorkQueue - all workers busy" 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
105 - def remove_from_workers(self, result, d):
106 self.workers.remove(d) 107 reactor.callLater(1,self.queue_run) # a very,very weak attempt
108
109 -class CoverGetter(object):
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
189 - def send_request(self,url,*args,**kwargs):
190 #print "send_request", url 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 #print "got_image" 198 if(len(convert_from) and len(convert_to)): 199 #print "got_image %d, convert to %s" % (len(result), convert_to) 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 #print "got_image", self.callback 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
240 - def got_response(self, result):
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 #print "hmm, we need a conversion..." 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
288 - def got_error(self, failure, url):
289 print "got_error", failure, url
290 291 if __name__ == '__main__': 292 293 from twisted.python import usage 294
295 - class Options(usage.Options):
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