# Author: Nic Wolfe # URL: http://code.google.com/p/sickbeard/ # # This file is part of SickGear. # # SickGear is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # SickGear is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with SickGear. If not, see . from __future__ import with_statement import os.path try: from lxml import etree except ImportError: try: import xml.etree.cElementTree as etree except ImportError: import xml.etree.ElementTree as etree import re import sickbeard from sickbeard import helpers from sickbeard.metadata import helpers as metadata_helpers from sickbeard import logger from sickbeard import encodingKludge as ek from sickbeard.exceptions import ex from sickbeard.indexers import indexer_config from sickbeard.indexers.indexer_config import INDEXER_TVDB, INDEXER_TVDB_V1 from six import iteritems from lib.tmdb_api.tmdb_api import TMDB from lib.fanart.core import Request as fanartRequest import lib.fanart as fanart class GenericMetadata(): """ Base class for all metadata providers. Default behavior is meant to mostly follow XBMC 12+ metadata standards. Has support for: - show metadata file - episode metadata file - episode thumbnail - show fanart - show poster - show banner - season thumbnails (poster) - season thumbnails (banner) - season all poster - season all banner """ def __init__(self, show_metadata=False, episode_metadata=False, fanart=False, poster=False, banner=False, episode_thumbnails=False, season_posters=False, season_banners=False, season_all_poster=False, season_all_banner=False): self.name = "Generic" self._ep_nfo_extension = "nfo" self._show_metadata_filename = "tvshow.nfo" self.fanart_name = "fanart.jpg" self.poster_name = "poster.jpg" self.banner_name = "banner.jpg" self.season_all_poster_name = "season-all-poster.jpg" self.season_all_banner_name = "season-all-banner.jpg" self.show_metadata = show_metadata self.episode_metadata = episode_metadata self.fanart = fanart self.poster = poster self.banner = banner self.episode_thumbnails = episode_thumbnails self.season_posters = season_posters self.season_banners = season_banners self.season_all_poster = season_all_poster self.season_all_banner = season_all_banner def get_config(self): config_list = [self.show_metadata, self.episode_metadata, self.fanart, self.poster, self.banner, self.episode_thumbnails, self.season_posters, self.season_banners, self.season_all_poster, self.season_all_banner] return '|'.join([str(int(x)) for x in config_list]) def get_id(self): return GenericMetadata.makeID(self.name) @staticmethod def makeID(name): name_id = re.sub("[+]", "plus", name) name_id = re.sub("[^\w\d_]", "_", name_id).lower() return name_id def set_config(self, string): config_list = [bool(int(x)) for x in string.split('|')] self.show_metadata = config_list[0] self.episode_metadata = config_list[1] self.fanart = config_list[2] self.poster = config_list[3] self.banner = config_list[4] self.episode_thumbnails = config_list[5] self.season_posters = config_list[6] self.season_banners = config_list[7] self.season_all_poster = config_list[8] self.season_all_banner = config_list[9] def _has_show_metadata(self, show_obj): result = ek.ek(os.path.isfile, self.get_show_file_path(show_obj)) logger.log(u"Checking if " + self.get_show_file_path(show_obj) + " exists: " + str(result), logger.DEBUG) return result def _has_episode_metadata(self, ep_obj): result = ek.ek(os.path.isfile, self.get_episode_file_path(ep_obj)) logger.log(u"Checking if " + self.get_episode_file_path(ep_obj) + " exists: " + str(result), logger.DEBUG) return result def _has_fanart(self, show_obj): result = ek.ek(os.path.isfile, self.get_fanart_path(show_obj)) logger.log(u"Checking if " + self.get_fanart_path(show_obj) + " exists: " + str(result), logger.DEBUG) return result def _has_poster(self, show_obj): result = ek.ek(os.path.isfile, self.get_poster_path(show_obj)) logger.log(u"Checking if " + self.get_poster_path(show_obj) + " exists: " + str(result), logger.DEBUG) return result def _has_banner(self, show_obj): result = ek.ek(os.path.isfile, self.get_banner_path(show_obj)) logger.log(u"Checking if " + self.get_banner_path(show_obj) + " exists: " + str(result), logger.DEBUG) return result def _has_episode_thumb(self, ep_obj): location = self.get_episode_thumb_path(ep_obj) result = None is not location and ek.ek(os.path.isfile, location) if location: logger.log(u"Checking if " + location + " exists: " + str(result), logger.DEBUG) return result def _has_season_poster(self, show_obj, season): location = self.get_season_poster_path(show_obj, season) result = None is not location and ek.ek(os.path.isfile, location) if location: logger.log(u"Checking if " + location + " exists: " + str(result), logger.DEBUG) return result def _has_season_banner(self, show_obj, season): location = self.get_season_banner_path(show_obj, season) result = None is not location and ek.ek(os.path.isfile, location) if location: logger.log(u"Checking if " + location + " exists: " + str(result), logger.DEBUG) return result def _has_season_all_poster(self, show_obj): result = ek.ek(os.path.isfile, self.get_season_all_poster_path(show_obj)) logger.log(u"Checking if " + self.get_season_all_poster_path(show_obj) + " exists: " + str(result), logger.DEBUG) return result def _has_season_all_banner(self, show_obj): result = ek.ek(os.path.isfile, self.get_season_all_banner_path(show_obj)) logger.log(u"Checking if " + self.get_season_all_banner_path(show_obj) + " exists: " + str(result), logger.DEBUG) return result def get_show_file_path(self, show_obj): return ek.ek(os.path.join, show_obj.location, self._show_metadata_filename) def get_episode_file_path(self, ep_obj): return helpers.replaceExtension(ep_obj.location, self._ep_nfo_extension) def get_fanart_path(self, show_obj): return ek.ek(os.path.join, show_obj.location, self.fanart_name) def get_poster_path(self, show_obj): return ek.ek(os.path.join, show_obj.location, self.poster_name) def get_banner_path(self, show_obj): return ek.ek(os.path.join, show_obj.location, self.banner_name) def get_episode_thumb_path(self, ep_obj): """ Returns the path where the episode thumbnail should be stored. ep_obj: a TVEpisode instance for which to create the thumbnail """ if ek.ek(os.path.isfile, ep_obj.location): tbn_filename = ep_obj.location.rpartition(".") if tbn_filename[0] == "": tbn_filename = ep_obj.location + "-thumb.jpg" else: tbn_filename = tbn_filename[0] + "-thumb.jpg" else: return None return tbn_filename def get_season_poster_path(self, show_obj, season): """ Returns the full path to the file for a given season poster. show_obj: a TVShow instance for which to generate the path season: a season number to be used for the path. Note that season 0 means specials. """ # Our specials thumbnail is, well, special if season == 0: season_poster_filename = 'season-specials' else: season_poster_filename = 'season' + str(season).zfill(2) return ek.ek(os.path.join, show_obj.location, season_poster_filename + '-poster.jpg') def get_season_banner_path(self, show_obj, season): """ Returns the full path to the file for a given season banner. show_obj: a TVShow instance for which to generate the path season: a season number to be used for the path. Note that season 0 means specials. """ # Our specials thumbnail is, well, special if season == 0: season_banner_filename = 'season-specials' else: season_banner_filename = 'season' + str(season).zfill(2) return ek.ek(os.path.join, show_obj.location, season_banner_filename + '-banner.jpg') def get_season_all_poster_path(self, show_obj): return ek.ek(os.path.join, show_obj.location, self.season_all_poster_name) def get_season_all_banner_path(self, show_obj): return ek.ek(os.path.join, show_obj.location, self.season_all_banner_name) def _show_data(self, show_obj): """ This should be overridden by the implementing class. It should provide the content of the show metadata file. """ return None def _ep_data(self, ep_obj): """ This should be overridden by the implementing class. It should provide the content of the episode metadata file. """ return None def create_show_metadata(self, show_obj): if self.show_metadata and show_obj and not self._has_show_metadata(show_obj): logger.log(u"Metadata provider " + self.name + " creating show metadata for " + show_obj.name, logger.DEBUG) return self.write_show_file(show_obj) return False def create_episode_metadata(self, ep_obj): if self.episode_metadata and ep_obj and not self._has_episode_metadata(ep_obj): logger.log(u"Metadata provider " + self.name + " creating episode metadata for " + ep_obj.prettyName(), logger.DEBUG) return self.write_ep_file(ep_obj) return False def update_show_indexer_metadata(self, show_obj): if self.show_metadata and show_obj and self._has_show_metadata(show_obj): logger.log(u'Metadata provider %s updating show indexer metadata file for %s' % (self.name, show_obj.name), logger.DEBUG) nfo_file_path = self.get_show_file_path(show_obj) try: with ek.ek(open, nfo_file_path, 'r') as xmlFileObj: show_xml = etree.ElementTree(file=xmlFileObj) indexer = show_xml.find('indexer') indexerid = show_xml.find('id') root = show_xml.getroot() show_indexer = str(show_obj.indexer) if None is not indexer: indexer.text = show_indexer else: etree.SubElement(root, 'indexer').text = show_indexer show_indexerid = str(show_obj.indexerid) if None is not indexerid: indexerid.text = show_indexerid else: etree.SubElement(root, 'id').text = show_indexerid # Make it purdy helpers.indentXML(root) show_xml.write(nfo_file_path) helpers.chmodAsParent(nfo_file_path) return True except IOError as e: logger.log(u'Unable to write file %s - is the folder writable? %s' % (nfo_file_path, ex(e)), logger.ERROR) def create_fanart(self, show_obj): if self.fanart and show_obj and not self._has_fanart(show_obj): logger.log(u"Metadata provider " + self.name + " creating fanart for " + show_obj.name, logger.DEBUG) return self.save_fanart(show_obj) return False def create_poster(self, show_obj): if self.poster and show_obj and not self._has_poster(show_obj): logger.log(u"Metadata provider " + self.name + " creating poster for " + show_obj.name, logger.DEBUG) return self.save_poster(show_obj) return False def create_banner(self, show_obj): if self.banner and show_obj and not self._has_banner(show_obj): logger.log(u"Metadata provider " + self.name + " creating banner for " + show_obj.name, logger.DEBUG) return self.save_banner(show_obj) return False def create_episode_thumb(self, ep_obj): if self.episode_thumbnails and ep_obj and not self._has_episode_thumb(ep_obj): logger.log(u"Metadata provider " + self.name + " creating episode thumbnail for " + ep_obj.prettyName(), logger.DEBUG) return self.save_thumbnail(ep_obj) return False def create_season_posters(self, show_obj): if self.season_posters and show_obj: result = [] for season, episodes in iteritems(show_obj.episodes): # @UnusedVariable if not self._has_season_poster(show_obj, season): logger.log(u"Metadata provider " + self.name + " creating season posters for " + show_obj.name, logger.DEBUG) result = result + [self.save_season_posters(show_obj, season)] return all(result) return False def create_season_banners(self, show_obj): if self.season_banners and show_obj: result = [] for season, episodes in iteritems(show_obj.episodes): # @UnusedVariable if not self._has_season_banner(show_obj, season): logger.log(u"Metadata provider " + self.name + " creating season banners for " + show_obj.name, logger.DEBUG) result = result + [self.save_season_banners(show_obj, season)] return all(result) return False def create_season_all_poster(self, show_obj): if self.season_all_poster and show_obj and not self._has_season_all_poster(show_obj): logger.log(u"Metadata provider " + self.name + " creating season all poster for " + show_obj.name, logger.DEBUG) return self.save_season_all_poster(show_obj) return False def create_season_all_banner(self, show_obj): if self.season_all_banner and show_obj and not self._has_season_all_banner(show_obj): logger.log(u"Metadata provider " + self.name + " creating season all banner for " + show_obj.name, logger.DEBUG) return self.save_season_all_banner(show_obj) return False def _get_episode_thumb_url(self, ep_obj): """ Returns the URL to use for downloading an episode's thumbnail. Uses theTVDB.com and TVRage.com data. ep_obj: a TVEpisode object for which to grab the thumb URL """ all_eps = [ep_obj] + ep_obj.relatedEps # validate show if not helpers.validateShow(ep_obj.show): return None # try all included episodes in case some have thumbs and others don't for cur_ep in all_eps: if INDEXER_TVDB == cur_ep.show.indexer: indexer_lang = cur_ep.show.lang try: lINDEXER_API_PARMS = sickbeard.indexerApi(INDEXER_TVDB_V1).api_params.copy() lINDEXER_API_PARMS['dvdorder'] = 0 != cur_ep.show.dvdorder lINDEXER_API_PARMS['no_dummy'] = True if indexer_lang and not indexer_lang == 'en': lINDEXER_API_PARMS['language'] = indexer_lang t = sickbeard.indexerApi(INDEXER_TVDB_V1).indexer(**lINDEXER_API_PARMS) myEp = t[cur_ep.show.indexerid][cur_ep.season][cur_ep.episode] except (sickbeard.indexer_episodenotfound, sickbeard.indexer_seasonnotfound, TypeError): myEp = None else: myEp = helpers.validateShow(cur_ep.show, cur_ep.season, cur_ep.episode) if not myEp: continue thumb_url = getattr(myEp, 'filename', None) or (isinstance(myEp, dict) and myEp.get('filename', None)) if thumb_url not in (None, False, ''): return thumb_url return None def write_show_file(self, show_obj): """ Generates and writes show_obj's metadata under the given path to the filename given by get_show_file_path() show_obj: TVShow object for which to create the metadata path: An absolute or relative path where we should put the file. Note that the file name will be the default show_file_name. Note that this method expects that _show_data will return an ElementTree object. If your _show_data returns data in another format you'll need to override this method. """ data = self._show_data(show_obj) if not data: return False nfo_file_path = self.get_show_file_path(show_obj) nfo_file_dir = ek.ek(os.path.dirname, nfo_file_path) try: if not ek.ek(os.path.isdir, nfo_file_dir): logger.log(u"Metadata dir didn't exist, creating it at " + nfo_file_dir, logger.DEBUG) ek.ek(os.makedirs, nfo_file_dir) helpers.chmodAsParent(nfo_file_dir) logger.log(u"Writing show nfo file to " + nfo_file_path, logger.DEBUG) nfo_file = ek.ek(open, nfo_file_path, 'w') data.write(nfo_file, encoding="utf-8") nfo_file.close() helpers.chmodAsParent(nfo_file_path) except IOError as e: logger.log(u"Unable to write file to " + nfo_file_path + " - are you sure the folder is writable? " + ex(e), logger.ERROR) return False return True def write_ep_file(self, ep_obj): """ Generates and writes ep_obj's metadata under the given path with the given filename root. Uses the episode's name with the extension in _ep_nfo_extension. ep_obj: TVEpisode object for which to create the metadata file_name_path: The file name to use for this metadata. Note that the extension will be automatically added based on _ep_nfo_extension. This should include an absolute path. Note that this method expects that _ep_data will return an ElementTree object. If your _ep_data returns data in another format you'll need to override this method. """ data = self._ep_data(ep_obj) if not data: return False nfo_file_path = self.get_episode_file_path(ep_obj) nfo_file_dir = ek.ek(os.path.dirname, nfo_file_path) try: if not ek.ek(os.path.isdir, nfo_file_dir): logger.log(u"Metadata dir didn't exist, creating it at " + nfo_file_dir, logger.DEBUG) ek.ek(os.makedirs, nfo_file_dir) helpers.chmodAsParent(nfo_file_dir) logger.log(u"Writing episode nfo file to " + nfo_file_path, logger.DEBUG) nfo_file = ek.ek(open, nfo_file_path, 'w') data.write(nfo_file, encoding="utf-8") nfo_file.close() helpers.chmodAsParent(nfo_file_path) except IOError as e: logger.log(u"Unable to write file to " + nfo_file_path + " - are you sure the folder is writable? " + ex(e), logger.ERROR) return False return True def save_thumbnail(self, ep_obj): """ Retrieves a thumbnail and saves it to the correct spot. This method should not need to be overridden by implementing classes, changing get_episode_thumb_path and _get_episode_thumb_url should suffice. ep_obj: a TVEpisode object for which to generate a thumbnail """ file_path = self.get_episode_thumb_path(ep_obj) if not file_path: logger.log(u"Unable to find a file path to use for this thumbnail, not generating it", logger.DEBUG) return False thumb_url = self._get_episode_thumb_url(ep_obj) # if we can't find one then give up if not thumb_url: logger.log(u"No thumb is available for this episode, not creating a thumb", logger.DEBUG) return False thumb_data = metadata_helpers.getShowImage(thumb_url, showName=ep_obj.show.name) result = self._write_image(thumb_data, file_path) if not result: return False for cur_ep in [ep_obj] + ep_obj.relatedEps: cur_ep.hastbn = True return True def save_fanart(self, show_obj, which=None): """ Downloads a fanart image and saves it to the filename specified by fanart_name inside the show's root folder. show_obj: a TVShow object for which to download fanart """ # use the default fanart name fanart_path = self.get_fanart_path(show_obj) fanart_data = self._retrieve_show_image('fanart', show_obj, which) if not fanart_data: logger.log(u"No fanart image was retrieved, unable to write fanart", logger.DEBUG) return False return self._write_image(fanart_data, fanart_path) def save_poster(self, show_obj, which=None): """ Downloads a poster image and saves it to the filename specified by poster_name inside the show's root folder. show_obj: a TVShow object for which to download a poster """ # use the default poster name poster_path = self.get_poster_path(show_obj) poster_data = self._retrieve_show_image('poster', show_obj, which) if not poster_data: logger.log(u"No show poster image was retrieved, unable to write poster", logger.DEBUG) return False return self._write_image(poster_data, poster_path) def save_banner(self, show_obj, which=None): """ Downloads a banner image and saves it to the filename specified by banner_name inside the show's root folder. show_obj: a TVShow object for which to download a banner """ # use the default banner name banner_path = self.get_banner_path(show_obj) banner_data = self._retrieve_show_image('banner', show_obj, which) if not banner_data: logger.log(u"No show banner image was retrieved, unable to write banner", logger.DEBUG) return False return self._write_image(banner_data, banner_path) def save_season_posters(self, show_obj, season): """ Saves all season posters to disk for the given show. show_obj: a TVShow object for which to save the season thumbs Cycles through all seasons and saves the season posters if possible. """ season_dict = self._season_image_dict(show_obj, season, 'seasons') result = [] # Returns a nested dictionary of season art with the season # number as primary key. It's really overkill but gives the option # to present to user via ui to pick down the road. for cur_season in season_dict: cur_season_art = season_dict[cur_season] if 0 == len(cur_season_art): continue # Just grab whatever's there for now art_id, season_url = cur_season_art.popitem() # @UnusedVariable season_poster_file_path = self.get_season_poster_path(show_obj, cur_season) if not season_poster_file_path: logger.log(u'Path for season ' + str(cur_season) + ' came back blank, skipping this season', logger.DEBUG) continue season_data = metadata_helpers.getShowImage(season_url, showName=show_obj.name) if not season_data: logger.log(u'No season poster data available, skipping this season', logger.DEBUG) continue result = result + [self._write_image(season_data, season_poster_file_path)] if result: return all(result) return False def save_season_banners(self, show_obj, season): """ Saves all season banners to disk for the given show. show_obj: a TVShow object for which to save the season thumbs Cycles through all seasons and saves the season banners if possible. """ season_dict = self._season_image_dict(show_obj, season, 'seasonwides') result = [] # Returns a nested dictionary of season art with the season # number as primary key. It's really overkill but gives the option # to present to user via ui to pick down the road. for cur_season in season_dict: cur_season_art = season_dict[cur_season] if 0 == len(cur_season_art): continue # Just grab whatever's there for now art_id, season_url = cur_season_art.popitem() # @UnusedVariable season_banner_file_path = self.get_season_banner_path(show_obj, cur_season) if not season_banner_file_path: logger.log(u'Path for season ' + str(cur_season) + ' came back blank, skipping this season', logger.DEBUG) continue season_data = metadata_helpers.getShowImage(season_url, showName=show_obj.name) if not season_data: logger.log(u'No season banner data available, skipping this season', logger.DEBUG) continue result = result + [self._write_image(season_data, season_banner_file_path)] if result: return all(result) return False def save_season_all_poster(self, show_obj, which=None): # use the default season all poster name poster_path = self.get_season_all_poster_path(show_obj) poster_data = self._retrieve_show_image('poster', show_obj, which) if not poster_data: logger.log(u"No show poster image was retrieved, unable to write season all poster", logger.DEBUG) return False return self._write_image(poster_data, poster_path) def save_season_all_banner(self, show_obj, which=None): # use the default season all banner name banner_path = self.get_season_all_banner_path(show_obj) banner_data = self._retrieve_show_image('banner', show_obj, which) if not banner_data: logger.log(u"No show banner image was retrieved, unable to write season all banner", logger.DEBUG) return False return self._write_image(banner_data, banner_path) def _write_image(self, image_data, image_path): """ Saves the data in image_data to the location image_path. Returns True/False to represent success or failure. image_data: binary image data to write to file image_path: file location to save the image to """ # don't bother overwriting it if ek.ek(os.path.isfile, image_path): logger.log(u"Image already exists, not downloading", logger.DEBUG) return False if not image_data: logger.log(u"Unable to retrieve image, skipping", logger.WARNING) return False image_dir = ek.ek(os.path.dirname, image_path) try: if not ek.ek(os.path.isdir, image_dir): logger.log(u"Metadata dir didn't exist, creating it at " + image_dir, logger.DEBUG) ek.ek(os.makedirs, image_dir) helpers.chmodAsParent(image_dir) outFile = ek.ek(open, image_path, 'wb') outFile.write(image_data) outFile.close() helpers.chmodAsParent(image_path) except IOError as e: logger.log( u"Unable to write image to " + image_path + " - are you sure the show folder is writable? " + ex(e), logger.ERROR) return False return True def _retrieve_show_image(self, image_type, show_obj, which=None): """ Gets an image URL from theTVDB.com, fanart.tv and TMDB.com, downloads it and returns the data. If type is fanart, multiple image src urls are returned instead of a single data image. image_type: type of image to retrieve (currently supported: fanart, poster, banner, poster_thumb, banner_thumb) show_obj: a TVShow object to use when searching for the image which: optional, a specific numbered poster to look for Returns: the binary image data if available, or else None """ indexer_lang = show_obj.lang try: # There's gotta be a better way of doing this but we don't wanna # change the language value elsewhere lINDEXER_API_PARMS = sickbeard.indexerApi(show_obj.indexer).api_params.copy() if image_type.startswith('fanart'): lINDEXER_API_PARMS['fanart'] = True elif image_type.startswith('poster'): lINDEXER_API_PARMS['posters'] = True else: lINDEXER_API_PARMS['banners'] = True lINDEXER_API_PARMS['dvdorder'] = 0 != show_obj.dvdorder if indexer_lang and not 'en' == indexer_lang: lINDEXER_API_PARMS['language'] = indexer_lang t = sickbeard.indexerApi(show_obj.indexer).indexer(**lINDEXER_API_PARMS) indexer_show_obj = t[show_obj.indexerid, False] except (sickbeard.indexer_error, IOError) as e: logger.log(u"Unable to look up show on " + sickbeard.indexerApi( show_obj.indexer).name + ", not downloading images: " + ex(e), logger.ERROR) return None return_links = False if 'fanart_all' == image_type: return_links = True image_type = 'fanart' if image_type not in ('poster', 'banner', 'fanart', 'poster_thumb', 'banner_thumb'): logger.log(u"Invalid image type " + str(image_type) + ", couldn't find it in the " + sickbeard.indexerApi( show_obj.indexer).name + " object", logger.ERROR) return None image_urls = [] init_url = None if 'poster_thumb' == image_type: if getattr(indexer_show_obj, 'poster', None) is not None: image_url = re.sub('posters', '_cache/posters', indexer_show_obj['poster']) if image_url: image_urls.append(image_url) for item in self._fanart_urls_from_show(show_obj, image_type, indexer_lang, True) or []: image_urls.append(item[2]) if 0 == len(image_urls): for item in self._tmdb_image_url(show_obj, image_type) or []: image_urls.append(item[2]) elif 'banner_thumb' == image_type: if getattr(indexer_show_obj, 'banner', None) is not None: image_url = re.sub('graphical', '_cache/graphical', indexer_show_obj['banner']) if image_url: image_urls.append(image_url) for item in self._fanart_urls_from_show(show_obj, image_type, indexer_lang, True) or []: image_urls.append(item[2]) else: for item in self._fanart_urls_from_show(show_obj, image_type, indexer_lang) or []: image_urls.append(item[2]) if getattr(indexer_show_obj, image_type, None) is not None: image_url = indexer_show_obj[image_type] if image_url: image_urls.append(image_url) if 'poster' == image_type: init_url = image_url if 0 == len(image_urls) or 'fanart' == image_type: for item in self._tmdb_image_url(show_obj, image_type) or []: image_urls.append('%s?%s' % (item[2], item[0])) image_data = len(image_urls) or None if image_data: if return_links: return image_urls else: image_data = metadata_helpers.getShowImage((init_url, image_urls[0])[None is init_url], which, show_obj.name) if None is not image_data: return image_data return None @staticmethod def _season_image_dict(show_obj, season, image_type): """ image_type : Type of image to fetch, 'seasons' or 'seasonwides' image_type type : String Should return a dict like: result = {: {1: '', 2: , ...},} """ result = {} try: # There's gotta be a better way of doing this but we don't wanna # change the language value elsewhere lINDEXER_API_PARMS = sickbeard.indexerApi(show_obj.indexer).api_params.copy() lINDEXER_API_PARMS[image_type] = True lINDEXER_API_PARMS['dvdorder'] = 0 != show_obj.dvdorder if 'en' != getattr(show_obj, 'lang', None): lINDEXER_API_PARMS['language'] = show_obj.lang t = sickbeard.indexerApi(show_obj.indexer).indexer(**lINDEXER_API_PARMS) indexer_show_obj = t[show_obj.indexerid] except (sickbeard.indexer_error, IOError) as e: logger.log(u'Unable to look up show on ' + sickbeard.indexerApi( show_obj.indexer).name + ', not downloading images: ' + ex(e), logger.ERROR) return result season_images = getattr(indexer_show_obj, '_banners', {}).get( ('season', 'seasonwide')['seasonwides' == image_type], {}).get(season, {}) for image_id in season_images.keys(): if season not in result: result[season] = {} result[season][image_id] = season_images[image_id]['_bannerpath'] return result def retrieveShowMetadata(self, folder): """ Used only when mass adding Existing Shows, using previously generated Show metadata to reduce the need to query TVDB. """ from sickbeard.indexers.indexer_config import INDEXER_TVDB empty_return = (None, None, None) metadata_path = ek.ek(os.path.join, folder, self._show_metadata_filename) if not ek.ek(os.path.isdir, folder) or not ek.ek(os.path.isfile, metadata_path): logger.log(u"Can't load the metadata file from " + repr(metadata_path) + ", it doesn't exist", logger.DEBUG) return empty_return logger.log(u"Loading show info from metadata file in " + folder, logger.DEBUG) try: with ek.ek(open, metadata_path, 'r') as xmlFileObj: showXML = etree.ElementTree(file=xmlFileObj) if showXML.findtext('title') == None \ or (showXML.findtext('tvdbid') == None and showXML.findtext('id') == None) \ and showXML.findtext('indexer') == None: logger.log(u"Invalid info in tvshow.nfo (missing name or id):" \ + str(showXML.findtext('title')) + " " \ + str(showXML.findtext('indexer')) + " " \ + str(showXML.findtext('tvdbid')) + " " \ + str(showXML.findtext('id'))) return empty_return name = showXML.findtext('title') try: indexer = int(showXML.findtext('indexer')) except: indexer = None if None is not showXML.findtext('tvdbid'): indexer_id = int(showXML.findtext('tvdbid')) indexer = INDEXER_TVDB elif None is not showXML.findtext('id'): indexer_id = int(showXML.findtext('id')) try: indexer = INDEXER_TVDB if [s for s in showXML.findall('.//*') if s.text and s.text.find('thetvdb.com') != -1] else indexer except: pass else: logger.log(u"Empty or field in NFO, unable to find a ID", logger.WARNING) return empty_return if indexer_id is None: logger.log(u"Invalid Indexer ID (" + str(indexer_id) + "), not using metadata file", logger.WARNING) return empty_return except Exception as e: logger.log( u"There was an error parsing your existing metadata file: '" + metadata_path + "' error: " + ex(e), logger.WARNING) return empty_return return (indexer_id, name, indexer) @staticmethod def _tmdb_image_url(show, image_type): types = {'poster': 'poster_path', 'banner': None, 'fanart': 'backdrop_path', 'fanart_all': 'backdrops', 'poster_thumb': 'poster_path', 'banner_thumb': None} # get TMDB configuration info tmdb = TMDB(sickbeard.TMDB_API_KEY) config = tmdb.Configuration() response = config.info() base_url = response['images']['base_url'] sizes = response['images']['poster_sizes'] def size_str_to_int(x): return float('inf') if x == 'original' else int(x[1:]) max_size = max(sizes, key=size_str_to_int) try: itemlist = [] for (src, name) in [(indexer_config.INDEXER_TVDB, 'tvdb'), (indexer_config.INDEXER_IMDB, 'imdb'), (indexer_config.INDEXER_TVRAGE, 'tvrage')]: is_id = show.ids.get(src, {}).get('id', None) if not is_id: continue result = tmdb.Find(is_id).info({'external_source': '%s_id' % name}) if 'tv_results' not in result or not len(result['tv_results']): continue tmdb_show = result['tv_results'][0] if 'fanart' == image_type: tv_obj = tmdb.TV(tmdb_show['id']) rjson_info = tv_obj.info({'append_to_response': 'images', 'include_image_language': 'en,null'}) rjson_img = rjson_info['images'] for art in rjson_img[types['fanart_all']] or []: if 'vote_average' not in art or 'file_path' not in art: continue art_likes = art['vote_average'] url = u'%s%s%s' % (base_url, max_size, art['file_path']) itemlist += [[tmdb_show['id'], art_likes, url]] itemlist.sort(lambda a, b: cmp((a[1]), (b[1])), reverse=True) return itemlist elif tmdb_show[types[image_type]]: return [[tmdb_show['id'], tmdb_show['vote_average'], '%s%s%s' % (base_url, max_size, tmdb_show[types[image_type]])]] except (StandardError, Exception): pass logger.log(u'Could not find any %s images on TMDB for %s' % (image_type, show.name), logger.DEBUG) def _fanart_urls_from_show(self, show, image_type='banner', lang='en', thumb=False): try: tvdb_id = show.ids.get(indexer_config.INDEXER_TVDB, {}).get('id', None) if tvdb_id: return self._fanart_urls(tvdb_id, image_type, lang, thumb) except (StandardError, Exception): pass logger.log(u'Could not find any %s images on Fanart.tv for %s' % (image_type, show.name), logger.DEBUG) @staticmethod def _fanart_urls(tvdb_id, image_type='banner', lang='en', thumb=False): types = {'poster': fanart.TYPE.TV.POSTER, 'banner': fanart.TYPE.TV.BANNER, 'fanart': fanart.TYPE.TV.BACKGROUND, 'poster_thumb': fanart.TYPE.TV.POSTER, 'banner_thumb': fanart.TYPE.TV.BANNER} try: if tvdb_id: request = fanartRequest(apikey=sickbeard.FANART_API_KEY, tvdb_id=tvdb_id) resp = request.response() itemlist = [] for art in resp[types[image_type]]: if ('url' in art and 10 > len(art['url']))\ or ('lang' in art and lang != art['lang']): continue try: art_id = int(art['id']) art_likes = int(art['likes']) url = (art['url'], re.sub('/fanart/', '/preview/', art['url']))[thumb] except: continue itemlist += [[art_id, art_likes, url]] itemlist.sort(lambda a, b: cmp((a[1], a[0]), (b[1], b[0])), reverse=True) return itemlist except Exception as e: raise def retrieve_show_image(self, image_type, show_obj, which=None): return self._retrieve_show_image(image_type=image_type, show_obj=show_obj, which=which) def write_image(self, image_data, image_path): return self._write_image(image_data=image_data, image_path=image_path)