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