def _get_characterart(self, mediaid): data = self.get_data(mediaid, 'characterart') if not data: return {} baseurl = data['base_url_character_art'] result = [] for characterart in data['character_art']: result.append({ 'url': baseurl + '/' + characterart['name'], 'language': None, 'rating': SortedDisplay(characterart['likes'], '{0} likes'.format(characterart['likes'])), 'size': SortedDisplay(characterart['height'], characterart['resolution']), 'provider': self.name, 'preview': baseurl + '/' + characterart['name'] }) return result
def _get_rating(self, image): if image['vote_count']: # Reweigh ratings, increase difference from 5 rating = image['vote_average'] rating = 5 + (rating - 5) * 2 return SortedDisplay(rating, '{0:.1f} stars'.format(image['vote_average'])) else: return SortedDisplay(5, 'Not rated')
def buildimage(self, url, title, fromartistfolder=False): provider = ARTIST_INFOFOLDER_PROVIDER if fromartistfolder else self.name result = {'url': url, 'provider': provider, 'preview': url} result['title'] = title result['rating'] = SortedDisplay(0, '') result['size'] = SortedDisplay(0, '') result['language'] = 'xx' return result
def _get_rating(self, image): if image['ratingsInfo']['count']: info = image['ratingsInfo'] rating = info['average'] if info['count'] < 5: # Reweigh ratings, decrease difference from 5 rating = 5 + (rating - 5) * sin(info['count'] / pi) return SortedDisplay(rating, '{0:.1f} stars'.format(info['average'])) else: return SortedDisplay(5, 'Not rated')
def build_resultimage(self, url, title): if isinstance(url, unicode): url = url.encode('utf-8') if url.startswith('http'): url = urllib.quote(url, safe="%/:=&?~#+!$,;'@()*[]") resultimage = {'url': url, 'provider': self.name, 'preview': url} resultimage['title'] = '<{0}>'.format(title) resultimage['rating'] = SortedDisplay(0, '') resultimage['size'] = SortedDisplay(0, '') resultimage['language'] = 'xx' return resultimage
def get_images(self, uniqueids, types=None): if not settings.get_apienabled('tvdb'): return {} if types is not None and not self.provides(types): return {} mediaid = get_mediaid(uniqueids) if not mediaid: return {} result = {} languages = base.languages # Useful fanart can be hidden by the language filter, try a few of the most frequently used flanguages = ['en', 'de', 'fr', 'es', 'ru'] flanguages.extend(lang for lang in languages if lang not in flanguages) for arttype in self.artmap: if types and not typematches(self.artmap[arttype], types): continue for language in languages if arttype != 'fanart' else flanguages: generaltype = self.artmap[arttype] data = self.get_data(mediaid, arttype, language) if not data: continue isseason = arttype.startswith('season') if not isseason: if generaltype not in result: result[generaltype] = [] for image in data['data']: if not image.get('fileName' ) or isseason and not image.get('subKey'): continue ntype = generaltype if isseason: ntype = ntype % image['subKey'] if ntype not in result: result[ntype] = [] resultimage = {'provider': self.name} resultimage['url'] = self.imageurl_base + image['fileName'] resultimage['preview'] = self.imageurl_base + ( image['thumbnail'] or '_cache/' + image['fileName']) resultimage[ 'language'] = language if shouldset_imagelanguage( image) else None resultimage['rating'] = self._get_rating(image) if arttype in ('series', 'seasonwide'): resultimage['size'] = SortedDisplay(758, '758x140') elif arttype == 'season': resultimage['size'] = SortedDisplay(1000, '680x1000') else: resultimage['size'] = parse_sortsize(image, arttype) result[ntype].append(resultimage) return result
def _get_animated_images(self, mediaid): data = self.get_data(mediaid, 'animated') if not data: return {} baseposter = data['base_url_posters'] basefanart = data['base_url_backgrounds'] result = {} for poster in data['posters']: newposter = { 'url': baseposter + '/' + poster['name'], 'language': get_language(poster.get('language', None)), 'rating': SortedDisplay(poster['likes'], '{0} likes'.format(poster['likes'])), 'size': SortedDisplay(poster['height'], poster['resolution']), 'provider': self.name, 'preview': baseposter + '/' + poster['name'] } arttype = 'animatedposter' if newposter[ 'language'] else 'animatedkeyart' if arttype not in result: result[arttype] = [] result[arttype].append(newposter) for fanart in data['backgrounds']: if 'animatedfanart' not in result: result['animatedfanart'] = [] result['animatedfanart'].append({ 'url': basefanart + '/' + fanart['name'], 'language': None, 'rating': SortedDisplay(fanart['likes'], '{0} likes'.format(fanart['likes'])), 'size': SortedDisplay(fanart['height'], fanart['resolution']), 'provider': self.name, 'preview': basefanart + '/' + fanart['name'] }) return result
def parse_sortsize(image, arttype): try: sortsize = int( image['resolution'].split('x')[0 if arttype != 'poster' else 1]) except (ValueError, IndexError): sortsize = 0 return SortedDisplay(sortsize, image['resolution'])
def build_image(self, url, arttype, image, likediv=5.0): result = {'url': url, 'provider': self.name} result['preview'] = url.replace('.fanart.tv/fanart/', '.fanart.tv/preview/') result['rating'] = SortedDisplay(5.25 + int(image['likes']) / float(likediv), '{0} likes'.format(image['likes'])) result['size'] = _get_imagesize(arttype) result['language'] = _get_imagelanguage(arttype, image) return result
class AbstractProvider(object): __metaclass__ = ABCMeta name = SortedDisplay(0, '') mediatype = None contenttype = None def __init__(self): self.getter = Getter(self.contenttype, self.login) self.getter.session.headers['User-Agent'] = settings.useragent def doget(self, url, **kwargs): try: return self.getter(url, **kwargs) except GetterError as ex: message = L(CANT_CONTACT_PROVIDER) if ex.connection_error else L( HTTP_ERROR).format(ex.message) raise ProviderError(message, ex.cause) def log(self, message, level=xbmc.LOGDEBUG): if self.mediatype: log(message, level, tag='%s:%s' % (self.name.sort, self.mediatype)) else: log(message, level, tag='%s' % self.name.sort) def login(self): return False
class ArtFilesAbstractProvider(object): __metaclass__ = ABCMeta # 13514 = Local art name = SortedDisplay('file:art', 13514) def buildimage(self, url, title, fromartistfolder=False): provider = ARTIST_INFOFOLDER_PROVIDER if fromartistfolder else self.name result = {'url': url, 'provider': provider, 'preview': url} result['title'] = title result['rating'] = SortedDisplay(0, '') result['size'] = SortedDisplay(0, '') result['language'] = 'xx' return result def getextra(self, path, exacttypes, thumbs=False): arttype = 'thumb' if thumbs else 'fanart' extradir = 'extrathumbs' if thumbs else 'extrafanart' sep = get_pathsep(path) missing, nextno = getopentypes(exacttypes, arttype) path += extradir + sep _, files = xbmcvfs.listdir(path) files.sort(key=natural_sort) result = {} for filename in files: check_filename = filename.lower() if not check_filename.endswith(ARTWORK_EXTS): continue popped = missing.pop(0) if missing else None nexttype = popped if popped else format_arttype(arttype, nextno) result[nexttype] = self.buildimage(path + filename, extradir + sep + filename) if not popped: nextno += 1 return result
class VideoFileAbstractProvider(object): __metaclass__ = ABCMeta name = SortedDisplay('video:thumb', VIDEO_FILE) def build_video_thumbnail(self, path): url = build_video_thumbnail_path(path) return {'url': url, 'rating': SortedDisplay(0, ''), 'language': 'xx', 'title': L(VIDEO_FILE_THUMB), 'provider': self.name, 'size': SortedDisplay(0, ''), 'preview': url}
def _get_imagesize(arttype): if arttype in ('hdtvlogo', 'hdclearart', 'hdmovielogo', 'hdmovieclearart', 'hdmusiclogo'): return SortedDisplay(800, 'HD') elif arttype in ('clearlogo', 'clearart', 'movielogo', 'movieart', 'musiclogo'): return SortedDisplay(400, 'SD') elif arttype in ('tvbanner', 'seasonbanner', 'moviebanner', 'musicbanner'): return SortedDisplay(1000, '1000x185') elif arttype in ('showbackground', 'moviebackground', 'artistbackground'): return SortedDisplay(1920, '1920x1080') elif arttype in ('tvposter', 'seasonposter', 'movieposter'): return SortedDisplay(1426, '1000x1426') elif arttype in ('tvthumb', 'seasonthumb'): return SortedDisplay(500, '500x281 or 1000x562') elif arttype == 'characterart': return SortedDisplay(512, '512x512') elif arttype == 'moviethumb': return SortedDisplay(1000, '1000x562') elif arttype in ('moviedisc', 'cdart', 'artistthumb', 'albumcover'): return SortedDisplay(1000, '1000x1000') return SortedDisplay(0, '')
def _build_image(self, url, size, title=None): result = { 'provider': self.name, 'url': url, 'preview': url + '/preview', 'size': size, 'language': None, 'rating': SortedDisplay(5.1 if title == 'track' else 5.0, '') } if title: result['title'] = title return result
def _get_imagesize(arttype): if arttype in ('strTrackThumb', 'strAlbumThumb', 'strArtistThumb', 'strAlbumThumbBack'): return SortedDisplay(500, '500-800') if arttype in ('strAlbumCDart', ): return SortedDisplay(500, '500 or 1000') if arttype in ('strArtistLogo', ): return SortedDisplay(400, '400x155 or 800x310') if arttype in ('strArtistBanner', ): return SortedDisplay(1000, '1000x185') if arttype in ('strArtistClearart', 'strArtistWideThumb'): return SortedDisplay(1000, '1000x562') if arttype in ('strArtistFanart', 'strArtistFanart2', 'strArtistFanart3'): return SortedDisplay(1280, '1280x720 or 1920x1080') if arttype in ('strAlbumSpine', ): return (SortedDisplay(700, '700x35')) return SortedDisplay(0, '')
def _sort_images(basearttype, imagelist, mediasource, mediatype): # 1. Language, preferring fanart with no language/title if configured # 2. Match discart to media source # 3. Preferred source # 4. Size (in 200px groups), up to preferredsize # 5. Rating imagelist.sort(key=lambda image: image['rating'].sort, reverse=True) imagelist.sort(key=_size_sort, reverse=True) if basearttype == 'discart': if mediasource != 'unknown': imagelist.sort(key=lambda image: 0 if image.get( 'subtype', SortedDisplay(None, '')).sort == mediasource else 1) imagelist.sort(key=lambda image: _preferredsource_sort(image, mediatype), reverse=True) imagelist.sort(key=lambda image: _imagelanguage_sort(image, basearttype))
class NFOFileAbstractProvider(object): __metaclass__ = ABCMeta name = SortedDisplay('file:nfo', NFO_FILE) def build_resultimage(self, url, title): if isinstance(url, unicode): url = url.encode('utf-8') if url.startswith('http'): url = urllib.quote(url, safe="%/:=&?~#+!$,;'@()*[]") resultimage = {'url': url, 'provider': self.name, 'preview': url} resultimage['title'] = '<{0}>'.format(title) resultimage['rating'] = SortedDisplay(0, '') resultimage['size'] = SortedDisplay(0, '') resultimage['language'] = 'xx' return resultimage
class TheAudioDBSearch(AbstractProvider): name = SortedDisplay('theaudiodb.com:search', 'TheAudioDB.com search') contenttype = 'application/json' def __init__(self): super(TheAudioDBSearch, self).__init__() # s=[artist], t=[track title] self.url_trackby_artistandtrack = 'https://www.theaudiodb.com/api/v1/json/{0}/searchtrack.php' def get_data(self, url, params=None): apikey = settings.get_apikey('tadb') if not apikey: raise build_key_error('tadb') result = cache.cacheFunction(self._get_data, url.format(settings.get_apikey('tadb')), params) return result if result != 'Empty' else None def _get_data(self, url, params=None): self.log('uncached', xbmc.LOGINFO) if params is None: params = {} response = self.doget(url, params=params) if response is None: raise build_key_error('tadb') return 'Empty' if response is None else response.json() def search(self, query, mediatype): if mediatype != mediatypes.MUSICVIDEO: return [] query = query.split(' - ', 1) if len(query) != 2: return [] data = self.get_data(self.url_trackby_artistandtrack, { 's': query[0], 't': query[1] }) if not data or not data.get('track'): return [] return [{ 'label': item['strArtist'] + ' - ' + item['strTrack'], 'uniqueids': { 'mbtrack': item['strMusicBrainzID'], 'mbartist': item['strMusicBrainzArtistID'], 'mbgroup': item['strMusicBrainzAlbumID'] } } for item in data['track']]
def _get_images(self, data): result = {} for arttype, artlist in data.iteritems(): if arttype not in self.artmap: continue for image in artlist: generaltype = self.artmap[arttype] if generaltype == 'poster' and not _get_imagelanguage(arttype, image): generaltype = 'keyart' if artlist and generaltype not in result: result[generaltype] = [] url = urllib.quote(image['url'], safe="%/:=&?~#+!$,;'@()*[]") resultimage = self.build_image(url, arttype, image) if arttype == 'moviedisc': display = self.disctitles.get(image['disc_type']) or image['disc_type'] resultimage['subtype'] = SortedDisplay(image['disc_type'], display) result[generaltype].append(resultimage) return result
class TheMovieDBSearch(AbstractProvider): name = SortedDisplay('themoviedb.org:search', 'The Movie Database search') contenttype = 'application/json' searchurl = 'https://api.themoviedb.org/3/search/{0}' tvexternalidsurl = 'https://api.themoviedb.org/3/tv/{0}/external_ids' typemap = {mediatypes.MOVIESET: 'collection'} def get_data(self, url, params=None): result = cache.cacheFunction(self._get_data, url, params) return result if result != 'Empty' else None def _get_data(self, url, params=None): apikey = settings.get_apikey('tmdb') if not apikey: raise build_key_error('tmdb') self.log('uncached', xbmc.LOGINFO) if params is None: params = {'api_key': apikey} else: params = dict(params, api_key=apikey) response = self.doget(url, params=params) return 'Empty' if response is None else response.json() def login(self): raise build_key_error('tmdb') def search(self, query, mediatype): if mediatype not in self.typemap: return [] url = self.searchurl.format(self.typemap[mediatype]) data = self.get_data(url, {'query': query}) if not data or 'results' not in data: return [] return [{'label': item['name'], 'uniqueids': {'tmdb': item['id']}} for item in data['results']] def get_more_uniqueids(self, uniqueids, mediatype): if mediatype != mediatypes.TVSHOW or 'tvdb' in uniqueids: return {} mediaid = get_mediaid(uniqueids) url = self.tvexternalidsurl.format(mediaid) data = self.get_data(url) return {} if not data or not data.get('tvdb_id') else {'tvdb': data['tvdb_id']}
def process_data(self, data): result = {} for arttype, artlist in data.iteritems(): if arttype not in self.artmap: continue previewbit = 'w300' if arttype in ('backdrops', 'stills') else 'w342' for image in artlist: resultimage = {'url': self.baseurl + 'original' + image['file_path'], 'provider': self.name} resultimage['preview'] = self.baseurl + previewbit + image['file_path'] resultimage['language'] = image['iso_639_1'] if image['iso_639_1'] != 'xx' else None resultimage['rating'] = self._get_rating(image) sortsize = image['width' if arttype != 'posters' else 'height'] resultimage['size'] = SortedDisplay(sortsize, '{0}x{1}'.format(image['width'], image['height'])) generaltype = self.artmap[arttype] if settings.use_tmdb_keyart and generaltype == 'poster' and not resultimage['language']: generaltype = 'keyart' if generaltype not in result: result[generaltype] = [] result[generaltype].append(resultimage) return result
def tag_forcedandexisting_art(availableart, forcedart, existingart): typeinsert = {} for exacttype, artlist in sorted( forcedart.iteritems(), key=lambda arttype: natural_sort(arttype[0])): arttype = info.get_basetype(exacttype) if arttype not in availableart: availableart[arttype] = artlist else: for image in artlist: match = next((available for available in availableart[arttype] if available['url'] == image['url']), None) if match: if 'title' in image and 'title' not in match: match['title'] = image['title'] match['second provider'] = image['provider'].display else: typeinsert[arttype] = typeinsert[ arttype] + 1 if arttype in typeinsert else 0 availableart[arttype].insert(typeinsert[arttype], image) typeinsert = {} for exacttype, existingurl in existingart.iteritems(): arttype = info.get_basetype(exacttype) if arttype in availableart: match = next((available for available in availableart[arttype] if available['url'] == existingurl), None) if match: match['preview'] = existingurl match['existing'] = True else: typeinsert[arttype] = typeinsert[ arttype] + 1 if arttype in typeinsert else 0 image = { 'url': existingurl, 'preview': existingurl, 'title': exacttype, 'existing': True, 'provider': SortedDisplay('current', L(CURRENT_ART)) } availableart[arttype].insert(typeinsert[arttype], image)
def get_images(self, uniqueids, types=None): if not settings.get_apienabled('tvdb'): return {} if types is not None and not self.provides(types): return {} mediaid = get_mediaid(uniqueids) if not mediaid: return {} result = {} languages = base.languages # Useful fanart can be hidden by the language filter, try a few of the most frequently used flanguages = ['en', 'de', 'fr', 'es', 'ru'] flanguages.extend(lang for lang in languages if lang not in flanguages) for arttype in self.artmap: if types and not typematches(self.artmap[arttype], types): continue arttype_error = False for language in languages if arttype != 'fanart' else flanguages: generaltype = self.artmap[arttype] data = self.get_data(mediaid, arttype, language) if not data: continue isseason = arttype.startswith('season') if not isseason: if generaltype not in result: result[generaltype] = [] for image in data['data']: if not image.get('fileName' ) or isseason and not image.get('subKey'): continue ntype = generaltype if isseason: try: int(image['subKey']) except ValueError: if arttype_error: continue arttype_error = True self.log( "Provider returned unexpected content and '{0}' " .format(arttype) + "artwork could not be processed:\n" + "expected a season number but got '{0}'". format(image['subKey']), xbmc.LOGWARNING) continue ntype = ntype % image['subKey'] if ntype not in result: result[ntype] = [] # skip duplicates if any(x for x in result[ntype] if x['url'].endswith(image['fileName'])): continue resultimage = {'provider': self.name} resultimage['url'] = self.imageurl_base + image['fileName'] resultimage['preview'] = self.imageurl_base + ( image['thumbnail'] or '_cache/' + image['fileName']) resultimage[ 'language'] = language if shouldset_imagelanguage( image) else None resultimage['rating'] = self._get_rating(image) if arttype in ('series', 'seasonwide'): resultimage['size'] = SortedDisplay(758, '758x140') elif arttype == 'season': resultimage['size'] = SortedDisplay(1000, '680x1000') else: resultimage['size'] = parse_sortsize(image, arttype) result[ntype].append(resultimage) return result
class TheAudioDBAbstractProvider(AbstractImageProvider): name = SortedDisplay('theaudiodb.com', 'TheAudioDB.com') contenttype = 'application/json' def __init__(self): super(TheAudioDBAbstractProvider, self).__init__() # url param i=MB track/album/artist ID self.artmap = { 'mbtrack': { 'datakey': 'track', 'artmap': { 'strTrackThumb': 'thumb' }, 'url': 'https://www.theaudiodb.com/api/v1/json/{0}/track-mb.php' }, 'mbgroup': { 'datakey': 'album', 'artmap': { 'strAlbumThumb': 'thumb', 'strAlbumCDart': 'discart', 'strAlbumThumbBack': 'back', 'strAlbumSpine': 'spine' }, 'url': 'https://www.theaudiodb.com/api/v1/json/{0}/album-mb.php' }, 'mbartist': { 'datakey': 'artists', 'artmap': { 'strArtistThumb': 'thumb', 'strArtistLogo': 'clearlogo', 'strArtistBanner': 'banner', 'strArtistFanart': 'fanart', 'strArtistFanart2': 'fanart', 'strArtistFanart3': 'fanart', 'strArtistClearart': 'clearart', 'strArtistWideThumb': 'landscape' }, 'url': 'https://www.theaudiodb.com/api/v1/json/{0}/artist-mb.php' } } self.provtypes = set(x for data in self.artmap.values() for x in data['artmap'].values()) def get_data(self, url, params): result = cache.cacheFunction(self._get_data, url.format(settings.get_apikey('tadb')), params) return result if result != 'Empty' else None def _get_data(self, url, params): apikey = settings.get_apikey('tadb') if not apikey: raise build_key_error('tadb') self.log('uncached', xbmc.LOGINFO) response = self.doget(url, params=params) if response is None: raise build_key_error('tadb') return 'Empty' if response is None else json.loads(response.text, cls=UTF8JSONDecoder) def _build_image(self, url, size, title=None): result = { 'provider': self.name, 'url': url, 'preview': url + '/preview', 'size': size, 'language': None, 'rating': SortedDisplay(5.1 if title == 'track' else 5.0, '') } if title: result['title'] = title return result
class KyraDBMovieProvider(AbstractImageProvider): name = SortedDisplay('kyradb.com', 'KyraDB.com') contenttype = 'application/json' mediatype = mediatypes.MOVIE apiurl = 'https://www.kyradb.com/api10/movie/{0}/images/{1}' animatedtypes = ('animatedposter', 'animatedkeyart', 'animatedfanart') provtypes = animatedtypes + ('characterart', ) def provides(self, types): return any(x in self.provtypes for x in types) def get_images(self, uniqueids, types=None): if not settings.get_apienabled('kyradb'): return {} mediaid = get_mediaid(uniqueids) if not mediaid: return {} if types is None: types = self.provtypes result = {} if any(x in self.animatedtypes for x in types): result.update(self._get_animated_images(mediaid)) if 'characterart' in types: characterart = self._get_characterart(mediaid) if characterart: result['characterart'] = characterart return result def _get_animated_images(self, mediaid): data = self.get_data(mediaid, 'animated') if not data: return {} baseposter = data['base_url_posters'] basefanart = data['base_url_backgrounds'] result = {} for poster in data['posters']: newposter = { 'url': baseposter + '/' + poster['name'], 'language': get_language(poster.get('language', None)), 'rating': SortedDisplay(poster['likes'], '{0} likes'.format(poster['likes'])), 'size': SortedDisplay(poster['height'], poster['resolution']), 'provider': self.name, 'preview': baseposter + '/' + poster['name'] } arttype = 'animatedposter' if newposter[ 'language'] else 'animatedkeyart' if arttype not in result: result[arttype] = [] result[arttype].append(newposter) for fanart in data['backgrounds']: if 'animatedfanart' not in result: result['animatedfanart'] = [] result['animatedfanart'].append({ 'url': basefanart + '/' + fanart['name'], 'language': None, 'rating': SortedDisplay(fanart['likes'], '{0} likes'.format(fanart['likes'])), 'size': SortedDisplay(fanart['height'], fanart['resolution']), 'provider': self.name, 'preview': basefanart + '/' + fanart['name'] }) return result def _get_characterart(self, mediaid): data = self.get_data(mediaid, 'characterart') if not data: return {} baseurl = data['base_url_character_art'] result = [] for characterart in data['character_art']: result.append({ 'url': baseurl + '/' + characterart['name'], 'language': None, 'rating': SortedDisplay(characterart['likes'], '{0} likes'.format(characterart['likes'])), 'size': SortedDisplay(characterart['height'], characterart['resolution']), 'provider': self.name, 'preview': baseurl + '/' + characterart['name'] }) return result def get_data(self, mediaid, urltype): result = cache.cacheFunction(self._get_data, mediaid, urltype) return result if result != 'Empty' else None def _get_data(self, mediaid, urltype): if not settings.kyradb_user_apikey or not settings.kyradb_userkey: raise ProviderError( "KyraDB API key and User key is required for artwork from KyraDB: " + str(self.provtypes)) headers = { 'Apikey': settings.kyradb_user_apikey, 'Userkey': settings.kyradb_userkey } self.log('uncached', xbmc.LOGINFO) response = self.doget(self.apiurl.format(mediaid, urltype), headers=headers) result = json.loads( response.text, cls=UTF8JSONDecoder) if response is not None else None if result and result.get('error'): if result['error'] != 4: # 4: "No results" raise ProviderError(result['message']) result = None return 'Empty' if result is None else result
class TheTVDBProvider(AbstractImageProvider): name = SortedDisplay('thetvdb.com', 'TheTVDB.com') mediatype = mediatypes.TVSHOW contenttype = 'application/vnd.thetvdb.v2.1.0' apiurl = 'https://api.thetvdb.com/series/%s/images/query' loginurl = 'https://api.thetvdb.com/login' imageurl_base = 'https://www.thetvdb.com/banners/' artmap = { 'fanart': 'fanart', 'poster': 'poster', 'season': mediatypes.SEASON + '.%s.poster', 'seasonwide': mediatypes.SEASON + '.%s.banner', 'series': 'banner' } def get_data(self, mediaid, arttype, language): result = cache.cacheFunction(self._get_data, mediaid, arttype, language) return result if result != 'Empty' else None def _get_data(self, mediaid, arttype, language): if not settings.get_apikey('tvdb'): raise build_key_error('tvdb') self.log('uncached', xbmc.LOGINFO) getparams = { 'params': { 'keyType': arttype }, 'headers': { 'Accept-Language': language } } response = self.doget(self.apiurl % mediaid, **getparams) return 'Empty' if response is None else json.loads(response.text, cls=UTF8JSONDecoder) def _get_rating(self, image): if image['ratingsInfo']['count']: info = image['ratingsInfo'] rating = info['average'] if info['count'] < 5: # Reweigh ratings, decrease difference from 5 rating = 5 + (rating - 5) * sin(info['count'] / pi) return SortedDisplay(rating, '{0:.1f} stars'.format(info['average'])) else: return SortedDisplay(5, 'Not rated') def get_images(self, uniqueids, types=None): if not settings.get_apienabled('tvdb'): return {} if types is not None and not self.provides(types): return {} mediaid = get_mediaid(uniqueids) if not mediaid: return {} result = {} languages = base.languages # Useful fanart can be hidden by the language filter, try a few of the most frequently used flanguages = ['en', 'de', 'fr', 'es', 'ru'] flanguages.extend(lang for lang in languages if lang not in flanguages) for arttype in self.artmap: if types and not typematches(self.artmap[arttype], types): continue arttype_error = False for language in languages if arttype != 'fanart' else flanguages: generaltype = self.artmap[arttype] data = self.get_data(mediaid, arttype, language) if not data: continue isseason = arttype.startswith('season') if not isseason: if generaltype not in result: result[generaltype] = [] for image in data['data']: if not image.get('fileName' ) or isseason and not image.get('subKey'): continue ntype = generaltype if isseason: try: int(image['subKey']) except ValueError: if arttype_error: continue arttype_error = True self.log( "Provider returned unexpected content and '{0}' " .format(arttype) + "artwork could not be processed:\n" + "expected a season number but got '{0}'". format(image['subKey']), xbmc.LOGWARNING) continue ntype = ntype % image['subKey'] if ntype not in result: result[ntype] = [] # skip duplicates if any(x for x in result[ntype] if x['url'].endswith(image['fileName'])): continue resultimage = {'provider': self.name} resultimage['url'] = self.imageurl_base + image['fileName'] resultimage['preview'] = self.imageurl_base + ( image['thumbnail'] or '_cache/' + image['fileName']) resultimage[ 'language'] = language if shouldset_imagelanguage( image) else None resultimage['rating'] = self._get_rating(image) if arttype in ('series', 'seasonwide'): resultimage['size'] = SortedDisplay(758, '758x140') elif arttype == 'season': resultimage['size'] = SortedDisplay(1000, '680x1000') else: resultimage['size'] = parse_sortsize(image, arttype) result[ntype].append(resultimage) return result def login(self): response = self.getter.session.post( self.loginurl, json={'apikey': settings.get_apikey('tvdb')}, headers={ 'Content-Type': 'application/json', 'User-Agent': settings.useragent }, timeout=15) if response is not None and response.status_code == 401: raise build_key_error('tvdb') response.raise_for_status() if not response or not response.headers['Content-Type'].startswith( 'application/json'): raise ProviderError("Provider returned unexpected content") self.getter.session.headers[ 'authorization'] = 'Bearer %s' % response.json()['token'] return True def provides(self, types): types = set( x if not x.startswith('season.') else re.sub(r'[\d]', '%s', x) for x in types) return any(x in types for x in self.artmap.values())
class TheMovieDBAbstractProvider(AbstractImageProvider): __metaclass__ = ABCMeta contenttype = 'application/json' name = SortedDisplay('themoviedb.org', 'The Movie Database') _baseurl = None artmap = {} @property def baseurl(self): if not self._baseurl: apikey = settings.get_apikey('tmdb') if not apikey: raise build_key_error('tmdb') response = self.doget(cfgurl, params={'api_key': apikey}) if response is None: return self._baseurl = response.json()['images']['secure_base_url'] return self._baseurl def _get_rating(self, image): if image['vote_count']: # Reweigh ratings, increase difference from 5 rating = image['vote_average'] rating = 5 + (rating - 5) * 2 return SortedDisplay(rating, '{0:.1f} stars'.format(image['vote_average'])) else: return SortedDisplay(5, 'Not rated') def get_data(self, url): result = cache.cacheFunction(self._get_data, url) return result if result != 'Empty' else None def _get_data(self, url): apikey = settings.get_apikey('tmdb') if not apikey: raise build_key_error('tmdb') self.log('uncached', xbmc.LOGINFO) response = self.doget(url, params={'api_key': apikey}) return 'Empty' if response is None else json.loads(response.text, cls=UTF8JSONDecoder) def login(self): raise build_key_error('tmdb') def process_data(self, data): result = {} for arttype, artlist in data.iteritems(): if arttype not in self.artmap: continue previewbit = 'w300' if arttype in ('backdrops', 'stills') else 'w342' for image in artlist: resultimage = {'url': self.baseurl + 'original' + image['file_path'], 'provider': self.name} resultimage['preview'] = self.baseurl + previewbit + image['file_path'] resultimage['language'] = image['iso_639_1'] if image['iso_639_1'] != 'xx' else None resultimage['rating'] = self._get_rating(image) sortsize = image['width' if arttype != 'posters' else 'height'] resultimage['size'] = SortedDisplay(sortsize, '{0}x{1}'.format(image['width'], image['height'])) generaltype = self.artmap[arttype] if settings.use_tmdb_keyart and generaltype == 'poster' and not resultimage['language']: generaltype = 'keyart' if generaltype not in result: result[generaltype] = [] result[generaltype].append(resultimage) return result def provides(self, types): return any(x in types for x in self.artmap.values())
def build_video_thumbnail(self, path): url = build_video_thumbnail_path(path) return {'url': url, 'rating': SortedDisplay(0, ''), 'language': 'xx', 'title': L(VIDEO_FILE_THUMB), 'provider': self.name, 'size': SortedDisplay(0, ''), 'preview': url}
class FanartTVAbstractProvider(AbstractImageProvider): __metaclass__ = ABCMeta api_section = None mediatype = None contenttype = 'application/json' name = SortedDisplay('fanart.tv', 'fanart.tv') apiurl = 'https://webservice.fanart.tv/v3/%s/%s' def get_images(self, uniqueids, types=None): if not settings.get_apienabled('fanarttv'): return {} if types is not None and not self.provides(types): return {} mediaid = get_mediaid(uniqueids, self.mediatype) if not mediaid or isinstance(mediaid, tuple) and not mediaid[0]: return {} data = self.get_data(mediaid[0] if isinstance(mediaid, tuple) else mediaid) if not data: return {} if self.mediatype == mediatypes.MUSICVIDEO: return self._get_images(data, mediaid) elif self.mediatype == mediatypes.ALBUM: if not data.get('albums', {}).get(mediaid): return {} return self._get_images(data['albums'][mediaid]) else: return self._get_images(data) def get_data(self, mediaid): result = cache.cacheFunction(self._get_data, mediaid) return result if result != 'Empty' else None def _get_data(self, mediaid): apikey = settings.get_apikey('fanarttv') if not apikey: raise build_key_error('fanarttv') self.log('uncached', xbmc.LOGINFO) headers = {'api-key': apikey} if settings.fanarttv_clientkey: headers['client-key'] = settings.fanarttv_clientkey response = self.doget(self.apiurl % (self.api_section, mediaid), headers=headers) return 'Empty' if response is None else json.loads(response.text, cls=UTF8JSONDecoder) def login(self): raise build_key_error('fanarttv') def build_image(self, url, arttype, image, likediv=5.0): result = {'url': url, 'provider': self.name} result['preview'] = url.replace('.fanart.tv/fanart/', '.fanart.tv/preview/') result['rating'] = SortedDisplay(5.25 + int(image['likes']) / float(likediv), '{0} likes'.format(image['likes'])) result['size'] = _get_imagesize(arttype) result['language'] = _get_imagelanguage(arttype, image) return result @abstractmethod def _get_images(self, data, mediaid=None): pass @abstractmethod def provides(self, types): pass
import os import xbmcvfs from abc import ABCMeta from lib.libs import mediatypes from lib.libs.addonsettings import settings from lib.libs.mediainfo import arttype_matches_base, format_arttype, find_central_infodir from lib.libs.utils import SortedDisplay, natural_sort, get_movie_path_list, get_pathsep, \ iter_possible_cleannames, parent_dir ARTWORK_EXTS = ('.jpg', '.png', '.gif') ARTIST_INFOFOLDER_PROVIDER = SortedDisplay('file:art', 20223) ARTTYPE_MAXLENGTH = 30 class ArtFilesAbstractProvider(object): __metaclass__ = ABCMeta # 13514 = Local art name = SortedDisplay('file:art', 13514) def buildimage(self, url, title, fromartistfolder=False): provider = ARTIST_INFOFOLDER_PROVIDER if fromartistfolder else self.name result = {'url': url, 'provider': provider, 'preview': url} result['title'] = title result['rating'] = SortedDisplay(0, '') result['size'] = SortedDisplay(0, '') result['language'] = 'xx' return result def getextra(self, path, exacttypes, thumbs=False):