Package screenlets :: Package plugins :: Module AmazonCoverArtSearch
[hide private]
[frames] | no frames]

Source Code for Module screenlets.plugins.AmazonCoverArtSearch

  1  # -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-  
  2  # 
  3  # Copyright (C) 2006 - Gareth Murphy, Martin Szulecki 
  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 2, or (at your option) 
  8  # 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, write to the Free Software 
 17  # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA. 
 18   
 19  from xml.dom import minidom 
 20  import re 
 21  import locale 
 22  import urllib 
 23   
 24  LICENSE_KEY = "18C3VZN9HCECM5G3HQG2" 
 25  DEFAULT_LOCALE = "en_US" 
 26  ASSOCIATE = "webservices-20" 
 27   
 28   
29 -class Bag: pass
30
31 -class AmazonCoverArtSearch (object):
32 - def __init__ (self, loader):
33 self.searching = False 34 self.cancel = False 35 self.loader = loader 36 self._supportedLocales = { 37 "en_US" : ("us", "xml.amazon.com"), 38 "en_GB" : ("uk", "xml-eu.amazon.com"), 39 "de" : ("de", "xml-eu.amazon.com"), 40 "ja" : ("jp", "xml.amazon.co.jp") 41 }
42
43 - def __get_locale (self):
44 default = locale.getdefaultlocale () 45 lc_id = DEFAULT_LOCALE 46 if default[0] is not None: 47 if self._supportedLocales.has_key (default[0]): 48 lc_id = default[0] 49 50 lc_host = self._supportedLocales[lc_id][1] 51 lc_name = self._supportedLocales[lc_id][0] 52 return ((lc_host, lc_name))
53
54 - def search (self, artist, album, on_search_completed_callback, *args):
55 self.searching = True 56 self.cancel = False 57 self.on_search_completed_callback = on_search_completed_callback 58 self.args = args 59 self.keywords = [] 60 61 self.album = album 62 self.artist = artist 63 64 st_artist = artist or "Unknown" 65 st_album = album or "Unknown" 66 67 # Tidy up 68 69 # Replace quote characters 70 # don't replace single quote: could be important punctuation 71 for char in ["\""]: 72 st_artist = st_artist.replace (char, '') 73 st_album = st_album.replace (char, '') 74 75 76 self.st_album = st_album 77 self.st_artist = st_artist 78 79 # Remove variants of Disc/CD [1-9] from album title before search 80 for exp in ["\([Dd]isc *[1-9]+\)", "\([Cc][Dd] *[1-9]+\)"]: 81 p = re.compile (exp) 82 st_album = p.sub ('', st_album) 83 84 st_album_no_vol = st_album 85 for exp in ["\(*[Vv]ol.*[1-9]+\)*"]: 86 p = re.compile (exp) 87 st_album_no_vol = p.sub ('', st_album_no_vol) 88 89 self.st_album_no_vol = st_album_no_vol 90 91 # Save current search's properties 92 self.search_album = st_album 93 self.search_artist = st_artist 94 self.search_album_no_vol = st_album_no_vol 95 96 # TODO: Improve to decrease wrong cover downloads, maybe add severity? 97 # Assemble list of search keywords (and thus search queries) 98 if st_album == "Unknown": 99 self.keywords.append ("%s Best of" % (st_artist)) 100 self.keywords.append ("%s Greatest Hits" % (st_artist)) 101 self.keywords.append ("%s Essential" % (st_artist)) 102 self.keywords.append ("%s Collection" % (st_artist)) 103 self.keywords.append ("%s" % (st_artist)) 104 elif st_artist == "Unknown": 105 self.keywords.append ("%s" % (st_album)) 106 if st_album_no_vol != st_artist: 107 self.keywords.append ("%s" % (st_album_no_vol)) 108 self.keywords.append ("Various %s" % (st_album)) 109 else: 110 if st_album != st_artist: 111 self.keywords.append ("%s %s" % (st_artist, st_album)) 112 if st_album_no_vol != st_album: 113 self.keywords.append ("%s %s" % (st_artist, st_album_no_vol)) 114 if (st_album != "Unknown"): 115 self.keywords.append ("Various %s" % (st_album)) 116 self.keywords.append ("%s" % (st_artist)) 117 118 # Initiate asynchronous search 119 self.search_next ();
120
121 - def __build_url (self, keyword):
122 try: 123 (lc_host, lc_name) = self.__get_locale () 124 125 url = "http://" + lc_host + "/onca/xml3?f=xml" 126 url += "&t=%s" % ASSOCIATE 127 url += "&dev-t=%s" % LICENSE_KEY 128 url += "&type=%s" % 'lite' 129 url += "&locale=%s" % lc_name 130 url += "&mode=%s" % 'music' 131 url += "&%s=%s" % ('KeywordSearch', urllib.quote (keyword)) 132 133 return url 134 except:return None
135
136 - def search_next (self):
137 self.searching = True 138 139 if len (self.keywords)==0: 140 keyword = None 141 else: 142 keyword = self.keywords.pop (0) 143 144 if keyword is None: 145 # No keywords left to search -> no results 146 self.on_search_completed (None) 147 ret = False 148 else: 149 # Retrieve search for keyword 150 url = self.__build_url (keyword.strip ()) 151 if url != None: 152 self.loader.get_url (url, self.on_search_response) 153 ret = True 154 else: 155 ret = False 156 157 return ret
158
159 - def __unmarshal (self, element):
160 rc = Bag () 161 if isinstance (element, minidom.Element) and (element.tagName == 'Details'): 162 rc.URL = element.attributes["url"].value 163 childElements = [e for e in element.childNodes if isinstance (e, minidom.Element)] 164 if childElements: 165 for child in childElements: 166 key = child.tagName 167 if hasattr (rc, key): 168 if type (getattr (rc, key)) <> type ([]): 169 setattr (rc, key, [getattr (rc, key)]) 170 setattr (rc, key, getattr (rc, key) + [self.__unmarshal (child)]) 171 elif isinstance(child, minidom.Element) and (child.tagName == 'Details'): 172 setattr (rc,key,[self.__unmarshal(child)]) 173 else: 174 setattr (rc, key, self.__unmarshal(child)) 175 else: 176 rc = "".join ([e.data for e in element.childNodes if isinstance (e, minidom.Text)]) 177 if element.tagName == 'SalesRank': 178 rc = rc.replace ('.', '') 179 rc = rc.replace (',', '') 180 rc = int (rc) 181 return rc
182
183 - def on_search_response (self, result_data):
184 if result_data is None: 185 self.search_next() 186 return 187 188 try: 189 xmldoc = minidom.parseString (result_data) 190 except: 191 self.search_next() 192 return 193 194 data = self.__unmarshal (xmldoc).ProductInfo 195 196 if hasattr(data, 'ErrorMsg'): 197 # Search was unsuccessful, try next keyword 198 self.search_next () 199 else: 200 # We got some search results 201 self.on_search_results (data.Details)
202
203 - def on_search_results (self, results):
204 self.on_search_completed (results)
205
206 - def on_search_completed (self, result):
207 self.on_search_completed_callback (self, self.artist, self.album, result, *self.args) 208 self.searching = False
209
210 - def __tidy_up_string (self, s):
211 # Lowercase 212 s = s.lower () 213 # Strip 214 s = s.strip () 215 216 # TODO: Convert accented to unaccented (fixes matching Salomé vs Salome) 217 s = s.replace (" - ", " ") 218 s = s.replace (": ", " ") 219 s = s.replace (" & ", " and ") 220 221 return s
222
223 - def get_best_match_urls (self, search_results):
224 # Default to "no match", our results must match our criteria 225 best_match = None 226 227 try: 228 if self.search_album != "Unknown": 229 album_check = self.__tidy_up_string (self.search_album) 230 for item in search_results: 231 # Check for album name in ProductName 232 product_name = self.__tidy_up_string (item.ProductName) 233 234 if product_name == album_check: 235 # Found exact album, can not get better than that 236 best_match = item 237 break 238 # If we already found a best_match, just keep checking for exact one 239 elif (best_match is None) and (product_name.find (album_check) != -1): 240 best_match = item 241 242 # If we still have no definite hit, use first result where artist matches 243 if (self.search_album == "Unknown" and self.search_artist != "Unknown"): 244 artist_check = self.__tidy_up_string (self.search_artist) 245 if best_match is None: 246 # Check if artist appears in the Artists list 247 hit = False 248 for item in search_results: 249 if type (item.Artists.Artist) <> type ([]): 250 artists = [item.Artists.Artist] 251 else: 252 artists = item.Artists.Artist 253 254 for artist in artists: 255 artist = self.__tidy_up_string (artist) 256 if artist.find (artist_check) != -1: 257 best_match = item 258 hit = True 259 break 260 if hit: 261 break 262 263 if best_match: 264 return [best_match.ImageUrlLarge, best_match.ImageUrlMedium] 265 else: 266 return [] 267 268 except TypeError: 269 return []
270