diff --git a/rhythmbox-0.11.5-amazon-ecs.patch b/rhythmbox-0.11.5-amazon-ecs.patch new file mode 100644 index 0000000..42f192b --- /dev/null +++ b/rhythmbox-0.11.5-amazon-ecs.patch @@ -0,0 +1,406 @@ +diff --git a/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py b/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py +index 8fb3e78..489d302 100644 +--- a/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py ++++ b/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py +@@ -35,11 +35,13 @@ class AmazonCoverArtSearch (object): + self.searching = False + self.cancel = False + self.loader = loader ++ # "JP is the only locale that correctly takes UTF8 input. All other locales use LATIN1." ++ # http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1295&categoryID=117 + self._supportedLocales = { +- "en_US" : ("us", "xml.amazon.com", "music"), +- "en_GB" : ("uk", "xml-eu.amazon.com", "music"), +- "de" : ("de", "xml-eu.amazon.com", "music"), +- "ja" : ("jp", "xml.amazon.co.jp", "music-jp") ++ "en_US" : ("com", "latin1"), ++ "en_GB" : ("co.uk", "latin1"), ++ "de" : ("de", "latin1"), ++ "ja" : ("jp", "utf8") + } + self.db = None + self.entry = None +@@ -55,10 +57,7 @@ class AmazonCoverArtSearch (object): + if self._supportedLocales.has_key (lang): + lc_id = lang + +- lc_host = self._supportedLocales[lc_id][1] +- lc_name = self._supportedLocales[lc_id][0] +- lc_mode = self._supportedLocales[lc_id][2] +- return ((lc_host, lc_name, lc_mode)) ++ return self._supportedLocales[lc_id] + + def search (self, db, entry, on_search_completed_callback, *args): + self.searching = True +@@ -127,15 +126,17 @@ class AmazonCoverArtSearch (object): + self.search_next (); + + def __build_url (self, keyword): +- (lc_host, lc_name, lc_mode) = self.__get_locale () +- +- url = "http://" + lc_host + "/onca/xml3?f=xml" +- url += "&t=%s" % ASSOCIATE +- url += "&dev-t=%s" % LICENSE_KEY +- url += "&type=%s" % 'lite' +- url += "&locale=%s" % lc_name +- url += "&mode=%s" % lc_mode +- url += "&%s=%s" % ('KeywordSearch', urllib.quote (keyword)) ++ (tld, encoding) = self.__get_locale () ++ ++ url = "http://ecs.amazonaws." + tld + "/onca/xml" \ ++ "?Service=AWSECommerceService" \ ++ "&AWSAccessKeyId=" + LICENSE_KEY + \ ++ "&AssociateTag=" + ASSOCIATE + \ ++ "&ResponseGroup=Images,ItemAttributes" \ ++ "&Operation=ItemSearch" \ ++ "&SearchIndex=Music" \ ++ "&Keywords=" + urllib.quote (keyword.encode (encoding, 'replace')) ++ print "url: '%s'" % (url) + + return url + +@@ -161,26 +162,21 @@ class AmazonCoverArtSearch (object): + + def __unmarshal (self, element): + rc = Bag () +- if isinstance (element, minidom.Element) and (element.tagName == 'Details'): +- rc.URL = element.attributes["url"].value + childElements = [e for e in element.childNodes if isinstance (e, minidom.Element)] + if childElements: + for child in childElements: + key = child.tagName + if hasattr (rc, key): +- if type (getattr (rc, key)) <> type ([]): ++ if not isinstance (getattr (rc, key), list): + setattr (rc, key, [getattr (rc, key)]) +- setattr (rc, key, getattr (rc, key) + [self.__unmarshal (child)]) +- elif isinstance(child, minidom.Element) and (child.tagName == 'Details'): +- setattr (rc,key,[self.__unmarshal(child)]) ++ getattr (rc, key).append (self.__unmarshal (child)) ++ # get_best_match_urls() wants a list, even if there is only one item/artist ++ elif (child.tagName == "Item") or (child.tagName == "Artist"): ++ setattr (rc, key, [self.__unmarshal(child)]) + else: + setattr (rc, key, self.__unmarshal(child)) + else: + rc = "".join ([e.data for e in element.childNodes if isinstance (e, minidom.Text)]) +- if element.tagName == 'SalesRank': +- rc = rc.replace ('.', '') +- rc = rc.replace (',', '') +- rc = int (rc) + return rc + + def on_search_response (self, result_data): +@@ -194,14 +190,14 @@ class AmazonCoverArtSearch (object): + self.search_next() + return + +- data = self.__unmarshal (xmldoc).ProductInfo ++ data = self.__unmarshal (xmldoc).ItemSearchResponse.Items + +- if hasattr(data, 'ErrorMsg'): ++ if hasattr (data.Request, "Errors"): + # Search was unsuccessful, try next keyword + self.search_next () + else: + # We got some search results +- self.on_search_results (data.Details) ++ self.on_search_results (data.Item) + + def on_search_results (self, results): + self.on_search_completed (results) +@@ -224,10 +220,8 @@ class AmazonCoverArtSearch (object): + return s + + def __valid_match (self, item): +- if item.ImageUrlLarge == "" and item.ImageUrlMedium == "": +- print "%s doesn't have image URLs; ignoring" % (item.URL) +- return False +- return True ++ return (hasattr (item, "LargeImage") or hasattr (item, "MediumImage")) \ ++ and hasattr (item, "ItemAttributes") + + def get_best_match_urls (self, search_results): + # Default to "no match", our results must match our criteria +@@ -238,11 +232,11 @@ class AmazonCoverArtSearch (object): + if self.search_album != _("Unknown"): + album_check = self.__tidy_up_string (self.search_album) + for item in search_results: ++ if not hasattr (item.ItemAttributes, "Title"): ++ continue + +- # Check for album name in ProductName +- product_name = self.__tidy_up_string (item.ProductName) +- +- if product_name == album_check: ++ album = self.__tidy_up_string (item.ItemAttributes.Title) ++ if album == album_check: + # Found exact album, can not get better than that + best_match = item + break +@@ -250,8 +244,9 @@ class AmazonCoverArtSearch (object): + # Check the results for both an album name that contains the name + # we're searching for, and an album name that's a substring of the + # name we're searching for +- elif (best_match is None) and (product_name.find (album_check) != -1 +- or album_check.find (product_name) != -1): ++ elif (best_match is None) and \ ++ (album.find (album_check) != -1 or ++ album_check.find (album) != -1): + best_match = item + + # If we still have no definite hit, use first result where artist matches +@@ -261,13 +256,10 @@ class AmazonCoverArtSearch (object): + # Check if artist appears in the Artists list + hit = False + for item in search_results: ++ if not hasattr (item.ItemAttributes, "Artist"): ++ continue + +- if type (item.Artists.Artist) <> type ([]): +- artists = [item.Artists.Artist] +- else: +- artists = item.Artists.Artist +- +- for artist in artists: ++ for artist in item.ItemAttributes.Artist: + artist = self.__tidy_up_string (artist) + if artist.find (artist_check) != -1: + best_match = item +@@ -276,10 +268,9 @@ class AmazonCoverArtSearch (object): + if hit: + break + +- if best_match: +- return filter(lambda x: x != "", [item.ImageUrlLarge, item.ImageUrlMedium]) +- else: +- return [] ++ return [getattr (best_match, size).URL for size in ("LargeImage", "MediumImage") ++ if hasattr (best_match, size)] + +- except TypeError: ++ except TypeError, e: ++ print "TypeError: %s" % (e) + return [] +diff --git a/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py b/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py +index 489d302..13ea35a 100644 +--- a/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py ++++ b/plugins/artdisplay/artdisplay/AmazonCoverArtSearch.py +@@ -27,6 +27,10 @@ LICENSE_KEY = "18C3VZN9HCECM5G3HQG2" + DEFAULT_LOCALE = "en_US" + ASSOCIATE = "webservices-20" + ++# We are not allowed to batch more than 2 requests at once ++# http://docs.amazonwebservices.com/AWSEcommerceService/4-0/PgCombiningOperations.html ++MAX_BATCH_JOBS = 2 ++ + + class Bag: pass + +@@ -35,29 +39,31 @@ class AmazonCoverArtSearch (object): + self.searching = False + self.cancel = False + self.loader = loader ++ self.db = None ++ self.entry = None ++ (self.tld, self.encoding) = self.__get_locale () ++ ++ def __get_locale (self): + # "JP is the only locale that correctly takes UTF8 input. All other locales use LATIN1." + # http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1295&categoryID=117 +- self._supportedLocales = { ++ supported_locales = { + "en_US" : ("com", "latin1"), + "en_GB" : ("co.uk", "latin1"), + "de" : ("de", "latin1"), + "ja" : ("jp", "utf8") + } +- self.db = None +- self.entry = None + +- def __get_locale (self): +- default = locale.getdefaultlocale () + lc_id = DEFAULT_LOCALE +- if default[0] is not None: +- if self._supportedLocales.has_key (default[0]): +- lc_id = default[0] ++ default = locale.getdefaultlocale ()[0] ++ if default: ++ if supported_locales.has_key (default): ++ lc_id = default + else: +- lang = default[0].split("_")[0] +- if self._supportedLocales.has_key (lang): ++ lang = default.split("_")[0] ++ if supported_locales.has_key (lang): + lc_id = lang + +- return self._supportedLocales[lc_id] ++ return supported_locales[lc_id] + + def search (self, db, entry, on_search_completed_callback, *args): + self.searching = True +@@ -71,6 +77,10 @@ class AmazonCoverArtSearch (object): + st_artist = db.entry_get (entry, rhythmdb.PROP_ARTIST) or _("Unknown") + st_album = db.entry_get (entry, rhythmdb.PROP_ALBUM) or _("Unknown") + ++ if st_artist == st_album == _("Unknown"): ++ self.on_search_completed (None) ++ return ++ + # Tidy up + + # Replace quote characters +@@ -118,60 +128,53 @@ class AmazonCoverArtSearch (object): + self.keywords.append ("%s %s" % (st_artist, st_album)) + if st_album_no_vol != st_album: + self.keywords.append ("%s %s" % (st_artist, st_album_no_vol)) +- if (st_album != _("Unknown")): +- self.keywords.append ("Various %s" % (st_album)) ++ self.keywords.append ("Various %s" % (st_album)) + self.keywords.append ("%s" % (st_artist)) + + # Initiate asynchronous search +- self.search_next (); ++ self.search_next () + +- def __build_url (self, keyword): +- (tld, encoding) = self.__get_locale () ++ def search_next (self): ++ if len (self.keywords) == 0: ++ # No keywords left to search -> no results ++ self.on_search_completed (None) ++ return False + +- url = "http://ecs.amazonaws." + tld + "/onca/xml" \ +- "?Service=AWSECommerceService" \ +- "&AWSAccessKeyId=" + LICENSE_KEY + \ +- "&AssociateTag=" + ASSOCIATE + \ +- "&ResponseGroup=Images,ItemAttributes" \ +- "&Operation=ItemSearch" \ +- "&SearchIndex=Music" \ +- "&Keywords=" + urllib.quote (keyword.encode (encoding, 'replace')) +- print "url: '%s'" % (url) ++ self.searching = True + +- return url ++ url = "http://ecs.amazonaws." + self.tld + "/onca/xml" \ ++ "?Service=AWSECommerceService" \ ++ "&AWSAccessKeyId=" + LICENSE_KEY + \ ++ "&AssociateTag=" + ASSOCIATE + \ ++ "&ResponseGroup=Images,ItemAttributes" \ ++ "&Operation=ItemSearch" \ ++ "&ItemSearch.Shared.SearchIndex=Music" + +- def search_next (self): +- self.searching = True +- +- if len (self.keywords)==0: +- keyword = None +- else: ++ job = 1 ++ while job <= MAX_BATCH_JOBS and len (self.keywords) > 0: + keyword = self.keywords.pop (0) ++ keyword = keyword.encode (self.encoding, "ignore") ++ keyword = keyword.strip () ++ keyword = urllib.quote (keyword) ++ url += "&ItemSearch.%d.Keywords=%s" % (job, keyword) ++ job += 1 + +- if keyword is None: +- # No keywords left to search -> no results +- self.on_search_completed (None) +- ret = False +- else: +- # Retrieve search for keyword +- url = self.__build_url (keyword.strip ()) +- self.loader.get_url (url, self.on_search_response) +- ret = True +- +- return ret ++ # Retrieve search for keyword ++ self.loader.get_url (url, self.on_search_response) ++ return True + + def __unmarshal (self, element): + rc = Bag () +- childElements = [e for e in element.childNodes if isinstance (e, minidom.Element)] +- if childElements: +- for child in childElements: ++ child_elements = [e for e in element.childNodes if isinstance (e, minidom.Element)] ++ if child_elements: ++ for child in child_elements: + key = child.tagName + if hasattr (rc, key): + if not isinstance (getattr (rc, key), list): + setattr (rc, key, [getattr (rc, key)]) + getattr (rc, key).append (self.__unmarshal (child)) + # get_best_match_urls() wants a list, even if there is only one item/artist +- elif (child.tagName == "Item") or (child.tagName == "Artist"): ++ elif child.tagName in ("Items", "Item", "Artist"): + setattr (rc, key, [self.__unmarshal(child)]) + else: + setattr (rc, key, self.__unmarshal(child)) +@@ -190,14 +193,14 @@ class AmazonCoverArtSearch (object): + self.search_next() + return + +- data = self.__unmarshal (xmldoc).ItemSearchResponse.Items +- +- if hasattr (data.Request, "Errors"): +- # Search was unsuccessful, try next keyword ++ data = self.__unmarshal (xmldoc) ++ if not hasattr (data, "ItemSearchResponse") or \ ++ not hasattr (data.ItemSearchResponse, "Items"): ++ # Something went wrong ... + self.search_next () + else: + # We got some search results +- self.on_search_results (data.Item) ++ self.on_search_results (data.ItemSearchResponse.Items) + + def on_search_results (self, results): + self.on_search_completed (results) +@@ -227,11 +230,15 @@ class AmazonCoverArtSearch (object): + # Default to "no match", our results must match our criteria + best_match = None + +- search_results = filter(self.__valid_match, search_results) +- try: ++ for result in search_results: ++ if not hasattr (result, "Item"): ++ # Search was unsuccessful, try next batch job ++ continue ++ ++ items = filter(self.__valid_match, result.Item) + if self.search_album != _("Unknown"): + album_check = self.__tidy_up_string (self.search_album) +- for item in search_results: ++ for item in items: + if not hasattr (item.ItemAttributes, "Title"): + continue + +@@ -255,7 +262,7 @@ class AmazonCoverArtSearch (object): + if best_match is None: + # Check if artist appears in the Artists list + hit = False +- for item in search_results: ++ for item in items: + if not hasattr (item.ItemAttributes, "Artist"): + continue + +@@ -268,9 +275,10 @@ class AmazonCoverArtSearch (object): + if hit: + break + +- return [getattr (best_match, size).URL for size in ("LargeImage", "MediumImage") ++ urls = [getattr (best_match, size).URL for size in ("LargeImage", "MediumImage") + if hasattr (best_match, size)] ++ if urls: ++ return urls + +- except TypeError, e: +- print "TypeError: %s" % (e) +- return [] ++ # No search was successful ++ return [] diff --git a/rhythmbox.spec b/rhythmbox.spec index 8ada451..db02991 100644 --- a/rhythmbox.spec +++ b/rhythmbox.spec @@ -3,7 +3,7 @@ Name: rhythmbox Summary: Music Management Application Version: 0.11.5 -Release: 9%{?dist} +Release: 10%{?dist} License: GPLv2+ and GFDL+ Group: Applications/Multimedia URL: http://www.gnome.org/projects/rhythmbox/ @@ -68,6 +68,8 @@ Patch5: rb-gvfs-cdda-activation.patch Patch6: rhythmbox-0.11.5-ipod-vfat.patch # http://bugzilla.gnome.org/show_bug.cgi?id=512226 Patch7: rhythmbox-0.11.5-xfade-deadlock.patch +# http://bugzilla.gnome.org/show_bug.cgi?id=513851 +Patch8: rhythmbox-0.11.5-amazon-ecs.patch %description Rhythmbox is an integrated music management application based on the powerful @@ -104,6 +106,7 @@ UPnP/DLNA network devices. %patch5 -p0 -b .cdda-activation %patch6 -p0 -b .ipod-vfat %patch7 -p1 -b .xfade-deadlock +%patch8 -p1 -b .amazon-ecs %build # work around a gstreamer bug @@ -217,6 +220,9 @@ fi %{_libdir}/rhythmbox/plugins/upnp_coherence %changelog +* Fri Apr 11 2008 - Bastien Nocera - 0.11.5-10 +- Add patch to use the new Amazon search, the old one was shutdown + * Tue Apr 08 2008 - Bastien Nocera - 0.11.5-9 - Update deadlock fix patch