def artists(self): try: return [ { 'name' : xp(self.attributes, 'Artist')['v'] } ] except Exception: try: return [ { 'name' : xp(self.attributes, 'Creator')['v'] } ] except Exception: return []
def images(self): try: image_set = xp(self.underlying.data, 'ImageSets','ImageSet') image = xp(image_set,'LargeImage','URL')['v'] if image is not None: return [image] except Exception: pass return []
def length(self): # Traditionally runtime is reported in minutes, but we want it in seconds. unitsConversion = 60 try: units = xp(self.attributes, 'RunningTime')['a']['Units'] if units != 'minutes': raise Exception('Unexpected units found on Amazon movie: (%s)' % units) except KeyError: pass try: return float(xp(self.attributes, 'RunningTime')['v']) * unitsConversion except KeyError: return None
def _is_video_game(item): try: if xp(item, 'ItemAttributes', 'ProductGroup')['v'].lower() == "video games": return True except Exception: pass try: if xp(item, 'ItemAttributes', 'ProductTypeName')['v'].lower() == "console_video_game": return True except Exception: pass return False
def publishers(self): try: return [{ 'name': xp(self.attributes, 'Publisher')['v'] }] except Exception: return []
def _issueLookup(self): # Slightly ugly -- calling ResolverObject method just because we know all _AmazonObject implementations also # inherit from ResolverObject. TODO: Get rid of multiple inheritance! # We don't catch the LookupRequiredError here because if you are initializing a capped-lookup object without # passing in initial data, you are doing it wrong. self.countLookupCall('base data') raw = globalAmazon().item_lookup(timeout=MERGE_TIMEOUT, **self.__params) self.__data = xp(raw, 'ItemLookupResponse','Items','Item')
def authors(self): try: author = xp(self.attributes, 'Author')['v'] if '-N/A-' in author: return [] return [{ 'name': author }] except Exception: return []
def tracks(self): # We might be missing related items data entirely, in which case we start by issuing a lookup there. # TODO: This probably could be done as part of one lookup with the one about to be made. try: tracks = list(xp(self.data, 'RelatedItems')['c']['RelatedItem']) except KeyError: try: self._issueLookup() except LookupRequiredError: return [] try: tracks = list(xp(self.data, 'RelatedItems')['c']['RelatedItem']) page_count = int(xp(self.data, 'RelatedItems', 'RelatedItemPageCount')['v']) for i in range(1,page_count): page = i+1 self.countLookupCall('tracks') data = globalAmazon().item_lookup(ItemId=self.key, ResponseGroup='Large,RelatedItems', RelationshipType='Tracks', RelatedItemPage=str(page), timeout=MERGE_TIMEOUT) tracks.extend( xp(data, 'ItemLookupResponse', 'Items', 'Item', 'RelatedItems')['c']['RelatedItem'] ) track_d = {} for track in tracks: track_d[ int(xp(track, 'Item', 'ItemAttributes', 'TrackSequence')['v']) ] = { 'name' : xp(track, 'Item', 'ItemAttributes', 'Title')['v'], 'key' : xp(track, 'Item', 'ASIN')['v'], } return [ track_d[k] for k in sorted(track_d) ] except LookupRequiredException: return [] except Exception: # TODO: It seems possible that only one of the requests failed; shouldn't we keep the results of the others? report() return []
def trackSource(self, query=None, query_string=None): if query_string is None: query_string = ' '.join([ query.name ]) return self.__searchGen(AmazonTrack, { 'test':lambda item: xp(item, 'ItemAttributes', 'ProductTypeName')['v'] == "DOWNLOADABLE_MUSIC_TRACK", 'Keywords':query_string })
def underlying(self): try: versions = xp(self.data, 'AlternateVersions')['c']['AlternateVersion'] priorities = [ 'Kindle Edition', 'Hardcover', 'Paperback', 'Audio CD', ] self_kind = xp(self.attributes, 'Binding')['v'] for kind in priorities: if self_kind == kind: return self for version in versions: if xp(version, 'Binding')['v'] == kind: return AmazonBook(xp(version, 'ASIN')['v']) except Exception: pass return self
def albums(self): try: album = xp(self.data, 'RelatedItems', 'RelatedItem', 'Item') except KeyError: try: self._issueLookup() except LookupRequiredError: return [] try: album = xp(self.data, 'RelatedItems', 'RelatedItem', 'Item') key = xp(album, 'ASIN')['v'] attributes = xp(album, 'ItemAttributes') return [{ 'name' : xp(attributes, 'Title')['v'], 'source' : 'amazon', 'key' : key, }] except Exception: return []
def __augmentAlbumResultsWithSongs(self, albums, tracks): """ Boosts the ranks of albums in search if we also see tracks from those albums in the same search results. This part is admittedly pretty heinous. Unlike, say, iTunes, Amazon does not tell us the ID or even the name of the album for a track, but we can get it through comparing cover art URLs. Ugh. Note that albums and tracks are both SearchResult objects. TODO: Should probably change naming to reflect that. TODO: We can get tracks now! Use tracks instead. """ picUrlsToAlbums = {} for album in albums: try: largeImageUrl = xp(album.resolverObject.data, 'ImageSets', 'ImageSet', 'LargeImage', 'URL')['v'] if largeImageUrl in picUrlsToAlbums: logs.warning('Found multiple albums in Amazon results with identical cover art!') picUrlsToAlbums[largeImageUrl] = album except KeyError: pass # If there are dupes in the tracks side, we don't want to double-increment artist scores on that basis. seenTitlesAndArtists = set() for track in tracks: simpleTitle = trackSimplify(track.resolverObject.name) simpleArtist = '' if track.resolverObject.artists: simpleArtist = artistSimplify(track.resolverObject.artists[0]) if (simpleTitle, simpleArtist) in seenTitlesAndArtists: continue seenTitlesAndArtists.add((simpleTitle, simpleArtist)) try: largeImageUrl = xp(track.resolverObject.data, 'ImageSets', 'ImageSet', 'LargeImage', 'URL')['v'] if largeImageUrl not in picUrlsToAlbums: continue scoreBoost = track.relevance / 5 album = picUrlsToAlbums[largeImageUrl] album.addRelevanceComponentDebugInfo('boost from song %s' % track.resolverObject.name, scoreBoost) album.relevance += scoreBoost except KeyError: pass
def __constructMusicObjectFromResult(self, rawResult, maxLookupCalls=None): """ Determines whether the raw result is an album or a track and constructs an _AmazonObject appropriately. """ productTypeName = xp(rawResult, 'ItemAttributes', 'ProductTypeName')['v'] try: binding = xp(rawResult, 'ItemAttributes', 'Binding')['v'] except KeyError: binding = None asin = xp(rawResult, 'ASIN')['v'] if productTypeName == 'DOWNLOADABLE_MUSIC_TRACK': return AmazonTrack(asin, data=rawResult, maxLookupCalls=maxLookupCalls) elif productTypeName == 'DOWNLOADABLE_MUSIC_ALBUM': return AmazonAlbum(asin, data=rawResult, maxLookupCalls=maxLookupCalls) elif productTypeName in ['CONTRIBUTOR_AUTHORITY_SET', 'ABIS_DVD', 'VIDEO_VHS', 'DOWNLOADABLE_MUSIC_ARTIST']: return None elif binding in ['Audio CD', 'Vinyl']: return AmazonAlbum(asin, data=rawResult, maxLookupCalls=maxLookupCalls) raise AmazonSource.UnknownTypeError('Unknown product type %s seen on result with ASIN %s!' % (productTypeName, asin))
def entityProxyFromKey(self, key, **kwargs): try: lookupData = globalAmazon().item_lookup(ResponseGroup='Large', ItemId=key, timeout=MERGE_TIMEOUT) result = _getLookupResult(lookupData) kind = xp(result, 'ItemAttributes', 'ProductGroup')['v'].lower() logs.debug(kind) if kind == 'book' or kind == 'ebooks': return AmazonBook(key, result, 0) if kind == 'video games': return AmazonVideoGame(key, result, 0) return self.__constructMusicObjectFromResult(result, 0) except KeyError: logs.report() return None
def bookSource(self, query=None, query_string=None): if query_string is None: query_string = ' '.join([ query.name ]) return self.__searchGen(AmazonBook, { 'Keywords':query_string, 'SearchIndex':'Books', 'test': lambda item: xp(item, 'ItemAttributes', 'Binding')['v'] == 'Kindle Edition', }, { 'Keywords':query_string, 'SearchIndex':'Books', } )
def gen(): try: for params in queries: test = params.pop('test', lambda x: True) if 'SearchIndex' not in params: params['SearchIndex'] = 'All' if 'ResponseGroup' not in params: params['ResponseGroup'] = "ItemAttributes" results = globalAmazon().item_search(timeout=SEARCH_TIMEOUT, **params) for item in _getSearchResults(results): try: if test == None or test(item): yield xp(item, 'ASIN')['v'], item except Exception: pass except GeneratorExit: pass
def artists(self): try: return [ { 'name' : xp(self.attributes, 'Artist')['v'] } ] except Exception: pass try: return [ { 'name' : xp(self.attributes, 'Creator')['v'] } ] except Exception: pass try: album = xp(self.data, 'RelatedItems', 'RelatedItem', 'Item') key = xp(album,'ASIN')['v'] attributes = xp(album, 'ItemAttributes') return [ { 'name' : xp(attributes, 'Creator')['v'] } ] except Exception: return []
def raw_name(self): return xp(self.attributes, 'Title')['v']
def mpaa_rating(self): try: return xp(self.attributes, 'AudienceRating')['v'] except KeyError: return None
def isbn(self): try: return xp(self.attributes, 'ISBN')['v'] except KeyError: return None
def genres(self): try: return [ xp(self.attributes, 'Genre')['v'] ] except KeyError: return []
def description(self): try: return xp(self.data, 'EditorialReview', 'Content')['v'] except Exception: return ""
def genres(self): try: return [ xp(self.attributes, 'Genre')['v'] ] except Exception: return []
def sku_number(self): try: return xp(self.attributes, 'SKU')['v'] except Exception: return None
def release_date(self): try: return parseDateString( xp(self.attributes, 'PublicationDate')['v'] ) except Exception: return None
def platform(self): try: return { 'name': xp(self.attributes, 'Platform')['v'] } except Exception: return { 'name':'' }
def attributes(self): return xp(self.data, 'ItemAttributes')
def link(self): try: return xp(self.data, 'ItemLinks', 'ItemLink', 'URL')['v'] except Exception: return None
def authors(self): try: return [ { 'name': xp(self.attributes, 'Author')['v'] } ] except Exception: return []
def description(self): # TODO: Many of these attributes are common between a bunch of Amazon types. Put them in _AmazonObject. try: return xp(self.attributes, 'EditorialReview', 'Content')['v'] except KeyError: return ''