From 5ce817cee644c43b9b5bfd6b6296f8bfc115e0fc Mon Sep 17 00:00:00 2001 From: Dan Boehm Date: Thu, 24 Apr 2014 15:00:04 -0500 Subject: [PATCH 1/2] Support for downloading extra artwork from Fanart.tv (resolves #1023). New image types include: * clearart * discart * extrathumbs * extrafanart * logo * banner * landscape (16:9 Thumb) There are a couple things that should be noted: 1. Only English images will be downloaded. 2. The fanart image is now downloaded from Fanart.tv if it can find one, otherwise it uses TMDB like it used to. This is because the images on Fanart.tv tend to be higher resolutions & quality. 3. Since multiple extrathumbs and extrafanarts are downloaded into a subdirectory, subdirectories are now supported for metadata file names. The subdirectories will be automatically created if they don't exist. 4. Bluray discart will always be preferred over DVD. Ideally, it would prefer DVD versions for SD quality movies, but I couldn't find an easy way to determine the quality from within the plugin. I suspect major changes would be needed to the plugin system in general in order to get this to work. If a user cares about the distinction, the best work-around is to not download these in Couchpotato and run the Artwork Downloader addon from within XBMC. 5. A maximum of 4 extrathumbs and 20 extrafanarts will be downloaded. Squashed commit of the following: commit b113a4def197a9ca8545bde9f5081c0591b93b36 Author: Dan Boehm Date: Thu Apr 24 14:24:12 2014 -0500 Bug-fix and code cleanup. Fixed a bug where the movie.info event would crash if there aren't any pictures to scrape in fanart.tv. commit adf7a4675d472e9e95a316c6cccc681a52804f13 Author: Dan Boehm Date: Wed Apr 23 16:15:03 2014 -0500 Added support for extrafanart. Also, the main fanart will be taken from fanart.tv unless one does not exist. commit 1791e46c8602f40bb56fe0cf7ecb0607f35b4b12 Author: Dan Boehm Date: Wed Apr 23 15:13:14 2014 -0500 Couchpotato now downloads extrathumbs from the extra tmdb backdrops if they exist. This commit made some major changes to the core image creation functionality that makes writing multiple images to folders possible. commit c0858807873749dbc928c0260037138f51f894ca Author: Dan Boehm Date: Wed Apr 23 12:18:53 2014 -0500 Bug Fix & Implemented functionality to select bluray or dvd disc images. Currently, only blurays will be selected, unless there are no blurays. However, if a mechanism for determining the quality of the release is implemented, it would be simple to make this selection based on the quality. commit 786751371d243f53d0f5c6f2c38d92288d8608ba Author: Dan Boehm Date: Wed Apr 23 10:59:25 2014 -0500 Fixed a bug where non-HD clearart and logos couldn't be downloaded. commit feda8df483d13b5a5df3a869f25de8f2c7e6ffe3 Author: Dan Boehm Date: Wed Apr 23 10:12:31 2014 -0500 Fixed some problems that were missed with the previous merge. commit 5ddab6c40e69a5accc6c0336cd7485920ff82d8f Merge: 7273abf ff46aa0 Author: Dan Boehm Date: Wed Apr 23 10:02:11 2014 -0500 Merge branch 'develop' into fanarttv Conflicts: couchpotato/core/media/movie/providers/info/themoviedb.py couchpotato/core/providers/metadata/xbmc/__init__.py commit 7273abf827735cf245711c3d3199a6a173a964aa Author: dan Date: Thu Feb 27 13:29:57 2014 -0600 Downloads extra artwork from fanart.tv Downloads occur with correct filenaming when XBMC metadata is generated, but the image URLs are selected when the movie.info event is called. commit 9080d9d749c7e1ddbdc78f7b37a3c5f83195d580 Author: dan Date: Wed Feb 26 16:31:37 2014 -0600 Added basic functionality for fanarttv provider. This should be mostly done and is based on the tvdb provider. commit 1b39b246c2a9d65f9ef86c4e150a12d893e362c0 Author: dan Date: Wed Feb 26 14:50:17 2014 -0600 Updated fanarttv library with the correct package hierarchy (libs.fanarttv). commit 8abb7c8f8ad3347900debb9f6a6d5a7acb7df396 Author: dan Date: Wed Feb 26 13:12:48 2014 -0600 Added fanart.tv API python library (lib.fanarttv). The upstream for this library is at https://github.com/z4r/python-fanart. --- .../core/media/movie/providers/info/_modifier.py | 9 +- .../core/media/movie/providers/info/fanarttv.py | 168 +++++++++++++++++++++ .../core/media/movie/providers/info/themoviedb.py | 36 ++++- .../core/media/movie/providers/metadata/base.py | 152 ++++++++++++++----- .../core/media/movie/providers/metadata/xbmc.py | 145 +++++++++++++++++- libs/fanarttv/__init__.py | 110 ++++++++++++++ libs/fanarttv/core.py | 44 ++++++ libs/fanarttv/errors.py | 15 ++ libs/fanarttv/immutable.py | 46 ++++++ libs/fanarttv/items.py | 68 +++++++++ libs/fanarttv/movie.py | 103 +++++++++++++ libs/fanarttv/music.py | 80 ++++++++++ libs/fanarttv/tv.py | 108 +++++++++++++ 13 files changed, 1038 insertions(+), 46 deletions(-) create mode 100644 couchpotato/core/media/movie/providers/info/fanarttv.py create mode 100644 libs/fanarttv/__init__.py create mode 100644 libs/fanarttv/core.py create mode 100644 libs/fanarttv/errors.py create mode 100644 libs/fanarttv/immutable.py create mode 100644 libs/fanarttv/items.py create mode 100644 libs/fanarttv/movie.py create mode 100644 libs/fanarttv/music.py create mode 100644 libs/fanarttv/tv.py diff --git a/couchpotato/core/media/movie/providers/info/_modifier.py b/couchpotato/core/media/movie/providers/info/_modifier.py index bf25727..beb29e7 100644 --- a/couchpotato/core/media/movie/providers/info/_modifier.py +++ b/couchpotato/core/media/movie/providers/info/_modifier.py @@ -26,7 +26,14 @@ class MovieResultModifier(Plugin): 'backdrop': [], 'poster_original': [], 'backdrop_original': [], - 'actors': {} + 'actors': {}, + 'landscape': [], + 'logo': [], + 'clearart': [], + 'discart': [], + 'banner': [], + 'extrathumbs': [], + 'extrafanart': [] }, 'runtime': 0, 'plot': '', diff --git a/couchpotato/core/media/movie/providers/info/fanarttv.py b/couchpotato/core/media/movie/providers/info/fanarttv.py new file mode 100644 index 0000000..07d1a00 --- /dev/null +++ b/couchpotato/core/media/movie/providers/info/fanarttv.py @@ -0,0 +1,168 @@ +import os +import traceback + +from couchpotato.core.event import addEvent +from couchpotato.core.logger import CPLog +from couchpotato.core.media.movie.providers.base import MovieProvider +from couchpotato.core.plugins.quality import QualityPlugin + +from libs.fanarttv.movie import Movie +import libs.fanarttv.errors as fanarttv_errors + + +log = CPLog(__name__) + +autoload = 'FanartTV' + + +class FanartTV(MovieProvider): + MAX_EXTRAFANART = 20 + + def __init__(self): + addEvent('movie.extraart', self.getArt, priority=2) + + # Configure fanarttv API settings + os.environ.setdefault('FANART_APIKEY', self.conf('api_key')) + + def getArt(self, identifier): + # FIXME: I believe I should be registering a cache here... I need to look into that. + log.debug("Getting Extra Artwork from Fanart.tv...") + if not identifier: + return {} + + images = {} + + try: + try: + exists = True + movie = Movie.get(id=identifier) + except (fanarttv_errors.FanartError, IOError): + exists = False + + if exists: + images = self._parseMovie(movie, True) + + except: + log.error('Failed getting extra art for %s: %s', + (identifier, traceback.format_exc())) + return {} + + return images + + def _parseMovie(self, movie, isHD): + images = { + 'landscape': [], + 'logo': [], + 'discart': [], + 'clearart': [], + 'banner': [], + 'extrafanart': [] + } + + images['landscape'] = self._getMultImages(movie.thumbs, 1) + images['banner'] = self._getMultImages(movie.banners, 1) + images['discart'] = self._getMultImages(self._trimDiscs(movie.discs, isHD), 1) + + images['clearart'] = self._getMultImages(movie.hdarts, 1) + if len(images['clearart']) is 0: + images['clearart'] = self._getMultImages(movie.arts, 1) + + images['logo'] = self._getMultImages(movie.hdlogos, 1) + if len(images['logo']) is 0: + images['logo'] = self._getMultImages(movie.logos, 1) + + fanarts = self._getMultImages(movie.backgrounds, self.MAX_EXTRAFANART + 1) + + if fanarts: + images['backdrop_original'] = fanarts[0] + images['extrafanart'] = fanarts[1:] + + # TODO: Add support for extra backgrounds + #extraFanart = self._getMultImages(movie.backgrounds, -1) + + return images + + def _trimDiscs(self, discImages, isHD): + ''' + Return a subset of discImages based on isHD. If isHD is true, only + bluray disc images will be returned. If isHD is false, only dvd disc + images will be returned. If the resulting list would be an empty list, + then the original list is returned instead. + ''' + trimmed = [] + for disc in discImages: + if isHD and disc.disc_type == u'bluray': + trimmed.append(disc) + elif not isHD and disc.disc_type == u'dvd': + trimmed.append(disc) + + if len(trimmed) is 0: + return discImages + else: + return trimmed + + def _getImage(self, images): + image_url = None + highscore = -1 + for image in images: + if image.likes > highscore: + highscore = image.likes + image_url = image.url + + return image_url + + def _getMultImages(self, images, n): + ''' + Chooses the best n images and returns them as a list. + If n<0, all images will be returned. + ''' + image_urls = [] + pool = [] + for image in images: + if image.lang == u'en': + pool.append(image) + origPoolSize = len(pool) + + while len(pool) > 0 and (n < 0 or origPoolSize - len(pool) < n): + best = None + highscore = -1 + for image in pool: + if image.likes > highscore: + highscore = image.likes + best = image + image_urls.append(best.url) + pool.remove(best) + + return image_urls + + def isDisabled(self): + if self.conf('api_key') == '': + log.error('No API key provided.') + return True + return False + + def _determineHD(self, quality): + for qualityDef in QualityPlugin.qualities: + if quality == qualityDef.get('identifier'): + return bool(qualityDef.get('hd')) + return False + +config = [{ + 'name': 'fanarttv', + 'groups': [ + { + 'tab': 'providers', + 'name': 'fanarttv', + 'label': 'fanart.tv', + 'hidden': True, + 'description': 'Used for all calls to fanart.tv.', + 'options': [ + { + 'name': 'api_key', + 'default': 'd788b4822b9e1f44068026e05557e5d9', + 'label': 'API Key', + }, + ], + }, + ], +}] diff --git a/couchpotato/core/media/movie/providers/info/themoviedb.py b/couchpotato/core/media/movie/providers/info/themoviedb.py index b48822c..7ffba38 100644 --- a/couchpotato/core/media/movie/providers/info/themoviedb.py +++ b/couchpotato/core/media/movie/providers/info/themoviedb.py @@ -1,6 +1,6 @@ import traceback -from couchpotato.core.event import addEvent +from couchpotato.core.event import addEvent, fireEvent from couchpotato.core.helpers.encoding import simplifyString, toUnicode, ss from couchpotato.core.helpers.variable import tryInt from couchpotato.core.logger import CPLog @@ -13,6 +13,7 @@ autoload = 'TheMovieDb' class TheMovieDb(MovieProvider): + MAX_EXTRATHUMBS = 4 def __init__(self): addEvent('movie.info', self.getInfo, priority = 2) @@ -87,6 +88,13 @@ class TheMovieDb(MovieProvider): except: log.error('Failed getting info for %s: %s', (identifier, traceback.format_exc())) + # Get extra artwork via Fanart.TV and merge into images dict + try: + extraArt = fireEvent('movie.extraart', identifier)[0] + result['images'] = dict(result['images'].items() + extraArt.items()) + except IndexError: + pass + return result def parseMovie(self, movie, extended = True): @@ -100,13 +108,15 @@ class TheMovieDb(MovieProvider): poster = self.getImage(movie, type = 'poster', size = 'poster') poster_original = self.getImage(movie, type = 'poster', size = 'original') backdrop_original = self.getImage(movie, type = 'backdrop', size = 'original') + extrathumbs = self.getMultImages(movie, type='backdrops', size='original', n=self.MAX_EXTRATHUMBS, skipfirst=True) images = { 'poster': [poster] if poster else [], #'backdrop': [backdrop] if backdrop else [], 'poster_original': [poster_original] if poster_original else [], 'backdrop_original': [backdrop_original] if backdrop_original else [], - 'actors': {} + 'actors': {}, + 'extrathumbs': extrathumbs } # Genres @@ -171,6 +181,28 @@ class TheMovieDb(MovieProvider): log.debug('Failed getting %s.%s for "%s"', (type, size, ss(str(movie)))) return image_url + + def getMultImages(self, movie, type='backdrops', size='original', n=-1, skipfirst=False): + ''' + If n < 0, return all images. Otherwise return n images. + If n > len(getattr(movie, type)), then return all images. + If skipfirst is True, then it will skip getattr(movie, type)[0]. This + is because backdrops[0] is typically backdrop. + ''' + image_urls = [] + try: + images = getattr(movie, type) + if n < 0 or n > len(images): + numImages = len(images) + else: + numImages = n + for i in range(int(skipfirst), numImages + int(skipfirst)): + image_urls.append(images[i].geturl(size=size)) + + except: + log.debug('Failed getting %i %s.%s for "%s"', (n, type, size, ss(str(movie)))) + + return image_urls def isDisabled(self): if self.conf('api_key') == '': diff --git a/couchpotato/core/media/movie/providers/metadata/base.py b/couchpotato/core/media/movie/providers/metadata/base.py index b9fc5d7..ec91b06 100644 --- a/couchpotato/core/media/movie/providers/metadata/base.py +++ b/couchpotato/core/media/movie/providers/metadata/base.py @@ -37,76 +37,150 @@ class MovieMetaData(MetaDataBase): root = os.path.dirname(root_name) movie_info = group['media'].get('info') - - for file_type in ['nfo', 'thumbnail', 'fanart']: + + for file_type in ['nfo']: try: - # Get file path - name = getattr(self, 'get' + file_type.capitalize() + 'Name')(meta_name, root) - - if name and (self.conf('meta_' + file_type) or self.conf('meta_' + file_type) is None): - - # Get file content - content = getattr(self, 'get' + file_type.capitalize())(movie_info = movie_info, data = group) - if content: - log.debug('Creating %s file: %s', (file_type, name)) - if os.path.isfile(content): - content = sp(content) - name = sp(name) - - shutil.copy2(content, name) - shutil.copyfile(content, name) - - # Try and copy stats seperately - try: shutil.copystat(content, name) - except: pass - else: - self.createFile(name, content) - group['renamed_files'].append(name) - - try: - os.chmod(sp(name), Env.getPermission('file')) - except: - log.debug('Failed setting permissions for %s: %s', (name, traceback.format_exc())) + self._createType(meta_name, root, movie_info, group, file_type, 0) + except: + log.error('Unable to create %s file: %s', ('nfo', traceback.format_exc())) + for file_type in ['thumbnail', 'fanart', 'banner', 'discart', 'logo', 'clearart', 'landscape', 'extrathumbs', 'extrafanart']: + try: + if file_type == 'thumbnail': + numImages = len(movie_info['images']['poster_original']) + elif file_type == 'fanart': + numImages = len(movie_info['images']['backdrop_original']) + else: + numImages = len(movie_info['images'][file_type]) + + for i in range(numImages): + self._createType(meta_name, root, movie_info, group, file_type, i) except: log.error('Unable to create %s file: %s', (file_type, traceback.format_exc())) + def _createType(self, meta_name, root, movie_info, group, file_type, i):# Get file path + name = getattr(self, 'get' + file_type.capitalize() + 'Name')(meta_name, root, i) + + if name and (self.conf('meta_' + file_type) or self.conf('meta_' + file_type) is None): + + # Get file content + content = getattr(self, 'get' + file_type.capitalize())(movie_info=movie_info, data=group, i=i) + if content: + log.debug('Creating %s file: %s', (file_type, name)) + if os.path.isfile(content): + content = sp(content) + name = sp(name) + + if not os.path.exists(os.path.dirname(name)): + os.makedirs(os.path.dirname(name)) + + shutil.copy2(content, name) + shutil.copyfile(content, name) + + # Try and copy stats seperately + try: shutil.copystat(content, name) + except: pass + else: + self.createFile(name, content) + group['renamed_files'].append(name) + + try: + os.chmod(sp(name), Env.getPermission('file')) + except: + log.debug('Failed setting permissions for %s: %s', (name, traceback.format_exc())) + def getRootName(self, data = None): if not data: data = {} return os.path.join(data['destination_dir'], data['filename']) - def getFanartName(self, name, root): + def getFanartName(self, name, root, i): + return + + def getThumbnailName(self, name, root, i): + return + + def getBannerName(self, name, root, i): + return + + def getClearartName(self, name, root, i): + return + + def getLogoName(self, name, root, i): + return + + def getDiscartName(self, name, root, i): + return + + def getLandscapeName(self, name, root, i): + return + + def getExtrathumbsName(self, name, root, i): return - def getThumbnailName(self, name, root): + def getExtrafanartName(self, name, root, i): return - def getNfoName(self, name, root): + def getNfoName(self, name, root, i): return - def getNfo(self, movie_info = None, data = None): + def getNfo(self, movie_info=None, data=None, i=0): if not data: data = {} if not movie_info: movie_info = {} - def getThumbnail(self, movie_info = None, data = None, wanted_file_type = 'poster_original'): + def getThumbnail(self, movie_info = None, data = None, wanted_file_type = 'poster_original', i = 0): if not data: data = {} if not movie_info: movie_info = {} # See if it is in current files files = data['media'].get('files') if files.get('image_' + wanted_file_type): - if os.path.isfile(files['image_' + wanted_file_type][0]): - return files['image_' + wanted_file_type][0] + if os.path.isfile(files['image_' + wanted_file_type][i]): + return files['image_' + wanted_file_type][i] # Download using existing info try: images = movie_info['images'][wanted_file_type] - file_path = fireEvent('file.download', url = images[0], single = True) + file_path = fireEvent('file.download', url = images[i], single = True) return file_path except: pass - def getFanart(self, movie_info = None, data = None): + def getFanart(self, movie_info = None, data = None, i = 0): + if not data: data = {} + if not movie_info: movie_info = {} + return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='backdrop_original', i=i) + + def getBanner(self, movie_info=None, data=None, i=0): + if not data: data = {} + if not movie_info: movie_info = {} + return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='banner', i=i) + + def getClearart(self, movie_info=None, data=None, i=0): + if not data: data = {} + if not movie_info: movie_info = {} + return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='clearart', i=i) + + def getLogo(self, movie_info=None, data=None, i=0): + if not data: data = {} + if not movie_info: movie_info = {} + return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='logo', i=i) + + def getDiscart(self, movie_info=None, data=None, i=0): + if not data: data = {} + if not movie_info: movie_info = {} + return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='discart', i=i) + + def getLandscape(self, movie_info=None, data=None, i=0): + if not data: data = {} + if not movie_info: movie_info = {} + return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='landscape', i=i) + + def getExtrathumbs(self, movie_info=None, data=None, i=0): + if not data: data = {} + if not movie_info: movie_info = {} + return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='extrathumbs', i=i) + + def getExtrafanart(self, movie_info=None, data=None, i=0): if not data: data = {} if not movie_info: movie_info = {} - return self.getThumbnail(movie_info = movie_info, data = data, wanted_file_type = 'backdrop_original') + return self.getThumbnail(movie_info=movie_info, data=data, wanted_file_type='extrafanart', i=i) diff --git a/couchpotato/core/media/movie/providers/metadata/xbmc.py b/couchpotato/core/media/movie/providers/metadata/xbmc.py index 9f6bb7d..318f79a 100644 --- a/couchpotato/core/media/movie/providers/metadata/xbmc.py +++ b/couchpotato/core/media/movie/providers/metadata/xbmc.py @@ -17,19 +17,43 @@ autoload = 'XBMC' class XBMC(MovieMetaData): - def getFanartName(self, name, root): + def getFanartName(self, name, root, i): return self.createMetaName(self.conf('meta_fanart_name'), name, root) - def getThumbnailName(self, name, root): + def getThumbnailName(self, name, root, i): return self.createMetaName(self.conf('meta_thumbnail_name'), name, root) - def getNfoName(self, name, root): + def getNfoName(self, name, root, i): return self.createMetaName(self.conf('meta_nfo_name'), name, root) + def getBannerName(self, name, root, i): + return self.createMetaName(self.conf('meta_banner_name'), name, root) + + def getClearartName(self, name, root, i): + return self.createMetaName(self.conf('meta_clearart_name'), name, root) + + def getLogoName(self, name, root, i): + return self.createMetaName(self.conf('meta_logo_name'), name, root) + + def getDiscartName(self, name, root, i): + return self.createMetaName(self.conf('meta_discart_name'), name, root) + + def getLandscapeName(self, name, root, i): + return self.createMetaName(self.conf('meta_landscape_name'), name, root) + + def getExtrathumbsName(self, name, root, i): + return self.createMetaNameMult(self.conf('meta_extrathumbs_name'), name, root, i) + + def getExtrafanartName(self, name, root, i): + return self.createMetaNameMult(self.conf('meta_extrafanart_name'), name, root, i) + def createMetaName(self, basename, name, root): return os.path.join(root, basename.replace('%s', name)) - def getNfo(self, movie_info = None, data = None): + def createMetaNameMult(self, basename, name, root, i): + return os.path.join(root, basename.replace('%s', name).replace('%i', str(i + 1))) + + def getNfo(self, movie_info=None, data=None, i=0): if not data: data = {} if not movie_info: movie_info = {} @@ -133,6 +157,35 @@ class XBMC(MovieMetaData): for image_url in movie_info['images']['backdrop_original']: image = SubElement(fanart, 'thumb') image.text = toUnicode(image_url) + banner = SubElement(nfoxml, 'banner') + for image_url in movie_info['images']['banner']: + image = SubElement(banner, 'thumb') + image.text = toUnicode(image_url) + discart = SubElement(nfoxml, 'discart') + for image_url in movie_info['images']['discart']: + image = SubElement(discart, 'thumb') + image.text = toUnicode(image_url) + logo = SubElement(nfoxml, 'logo') + for image_url in movie_info['images']['logo']: + image = SubElement(logo, 'thumb') + image.text = toUnicode(image_url) + clearart = SubElement(nfoxml, 'clearart') + for image_url in movie_info['images']['clearart']: + image = SubElement(clearart, 'thumb') + image.text = toUnicode(image_url) + landscape = SubElement(nfoxml, 'landscape') + for image_url in movie_info['images']['landscape']: + image = SubElement(landscape, 'thumb') + image.text = toUnicode(image_url) + extrathumb = SubElement(nfoxml, 'extrathumb') + for image_url in movie_info['images']['extrathumbs']: + image = SubElement(extrathumb, 'thumb') + image.text = toUnicode(image_url) + extrafanart = SubElement(nfoxml, 'extrafanart') + for image_url in movie_info['images']['extrafanart']: + image = SubElement(extrafanart, 'thumb') + image.text = toUnicode(image_url) + # Add trailer if found trailer_found = False @@ -239,6 +292,90 @@ config = [{ 'default': '%s.tbn', 'advanced': True, }, + { + 'name': 'meta_banner', + 'label': 'Banner', + 'default': True, + 'type': 'bool' + }, + { + 'name': 'meta_banner_name', + 'label': 'Banner filename', + 'default': 'banner.jpg', + 'advanced': True, + }, + { + 'name': 'meta_clearart', + 'label': 'ClearArt', + 'default': True, + 'type': 'bool' + }, + { + 'name': 'meta_clearart_name', + 'label': 'ClearArt filename', + 'default': 'clearart.png', + 'advanced': True, + }, + { + 'name': 'meta_disc', + 'label': 'DiscArt', + 'default': True, + 'type': 'bool' + }, + { + 'name': 'meta_discart_name', + 'label': 'DiscArt filename', + 'default': 'disc.png', + 'advanced': True, + }, + { + 'name': 'meta_landscape', + 'label': 'Landscape', + 'default': True, + 'type': 'bool' + }, + { + 'name': 'meta_landscape_name', + 'label': 'Landscape filename', + 'default': 'landscape.jpg', + 'advanced': True, + }, + { + 'name': 'meta_logo', + 'label': 'ClearLogo', + 'default': True, + 'type': 'bool' + }, + { + 'name': 'meta_logo_name', + 'label': 'ClearLogo filename', + 'default': 'logo.png', + 'advanced': True, + }, + { + 'name': 'meta_extrathumbs', + 'label': 'Extrathumbs', + 'default': True, + 'type': 'bool' + }, + { + 'name': 'meta_extrathumbs_name', + 'label': 'Extrathumbs filename (%i is the image number, and must be included to have multiple images).', + 'default': 'extrathumbs/thumb%i.jpg', + 'advanced': True + }, + { + 'name': 'meta_extrafanart', + 'lavel': 'Extrafanart', + 'default': True, + 'type': 'bool' + }, + { + 'name': 'meta_extrafanart_name', + 'label': 'Extrafanart filename (%i is the image number, and must be included to have multiple images).', + 'default': 'extrafanart/extrafanart%i.jpg', + 'advanced': True + } ], }, ], diff --git a/libs/fanarttv/__init__.py b/libs/fanarttv/__init__.py new file mode 100644 index 0000000..773703c --- /dev/null +++ b/libs/fanarttv/__init__.py @@ -0,0 +1,110 @@ +__author__ = 'Andrea De Marco <24erre@gmail.com>' +__version__ = '1.4.0' +__classifiers__ = [ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Software Development :: Libraries', +] +__copyright__ = "2012, %s " % __author__ +__license__ = """ + Copyright %s. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" % __copyright__ + +__docformat__ = 'restructuredtext en' + +__doc__ = """ +:abstract: Python interface to fanart.tv API +:version: %s +:author: %s +:contact: http://z4r.github.com/ +:date: 2012-04-04 +:copyright: %s +""" % (__version__, __author__, __license__) + + +def values(obj): + return [v for k, v in obj.__dict__.iteritems() if not k.startswith('_')] + +BASEURL = 'http://api.fanart.tv/webservice' + + +class FORMAT(object): + JSON = 'JSON' + XML = 'XML' + PHP = 'PHP' + + +class WS(object): + MUSIC = 'artist' + MOVIE = 'movie' + TV = 'series' + + +class TYPE(object): + ALL = 'all' + + class TV(object): + ART = 'clearart' + LOGO = 'clearlogo' + CHARACTER = 'characterart' + THUMB = 'tvthumb' + SEASONTHUMB = 'seasonthumb' + BACKGROUND = 'showbackground' + HDLOGO = 'hdtvlogo' + HDART = 'hdclearart' + POSTER = 'tvposter' + BANNER = 'tvbanner' + + class MUSIC(object): + DISC = 'cdart' + LOGO = 'musiclogo' + BACKGROUND = 'artistbackground' + COVER = 'albumcover' + THUMB = 'artistthumb' + + class MOVIE(object): + ART = 'movieart' + LOGO = 'movielogo' + DISC = 'moviedisc' + POSTER = 'movieposter' + BACKGROUND = 'moviebackground' + HDLOGO = 'hdmovielogo' + HDART = 'hdmovieclearart' + BANNER = 'moviebanner' + THUMB = 'moviethumb' + + +class SORT(object): + POPULAR = 1 + NEWEST = 2 + OLDEST = 3 + + +class LIMIT(object): + ONE = 1 + ALL = 2 + +FORMAT_LIST = values(FORMAT) +WS_LIST = values(WS) +TYPE_LIST = values(TYPE.MUSIC) + values(TYPE.TV) + values(TYPE.MOVIE) + [TYPE.ALL] +MUSIC_TYPE_LIST = values(TYPE.MUSIC) + [TYPE.ALL] +TV_TYPE_LIST = values(TYPE.TV) + [TYPE.ALL] +MOVIE_TYPE_LIST = values(TYPE.MOVIE) + [TYPE.ALL] +SORT_LIST = values(SORT) +LIMIT_LIST = values(LIMIT) diff --git a/libs/fanarttv/core.py b/libs/fanarttv/core.py new file mode 100644 index 0000000..9cd1cad --- /dev/null +++ b/libs/fanarttv/core.py @@ -0,0 +1,44 @@ +import libs.requests as requests +import libs.fanarttv as fanart +from libs.fanarttv.errors import RequestFanartError, ResponseFanartError + + +class Request(object): + def __init__(self, apikey, id, ws, type=None, sort=None, limit=None): + self._apikey = apikey + self._id = id + self._ws = ws + self._type = type or fanart.TYPE.ALL + self._sort = sort or fanart.SORT.POPULAR + self._limit = limit or fanart.LIMIT.ALL + self.validate() + self._response = None + + def validate(self): + for attribute_name in ('ws', 'type', 'sort', 'limit'): + attribute = getattr(self, '_' + attribute_name) + choices = getattr(fanart, attribute_name.upper() + '_LIST') + if attribute not in choices: + raise RequestFanartError('Not allowed {0}: {1} [{2}]'.format(attribute_name, attribute, ', '.join(choices))) + + def __str__(self): + return '/'.join(map(str, [ + fanart.BASEURL, + self._ws, + self._apikey, + self._id, + fanart.FORMAT.JSON, + self._type, + self._sort, + self._limit, + ])) + + def response(self): + try: + response = requests.get(str(self)) + rjson = response.json() + if not isinstance(rjson, dict): + raise Exception(response.text) + return rjson + except Exception as e: + raise ResponseFanartError(str(e)) diff --git a/libs/fanarttv/errors.py b/libs/fanarttv/errors.py new file mode 100644 index 0000000..95a71e3 --- /dev/null +++ b/libs/fanarttv/errors.py @@ -0,0 +1,15 @@ +class FanartError(Exception): + def __str__(self): + return ', '.join(map(str, self.args)) + + def __repr__(self): + name = self.__class__.__name__ + return '%s%r' % (name, self.args) + + +class ResponseFanartError(FanartError): + pass + + +class RequestFanartError(FanartError): + pass diff --git a/libs/fanarttv/immutable.py b/libs/fanarttv/immutable.py new file mode 100644 index 0000000..170de37 --- /dev/null +++ b/libs/fanarttv/immutable.py @@ -0,0 +1,46 @@ +class Immutable(object): + _mutable = False + + def __setattr__(self, name, value): + if self._mutable or name == '_mutable': + super(Immutable, self).__setattr__(name, value) + else: + raise TypeError("Can't modify immutable instance") + + def __delattr__(self, name): + if self._mutable: + super(Immutable, self).__delattr__(name) + else: + raise TypeError("Can't modify immutable instance") + + def __eq__(self, other): + return hash(self) == hash(other) + + def __hash__(self): + return hash(repr(self)) + + def __repr__(self): + return '%s(%s)' % ( + self.__class__.__name__, + ', '.join(['{0}={1}'.format(k, repr(v)) for k, v in self]) + ) + + def __iter__(self): + l = self.__dict__.keys() + l.sort() + for k in l: + if not k.startswith('_'): + yield k, getattr(self, k) + + @staticmethod + def mutablemethod(f): + def func(self, *args, **kwargs): + if isinstance(self, Immutable): + old_mutable = self._mutable + self._mutable = True + res = f(self, *args, **kwargs) + self._mutable = old_mutable + else: + res = f(self, *args, **kwargs) + return res + return func diff --git a/libs/fanarttv/items.py b/libs/fanarttv/items.py new file mode 100644 index 0000000..6484807 --- /dev/null +++ b/libs/fanarttv/items.py @@ -0,0 +1,68 @@ +import json +import os +import libs.requests as requests +from libs.fanarttv.core import Request +from libs.fanarttv.immutable import Immutable + + +class LeafItem(Immutable): + KEY = NotImplemented + + @Immutable.mutablemethod + def __init__(self, id, url, likes): + self.id = int(id) + self.url = url + self.likes = int(likes) + self._content = None + + @classmethod + def from_dict(cls, resource): + return cls(**dict([(str(k), v) for k, v in resource.iteritems()])) + + @classmethod + def extract(cls, resource): + return [cls.from_dict(i) for i in resource.get(cls.KEY, {})] + + @Immutable.mutablemethod + def content(self): + if not self._content: + self._content = requests.get(self.url).content + return self._content + + def __str__(self): + return self.url + + +class ResourceItem(Immutable): + WS = NotImplemented + request_cls = Request + + @classmethod + def from_dict(cls, map): + raise NotImplementedError + + @classmethod + def get(cls, id): + map = cls.request_cls( + apikey=os.environ.get('FANART_APIKEY'), + id=id, + ws=cls.WS + ).response() + return cls.from_dict(map) + + def json(self, **kw): + return json.dumps( + self, + default=lambda o: dict([(k, v) for k, v in o.__dict__.items() if not k.startswith('_')]), + **kw + ) + + +class CollectableItem(Immutable): + @classmethod + def from_dict(cls, key, map): + raise NotImplementedError + + @classmethod + def collection_from_dict(cls, map): + return [cls.from_dict(k, v) for k, v in map.iteritems()] diff --git a/libs/fanarttv/movie.py b/libs/fanarttv/movie.py new file mode 100644 index 0000000..fe473d4 --- /dev/null +++ b/libs/fanarttv/movie.py @@ -0,0 +1,103 @@ +import libs.fanarttv as fanart +from libs.fanarttv.items import LeafItem, Immutable, ResourceItem +__all__ = ( + 'ArtItem', + 'DiscItem', + 'LogoItem', + 'PosterItem', + 'BackgroundItem', + 'HdLogoItem', + 'HdArtItem', + 'BannerItem', + 'ThumbItem', + 'Movie', +) + + +class MovieItem(LeafItem): + + @Immutable.mutablemethod + def __init__(self, id, url, likes, lang): + super(MovieItem, self).__init__(id, url, likes) + self.lang = lang + + +class DiscItem(MovieItem): + KEY = fanart.TYPE.MOVIE.DISC + + @Immutable.mutablemethod + def __init__(self, id, url, likes, lang, disc, disc_type): + super(DiscItem, self).__init__(id, url, likes, lang) + self.disc = int(disc) + self.disc_type = disc_type + + +class ArtItem(MovieItem): + KEY = fanart.TYPE.MOVIE.ART + + +class LogoItem(MovieItem): + KEY = fanart.TYPE.MOVIE.LOGO + + +class PosterItem(MovieItem): + KEY = fanart.TYPE.MOVIE.POSTER + + +class BackgroundItem(MovieItem): + KEY = fanart.TYPE.MOVIE.BACKGROUND + + +class HdLogoItem(MovieItem): + KEY = fanart.TYPE.MOVIE.HDLOGO + + +class HdArtItem(MovieItem): + KEY = fanart.TYPE.MOVIE.HDART + + +class BannerItem(MovieItem): + KEY = fanart.TYPE.MOVIE.BANNER + + +class ThumbItem(MovieItem): + KEY = fanart.TYPE.MOVIE.THUMB + + +class Movie(ResourceItem): + WS = fanart.WS.MOVIE + + @Immutable.mutablemethod + def __init__(self, name, imdbid, tmdbid, arts, logos, discs, posters, backgrounds, hdlogos, hdarts, + banners, thumbs): + self.name = name + self.imdbid = imdbid + self.tmdbid = tmdbid + self.arts = arts + self.posters = posters + self.logos = logos + self.discs = discs + self.backgrounds = backgrounds + self.hdlogos = hdlogos + self.hdarts = hdarts + self.banners = banners + self.thumbs = thumbs + + @classmethod + def from_dict(cls, resource): + assert len(resource) == 1, 'Bad Format Map' + name, resource = resource.items()[0] + return cls( + name=name, + imdbid=resource['imdb_id'], + tmdbid=resource['tmdb_id'], + arts=ArtItem.extract(resource), + logos=LogoItem.extract(resource), + discs=DiscItem.extract(resource), + posters=PosterItem.extract(resource), + backgrounds=BackgroundItem.extract(resource), + hdlogos=HdLogoItem.extract(resource), + hdarts=HdArtItem.extract(resource), + banners=BannerItem.extract(resource), + thumbs=ThumbItem.extract(resource), + ) diff --git a/libs/fanarttv/music.py b/libs/fanarttv/music.py new file mode 100644 index 0000000..df634c6 --- /dev/null +++ b/libs/fanarttv/music.py @@ -0,0 +1,80 @@ +from libs.fanarttv.items import Immutable, LeafItem, ResourceItem, CollectableItem +import libs.fanarttv as fanart +__all__ = ( + 'BackgroundItem', + 'CoverItem', + 'LogoItem', + 'ThumbItem', + 'DiscItem', + 'Artist', + 'Album', +) + + +class BackgroundItem(LeafItem): + KEY = fanart.TYPE.MUSIC.BACKGROUND + + +class CoverItem(LeafItem): + KEY = fanart.TYPE.MUSIC.COVER + + +class LogoItem(LeafItem): + KEY = fanart.TYPE.MUSIC.LOGO + + +class ThumbItem(LeafItem): + KEY = fanart.TYPE.MUSIC.THUMB + + +class DiscItem(LeafItem): + KEY = fanart.TYPE.MUSIC.DISC + + @Immutable.mutablemethod + def __init__(self, id, url, likes, disc, size): + super(DiscItem, self).__init__(id, url, likes) + self.disc = int(disc) + self.size = int(size) + + +class Artist(ResourceItem): + WS = fanart.WS.MUSIC + + @Immutable.mutablemethod + def __init__(self, name, mbid, albums, backgrounds, logos, thumbs): + self.name = name + self.mbid = mbid + self.albums = albums + self.backgrounds = backgrounds + self.logos = logos + self.thumbs = thumbs + + @classmethod + def from_dict(cls, resource): + assert len(resource) == 1, 'Bad Format Map' + name, resource = resource.items()[0] + return cls( + name=name, + mbid=resource['mbid_id'], + albums=Album.collection_from_dict(resource.get('albums', {})), + backgrounds=BackgroundItem.extract(resource), + thumbs=ThumbItem.extract(resource), + logos=LogoItem.extract(resource), + ) + + +class Album(CollectableItem): + + @Immutable.mutablemethod + def __init__(self, mbid, covers, arts): + self.mbid = mbid + self.covers = covers + self.arts = arts + + @classmethod + def from_dict(cls, key, resource): + return cls( + mbid=key, + covers=CoverItem.extract(resource), + arts=DiscItem.extract(resource), + ) diff --git a/libs/fanarttv/tv.py b/libs/fanarttv/tv.py new file mode 100644 index 0000000..9b1b08a --- /dev/null +++ b/libs/fanarttv/tv.py @@ -0,0 +1,108 @@ +import libs.fanarttv as fanart +from libs.fanarttv.items import LeafItem, Immutable, ResourceItem +__all__ = ( + 'CharacterItem', + 'ArtItem', + 'LogoItem', + 'BackgroundItem', + 'SeasonItem', + 'ThumbItem', + 'HdLogoItem', + 'HdArtItem', + 'PosterItem', + 'BannerItem', + 'TvShow', +) + + +class TvItem(LeafItem): + @Immutable.mutablemethod + def __init__(self, id, url, likes, lang): + super(TvItem, self).__init__(id, url, likes) + self.lang = lang + + +class SeasonedTvItem(TvItem): + @Immutable.mutablemethod + def __init__(self, id, url, likes, lang, season): + super(SeasonedTvItem, self).__init__(id, url, likes, lang) + self.season = 0 if season == 'all' else int(season or 0) + + +class CharacterItem(TvItem): + KEY = fanart.TYPE.TV.CHARACTER + + +class ArtItem(TvItem): + KEY = fanart.TYPE.TV.ART + + +class LogoItem(TvItem): + KEY = fanart.TYPE.TV.LOGO + + +class BackgroundItem(SeasonedTvItem): + KEY = fanart.TYPE.TV.BACKGROUND + + +class SeasonItem(SeasonedTvItem): + KEY = fanart.TYPE.TV.SEASONTHUMB + + +class ThumbItem(TvItem): + KEY = fanart.TYPE.TV.THUMB + + +class HdLogoItem(TvItem): + KEY = fanart.TYPE.TV.HDLOGO + + +class HdArtItem(TvItem): + KEY = fanart.TYPE.TV.HDART + + +class PosterItem(TvItem): + KEY = fanart.TYPE.TV.POSTER + + +class BannerItem(TvItem): + KEY = fanart.TYPE.TV.BANNER + + +class TvShow(ResourceItem): + WS = fanart.WS.TV + + @Immutable.mutablemethod + def __init__(self, name, tvdbid, backgrounds, characters, arts, logos, seasons, thumbs, hdlogos, hdarts, posters, + banners): + self.name = name + self.tvdbid = tvdbid + self.backgrounds = backgrounds + self.characters = characters + self.arts = arts + self.logos = logos + self.seasons = seasons + self.thumbs = thumbs + self.hdlogos = hdlogos + self.hdarts = hdarts + self.posters = posters + self.banners = banners + + @classmethod + def from_dict(cls, resource): + assert len(resource) == 1, 'Bad Format Map' + name, resource = resource.items()[0] + return cls( + name=name, + tvdbid=resource['thetvdb_id'], + backgrounds=BackgroundItem.extract(resource), + characters=CharacterItem.extract(resource), + arts=ArtItem.extract(resource), + logos=LogoItem.extract(resource), + seasons=SeasonItem.extract(resource), + thumbs=ThumbItem.extract(resource), + hdlogos=HdLogoItem.extract(resource), + hdarts=HdArtItem.extract(resource), + posters=PosterItem.extract(resource), + banners=BannerItem.extract(resource), + ) From 6a81f2241d7fa69ccf4e4138ddefbeb025ff3e28 Mon Sep 17 00:00:00 2001 From: Dan Boehm Date: Thu, 24 Apr 2014 15:02:29 -0500 Subject: [PATCH 2/2] Added option to run the Artwork Downloader addon during XBMC notify. This option will only work in XBMCv12 (Frodo) or later. It also requires the Artwork Downloader Addon. Since XBMC's API doesn't support notifications over HTML, there is no way for couchpotato to know when the Library Scan is complete. Since running the Artwork Downloader before the movie has been scanned won't solve anything, a delay timer can be adjusted to suit the user's needs. Squashed commit of the following: commit bd60ed585f77cc40c31fd67d4ae732e0845d31ab Merge: fcb092e b113a4d Author: Dan Boehm Date: Thu Apr 24 14:26:24 2014 -0500 Merge branch 'fanarttv' into artdlnotify commit b113a4def197a9ca8545bde9f5081c0591b93b36 Author: Dan Boehm Date: Thu Apr 24 14:24:12 2014 -0500 Bug-fix and code cleanup. Fixed a bug where the movie.info event would crash if there aren't any pictures to scrape in fanart.tv. commit fcb092e776e00ceabea016b3c26d9394e32d72b0 Author: Dan Boehm Date: Thu Apr 24 14:21:27 2014 -0500 Option to run the artwork downloader addon during XBMC notify. commit adf7a4675d472e9e95a316c6cccc681a52804f13 Author: Dan Boehm Date: Wed Apr 23 16:15:03 2014 -0500 Added support for extrafanart. Also, the main fanart will be taken from fanart.tv unless one does not exist. commit 1791e46c8602f40bb56fe0cf7ecb0607f35b4b12 Author: Dan Boehm Date: Wed Apr 23 15:13:14 2014 -0500 Couchpotato now downloads extrathumbs from the extra tmdb backdrops if they exist. This commit made some major changes to the core image creation functionality that makes writing multiple images to folders possible. commit c0858807873749dbc928c0260037138f51f894ca Author: Dan Boehm Date: Wed Apr 23 12:18:53 2014 -0500 Bug Fix & Implemented functionality to select bluray or dvd disc images. Currently, only blurays will be selected, unless there are no blurays. However, if a mechanism for determining the quality of the release is implemented, it would be simple to make this selection based on the quality. commit 786751371d243f53d0f5c6f2c38d92288d8608ba Author: Dan Boehm Date: Wed Apr 23 10:59:25 2014 -0500 Fixed a bug where non-HD clearart and logos couldn't be downloaded. commit feda8df483d13b5a5df3a869f25de8f2c7e6ffe3 Author: Dan Boehm Date: Wed Apr 23 10:12:31 2014 -0500 Fixed some problems that were missed with the previous merge. commit 5ddab6c40e69a5accc6c0336cd7485920ff82d8f Merge: 7273abf ff46aa0 Author: Dan Boehm Date: Wed Apr 23 10:02:11 2014 -0500 Merge branch 'develop' into fanarttv Conflicts: couchpotato/core/media/movie/providers/info/themoviedb.py couchpotato/core/providers/metadata/xbmc/__init__.py commit 7273abf827735cf245711c3d3199a6a173a964aa Author: dan Date: Thu Feb 27 13:29:57 2014 -0600 Downloads extra artwork from fanart.tv Downloads occur with correct filenaming when XBMC metadata is generated, but the image URLs are selected when the movie.info event is called. commit 9080d9d749c7e1ddbdc78f7b37a3c5f83195d580 Author: dan Date: Wed Feb 26 16:31:37 2014 -0600 Added basic functionality for fanarttv provider. This should be mostly done and is based on the tvdb provider. commit 1b39b246c2a9d65f9ef86c4e150a12d893e362c0 Author: dan Date: Wed Feb 26 14:50:17 2014 -0600 Updated fanarttv library with the correct package hierarchy (libs.fanarttv). commit 8abb7c8f8ad3347900debb9f6a6d5a7acb7df396 Author: dan Date: Wed Feb 26 13:12:48 2014 -0600 Added fanart.tv API python library (lib.fanarttv). The upstream for this library is at https://github.com/z4r/python-fanart. --- couchpotato/core/notifications/xbmc.py | 78 +++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/couchpotato/core/notifications/xbmc.py b/couchpotato/core/notifications/xbmc.py index 28439a7..db34306 100644 --- a/couchpotato/core/notifications/xbmc.py +++ b/couchpotato/core/notifications/xbmc.py @@ -3,6 +3,8 @@ import json import socket import traceback import urllib +import time +import os from couchpotato.core.helpers.variable import splitString, getTitle from couchpotato.core.logger import CPLog @@ -36,7 +38,7 @@ class XBMC(Notification): if self.use_json_notifications.get(host): calls = [ - ('GUI.ShowNotification', {'title': self.default_title, 'message': message, 'image': self.getNotificationImage('small')}), + ('GUI.ShowNotification', None, {'title': self.default_title, 'message': message, 'image': self.getNotificationImage('small')}), ] if data and data.get('destination_dir') and (not self.conf('only_first') or hosts.index(host) == 0): @@ -44,7 +46,7 @@ class XBMC(Notification): if not self.conf('force_full_scan') and (self.conf('remote_dir_scan') or socket.getfqdn('localhost') == socket.getfqdn(host.split(':')[0])): param = {'directory': data['destination_dir']} - calls.append(('VideoLibrary.Scan', param)) + calls.append(('VideoLibrary.Scan', None, param)) max_successful += len(calls) response = self.request(host, calls) @@ -66,6 +68,50 @@ class XBMC(Notification): except: log.error('Failed parsing results: %s', traceback.format_exc()) + + if self.conf('run_artwork_downloader') and data and self.use_json_notifications.get(host): + time.sleep(self.conf('run_artwork_downloader_delay')) + + if self.conf('force_full_scan'): + calls = [('Addons.ExecuteAddon', None, {'addonid': 'script.artwork.downloader'})] + max_successful += len(calls) + response = self.request(host, calls) + + try: + if response[0].get('result') and result['result'] == 'OK': + successful += 1 + elif response[0].get('error'): + log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code'])) + except: + log.error('Failed parsing results: %s', traceback.format_exc()) + else: + calls = [('VideoLibrary.GetMovies', 'libMovies', {"filter":{"field": "title", "operator": "is", "value": data['media']['title'], "year": data['media']['info']['year']}})] + max_successful += len(calls) + response = self.request(host, calls) + + dbid = None + try: + if response[0].get('result'): + successful += 1 + dbid = response[0]['result']['movies'][-1]['movieid'] + elif response[0].get('error'): + log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code'])) + except: + log.error('Failed parsing results: %s', traceback.format_exc()) + + if dbid is not None: + calls = [('Addons.ExecuteAddon', None, {'addonid': 'script.artwork.downloader', 'params':{'media_type': 'movie', 'dbid': str(dbid)}})] + + max_successful += len(calls) + response = self.request(host, calls) + + try: + if response[0].get('result') and result['result'] == 'OK': + successful += 1 + elif response[0].get('error'): + log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code'])) + except: + log.error('Failed parsing results: %s', traceback.format_exc()) return successful == max_successful @@ -75,7 +121,7 @@ class XBMC(Notification): # XBMC JSON-RPC version request response = self.request(host, [ - ('JSONRPC.Version', {}) + ('JSONRPC.Version', None, {}) ]) for result in response: if result.get('result') and type(result['result']['version']).__name__ == 'int': @@ -112,7 +158,7 @@ class XBMC(Notification): self.use_json_notifications[host] = True # send the text message - resp = self.request(host, [('GUI.ShowNotification', {'title':self.default_title, 'message':message, 'image': self.getNotificationImage('small')})]) + resp = self.request(host, [('GUI.ShowNotification', None, {'title':self.default_title, 'message':message, 'image': self.getNotificationImage('small')})]) for r in resp: if r.get('result') and r['result'] == 'OK': log.debug('Message delivered successfully!') @@ -184,12 +230,16 @@ class XBMC(Notification): data = [] for req in do_requests: - method, kwargs = req + method, id, kwargs = req + + if id is None: + id = method + data.append({ 'method': method, 'params': kwargs, 'jsonrpc': '2.0', - 'id': method, + 'id': id, }) data = json.dumps(data) @@ -273,6 +323,22 @@ config = [{ 'advanced': True, 'description': 'Also send message when movie is snatched.', }, + { + 'name': 'run_artwork_downloader', + 'label': 'Run the Artwork Downloader', + 'default': 0, + 'type': 'bool', + 'advanced': True, + 'description': 'Runs the Artwork Downloader script to initialize/download artwork. (Requires the Artwork Downloader addon and XBMC Frodo or later)' + }, + { + 'name': 'run_artwork_downloader_delay', + 'label': 'Artwork Downloader delay', + 'default': 5, + 'type': 'int', + 'advanced': True, + 'description': 'Number of seconds to wait to start the Artwork Downloader script after notifying XBMC.', + }, ], } ],