# # 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 <http://www.gnu.org/licenses/>. import datetime import glob import os.path import re import zlib import exceptions_helper from exceptions_helper import ex import sickgear import sg_helpers from . import db, logger from .metadata.generic import GenericMetadata from .sgdatetime import SGDatetime from .indexers.indexer_config import TVINFO_TVDB, TVINFO_TVMAZE, TVINFO_TMDB, TVINFO_IMDB from six import itervalues, iteritems # noinspection PyUnreachableCode if False: from typing import AnyStr, Optional, Tuple, Union from .tv import TVShow, Person, Character from six import integer_types from .metadata.generic import ShowInfosDict from lib.hachoir.parser import createParser, guessParser from lib.hachoir.metadata import extractMetadata from lib.hachoir.stream import StringInputStream cache_img_base = {'tvmaze': TVINFO_TVMAZE, 'themoviedb': TVINFO_TMDB, 'thetvdb': TVINFO_TVDB, 'imdb': TVINFO_IMDB} cache_img_src = {TVINFO_TMDB: 'tmdb', TVINFO_TVDB: 'tvdb', TVINFO_TVMAZE: 'tvmaze', TVINFO_IMDB: 'imdb'} class ImageCache(object): base_dir = None # type: AnyStr or None shows_dir = None # type: AnyStr or None persons_dir = None # type: Optional[AnyStr] characters_dir = None # type: Optional[AnyStr] def __init__(self): if None is ImageCache.base_dir and os.path.exists(sickgear.CACHE_DIR): ImageCache.base_dir = os.path.abspath(os.path.join(sickgear.CACHE_DIR, 'images')) ImageCache.shows_dir = os.path.abspath(os.path.join(self.base_dir, 'shows')) ImageCache.persons_dir = self._persons_dir() ImageCache.characters_dir = self._characters_dir() def __del__(self): pass # @staticmethod # def _cache_dir(): # """ # Builds up the full path to the image cache directory # """ # return os.path.abspath(os.path.join(sickgear.CACHE_DIR, 'images')) @staticmethod def _persons_dir(): # type: (...) -> AnyStr return os.path.join(sickgear.CACHE_DIR, 'images', 'person') @staticmethod def _characters_dir(): # type: (...) -> AnyStr return os.path.join(sickgear.CACHE_DIR, 'images', 'characters') def _fanart_dir(self, tvid=None, prodid=None): # type: (int, int) -> AnyStr """ Builds up the full path to the fanart image cache directory :param tvid: TV info source ID to use in the file name :type tvid: int :param prodid: Show ID to use in the file name :type prodid: int or long :return: path :rtype: AnyStr or None """ if None not in (tvid, prodid): return os.path.abspath(os.path.join(self.shows_dir, '%s-%s' % (tvid, prodid), 'fanart')) def _thumbnails_dir(self, tvid, prodid): # type: (int, int) -> AnyStr """ Builds up the full path to the thumbnails image cache directory :param tvid: TV info source ID to use in the file name :type tvid: int :param prodid: Show ID to use in the file name :type prodid: int or long :return: path :rtype: AnyStr """ return os.path.abspath(os.path.join(self.shows_dir, '%s-%s' % (tvid, prodid), 'thumbnails')) @staticmethod def _person_base_name(person_obj): # type: (Person) -> AnyStr base_id = next((v for k, v in iteritems(cache_img_base) if k in (person_obj.image_url or '') or person_obj.thumb_url), 0) return '%s-%s' % (cache_img_src.get(base_id, base_id), person_obj.ids.get(base_id) or sg_helpers.sanitize_filename(person_obj.name)) @staticmethod def _character_base_name(character_obj, show_obj, tvid=None, proid=None): # type: (Character, TVShow, integer_types, integer_types) -> AnyStr return '%s-%s' % (cache_img_src.get(tvid or show_obj.tvid, tvid or show_obj.tvid), character_obj.ids.get(tvid or show_obj.tvid) or sg_helpers.sanitize_filename(character_obj.name)) def person_path(self, person_obj, base_path=None): # type: (Optional[Person], AnyStr) -> AnyStr """ return image filename :param person_obj: :param base_path: """ filename = '%s.jpg' % base_path or self._person_base_name(person_obj) return os.path.join(self.persons_dir, filename) def person_thumb_path(self, person_obj, base_path=None): # type: (Optional[Person], AnyStr) -> AnyStr """ return thumb image filename :param person_obj: :param base_path: """ filename = '%s_thumb.jpg' % base_path or self._person_base_name(person_obj) return os.path.join(self.persons_dir, filename) def person_both_paths(self, person_obj): # type: (Person) -> Tuple[AnyStr, AnyStr] """ return tuple image, thumb filenames :param person_obj: """ base_path = self._person_base_name(person_obj) return self.person_path(None, base_path=base_path), self.person_thumb_path(None, base_path=base_path) def character_path(self, character_obj, show_obj, base_path=None): # type: (Optional[Character], Optional[TVShow], AnyStr) -> AnyStr """ return image filename :param character_obj: :param show_obj: :param base_path: """ filename = '%s.jpg' % base_path or self._character_base_name(character_obj, show_obj) return os.path.join(self.characters_dir, filename) def character_thumb_path(self, character_obj, show_obj, base_path=None): # type: (Optional[Character], Optional[TVShow], AnyStr) -> AnyStr """ return thumb image filename :param character_obj: :param show_obj: :param base_path: """ filename = '%s_thumb.jpg' % base_path or self._character_base_name(character_obj, show_obj) return os.path.join(self.characters_dir, filename) def character_both_path(self, character_obj, show_obj=None, tvid=None, proid=None, person_obj=None): # type: (Character, TVShow, integer_types, integer_types, Person) -> Tuple[AnyStr, AnyStr] """ returns tuple image, thumb image :param character_obj: :param show_obj: :param tvid: :param proid: :param person_obj: """ base_path = self._character_base_name(character_obj, show_obj=show_obj, tvid=tvid, proid=proid) from .tv import Person if isinstance(person_obj, Person): person_base = self._person_base_name(person_obj) if person_base: base_path = '%s-%s' % (base_path, person_base) return self.character_path(None, None, base_path=base_path), \ self.character_thumb_path(None, None, base_path=base_path) def poster_path(self, tvid, prodid): # type: (int, int) -> AnyStr """ Builds up the path to a poster cache for a given tvid prodid :param tvid: TV info source ID to use in the file name :type tvid: int :param prodid: Show ID to use in the file name :type prodid: int or long :return: a full path to the cached poster file for the given tvid prodid :rtype: AnyStr """ return os.path.join(self.shows_dir, '%s-%s' % (tvid, prodid), 'poster.jpg') def banner_path(self, tvid, prodid): # type: (int, int) -> AnyStr """ Builds up the path to a banner cache for a given tvid prodid :param tvid: TV info source ID to use in the file name :type tvid: int :param prodid: Show ID to use in the file name :type prodid: int or long :return: a full path to the cached banner file for the given tvid prodid :rtype: AnyStr """ return os.path.join(self.shows_dir, '%s-%s' % (tvid, prodid), 'banner.jpg') def fanart_path(self, tvid, prodid, prefix=''): # type: (int, int, Optional[AnyStr]) -> AnyStr """ Builds up the path to a fanart cache for a given tvid prodid :param tvid: TV info source ID to use in the file name :type tvid: int :param prodid: Show ID to use in the file name :type prodid: int or long :param prefix: String to insert at the start of a filename (e.g. '001.') :type prefix: AnyStr :return: a full path to the cached fanart file for the given tvid prodid :rtype: AnyStr """ return os.path.join(self._fanart_dir(tvid, prodid), '%s%s' % (prefix, 'fanart.jpg')) def poster_thumb_path(self, tvid, prodid): # type: (int, int) -> AnyStr """ Builds up the path to a poster cache for a given tvid prodid :param tvid: TV info source ID to use in the file name :type tvid: int :param prodid: Show ID to use in the file name :type prodid: int or long :return: a full path to the cached poster file for the given tvid prodid :rtype: AnyStr """ return os.path.join(self._thumbnails_dir(tvid, prodid), 'poster.jpg') def banner_thumb_path(self, tvid, prodid): # type: (int, int) -> AnyStr """ Builds up the path to a poster cache for a given tvid prodid :param tvid: TV info source ID to use in the file name :type tvid: int :param prodid: Show ID to use in the file name :type prodid: int or long :return: a full path to the cached poster file for the given tvid prodid :rtype: AnyStr """ return os.path.join(self._thumbnails_dir(tvid, prodid), 'banner.jpg') @staticmethod def has_file(image_file): # type: (AnyStr) -> bool """ :param image_file: image file :type image_file: AnyStr :return: true if an image_file exists :rtype: bool """ result = [] for filename in glob.glob(image_file): result.append(os.path.isfile(filename) and filename) logger.debug(f'Found cached {filename}') not any(result) and logger.debug(f'No cache for {image_file}') return any(result) def has_poster(self, tvid, prodid): # type: (int, int) -> bool """ :param tvid: tvid :type tvid: int :param prodid: prodid :type prodid: int or long :return: true if a cached poster exists for the given tvid prodid :rtype: bool """ return self.has_file(self.poster_path(tvid, prodid)) def has_banner(self, tvid, prodid): # type: (int, int) -> bool """ :param tvid: tvid :type tvid: int :param prodid: prodid :type prodid: int or long :return: true if a cached banner exists for the given tvid prodid :rtype: bool """ return self.has_file(self.banner_path(tvid, prodid)) def has_fanart(self, tvid, prodid): # type: (int, int) -> bool """ :param tvid: tvid :type tvid: int :param prodid: prodid :type prodid: int or long :return: true if a cached fanart exists for the given tvid prodid :rtype: bool """ return self.has_file(self.fanart_path(tvid, prodid).replace('fanart.jpg', '001.*.fanart.jpg')) def has_poster_thumbnail(self, tvid, prodid): # type: (int, int) -> bool """ :param tvid: tvid :type tvid: int :param prodid: prodid :type prodid: int or long :return: true if a cached poster thumbnail exists for the given tvid prodid :rtype: bool """ return self.has_file(self.poster_thumb_path(tvid, prodid)) def has_banner_thumbnail(self, tvid, prodid): # type: (int, int) -> bool """ :param tvid: tvid :type tvid: int :param prodid: prodid :type prodid: int or long :return: true if a cached banner exists for the given tvid prodid :rtype: bool """ return self.has_file(self.banner_thumb_path(tvid, prodid)) BANNER = 1 POSTER = 2 BANNER_THUMB = 3 POSTER_THUMB = 4 FANART = 5 # img_type_str = { # 1: 'Banner', # 2: 'Poster', # 3: 'Banner thumb', # 4: 'Poster thumb', # 5: 'Fanart' # } @staticmethod def get_img_dimensions(image, is_binary=False): # type: (AnyStr, bool) -> Optional[Tuple[integer_types, integer_types, float]] """ get image dimensions: width, height, ratio :param image: image file or data :param is_binary: is data instead of path """ if not is_binary and not os.path.isfile(image): logger.warning(f'File not found to determine image type of {image}') return if not image: logger.warning('No Image Data to determinate image type') return try: if is_binary: img_parser = guessParser(StringInputStream(image)) else: img_parser = createParser(image) img_parser.parse_comments = False img_parser.parse_exif = False img_parser.parse_photoshop_content = False img_metadata = extractMetadata(img_parser) except (BaseException, Exception) as e: logger.debug(f'Unable to extract metadata from {image}, not using file. Error: {ex(e)}') return if not img_metadata: if is_binary: msg = 'Image Data' else: msg = image logger.debug(f'Unable to extract metadata from {msg}, not using file') return width = img_metadata.get('width') height = img_metadata.get('height') img_ratio = float(width) / float(height) if not is_binary: # noinspection PyProtectedMember img_parser.stream._input.close() return width, height, img_ratio def which_type(self, image, is_binary=False): # type: (AnyStr, bool) -> Optional[int] """ Analyzes the image provided and attempts to determine whether it is a poster, banner or fanart. :param image: full path to the image or image data :param is_binary: is binary data instead path to image :return: BANNER, POSTER, FANART or None if image type is not detected or doesn't exist :rtype: int """ result = self.get_img_dimensions(image, is_binary) if not result: return img_width, img_height, img_ratio = result if is_binary: msg_data = '<Image Data>' else: msg_data = image.replace('%', '%%') msg_success = 'Treating image as %s' \ + ' with extracted aspect ratio from %s' % msg_data # most posters are around 0.68 width/height ratio (eg. 680/1000) if 0.55 <= img_ratio <= 0.8: logger.debug(msg_success % 'poster') return self.POSTER # most banners are around 5.4 width/height ratio (eg. 758/140) if 5 <= img_ratio <= 6: logger.debug(msg_success % 'banner') return self.BANNER # most fan art are around 1.7 width/height ratio (eg. 1280/720 or 1920/1080) if 1.7 <= img_ratio <= 1.8: if 500 < img_width: logger.debug(msg_success % 'fanart') return self.FANART logger.warning('Skipped image with fanart aspect ratio but less than 500 pixels wide') else: logger.warning(f'Skipped image with useless ratio {img_ratio}') def should_refresh(self, image_type=None, provider='local'): # type: (int, Optional[AnyStr]) -> bool """ :param image_type: image type :type image_type: int :param provider: provider name :type provider: AnyStr :return: :rtype: bool """ my_db = db.DBConnection('cache.db', row_type='dict') sql_result = my_db.select('SELECT time FROM lastUpdate WHERE provider = ?', ['imsg_%s_%s' % ((image_type, self.FANART)[None is image_type], provider)]) if sql_result: minutes_iv = 60 * 3 # daily_interval = 60 * 60 * 23 iv = minutes_iv now_stamp = SGDatetime.timestamp_near() the_time = int(sql_result[0]['time']) return now_stamp - the_time > iv return True def set_last_refresh(self, image_type=None, provider='local'): # type: (int, Optional[AnyStr]) -> None """ :param image_type: image type :type image_type: int or None :param provider: provider name :type provider: AnyStr """ my_db = db.DBConnection('cache.db') my_db.upsert('lastUpdate', {'time': SGDatetime.timestamp_near()}, {'provider': 'imsg_%s_%s' % ((image_type, self.FANART)[None is image_type], provider)}) def _cache_image_from_file(self, image_path, img_type, tvid, prodid, prefix='', move_file=False): # type: (AnyStr, int, int, int, Optional[AnyStr], Optional[bool]) -> Union[AnyStr, bool] """ Takes the image provided and copies or moves it to the cache folder :param image_path: path to the image to cache :type image_path: AnyStr :param img_type: BANNER, POSTER, or FANART :type img_type: int :param tvid: id of the TV info source this image belongs to :type tvid: int :param prodid: id of the show this image belongs to :type prodid: int or long :param prefix: string to use at the start of a filename (e.g. '001.') :type prefix: AnyStr :param move_file: True if action is to move the file else file should be copied :type move_file: bool :return: full path to cached file or None :rtype: AnyStr """ # generate the path based on the type, tvid and prodid fanart_dir = [] id_args = (tvid, prodid) if self.POSTER == img_type: dest_path = self.poster_path(*id_args) dest_thumb_path = self.poster_thumb_path(*id_args) elif self.BANNER == img_type: dest_path = self.banner_path(*id_args) dest_thumb_path = self.banner_thumb_path(*id_args) elif self.FANART == img_type: dest_thumb_path = None with open(image_path, mode='rb') as resource: crc = '%05X' % (zlib.crc32(resource.read()) & 0xFFFFFFFF) dest_path = self.fanart_path(*id_args + (prefix,)).replace('.fanart.jpg', '.%s.fanart.jpg' % crc) fanart_dir = [self._fanart_dir(*id_args)] else: logger.error(f'Invalid cache image type: {img_type}') return False for cache_dir in [self.shows_dir, self._thumbnails_dir(*id_args)] + fanart_dir: sg_helpers.make_path(cache_dir) logger.log(f'{("Copy", "Mov")[move_file]}ing from {image_path} to {dest_path}') # copy poster, banner as thumb, even if moved we need to duplicate the images if img_type in (self.POSTER, self.BANNER) and dest_thumb_path: sg_helpers.copy_file(image_path, dest_thumb_path) if move_file: sg_helpers.move_file(image_path, dest_path) else: sg_helpers.copy_file(image_path, dest_path) return os.path.isfile(dest_path) and dest_path or None def _cache_info_source_images(self, show_obj, img_type, num_files=0, max_files=500, force=False, show_infos=None): # type: (TVShow, int, int, int, bool, ShowInfosDict) -> bool """ Retrieves an image of the type specified from TV info source and saves it to the cache folder :param show_obj: TVShow object to cache an image for :param img_type: BANNER, POSTER, or FANART :param num_files: :param max_files: :param force: :param show_infos: dict of showinfo objects to use :return: bool representing success """ # generate the path based on the type, tvid and prodid arg_tvid_prodid = (show_obj.tvid, show_obj.prodid) dest_thumb_path = None if self.POSTER == img_type: img_type_name = 'poster' dest_path = self.poster_path(*arg_tvid_prodid) dest_thumb_path = self.poster_thumb_path(*arg_tvid_prodid) elif self.BANNER == img_type: img_type_name = 'banner' dest_path = self.banner_path(*arg_tvid_prodid) dest_thumb_path = self.banner_thumb_path(*arg_tvid_prodid) elif self.FANART == img_type: img_type_name = 'fanart_all' dest_path = self.fanart_path(*arg_tvid_prodid).replace('fanart.jpg', '*') elif self.POSTER_THUMB == img_type: img_type_name = 'poster_thumb' dest_path = self.poster_thumb_path(*arg_tvid_prodid) elif self.BANNER_THUMB == img_type: img_type_name = 'banner_thumb' dest_path = self.banner_thumb_path(*arg_tvid_prodid) else: logger.error(f'Invalid cache image type: {img_type}') return False # retrieve the image from TV info source using the generic metadata class metadata_generator = GenericMetadata() if self.FANART == img_type: image_urls = metadata_generator.retrieve_show_image(img_type_name, show_obj, show_infos=show_infos) if None is image_urls: return False crcs = [] for cache_file_name in glob.glob(dest_path): with open(cache_file_name, mode='rb') as resource: crc = '%05X' % (zlib.crc32(resource.read()) & 0xFFFFFFFF) if crc not in crcs: crcs += [crc] success = 0 sources = [] sickgear.MEMCACHE.setdefault('cookies', {}) for image_url in image_urls or []: img_data = sg_helpers.get_url(image_url, nocache=True, as_binary=True, url_solver=sickgear.FLARESOLVERR_HOST, memcache_cookies=sickgear.MEMCACHE['cookies']) if None is img_data or self.FANART != self.which_type(img_data, is_binary=True): continue crc = '%05X' % (zlib.crc32(img_data) & 0xFFFFFFFF) if crc in crcs: continue crcs += [crc] img_source = ((((('', 'tvdb')['thetvdb.com' in image_url], 'tvrage')['tvrage.com' in image_url], 'fatv')['fanart.tv' in image_url], 'tmdb')['tmdb' in image_url], 'tvmaze')['tvmaze.com' in image_url] img_xtra = '' if 'tmdb' == img_source: match = re.search(r'(?:.*\?(\d+$))?', image_url, re.I | re.M) if match and None is not match.group(1): img_xtra = match.group(1) file_desc = '%03d%s.%s.' % (num_files, ('.%s%s' % (img_source, img_xtra), '')['' == img_source], crc) cur_file_path = self.fanart_path(show_obj.tvid, show_obj.prodid, file_desc) result = metadata_generator.write_image(img_data, cur_file_path) if img_source: sources += [img_source] num_files += (0, 1)[result] success += (0, 1)[result] if num_files > max_files: break total = len(glob.glob(dest_path)) logger.log(f'Saved {success} fanart images' f'{("", " from " + ", ".join([x for x in list(set(sources))]))[0 < len(sources)]}.' f' Cached {total} of max {sickgear.FANART_LIMIT} fanart file{sg_helpers.maybe_plural(total)}') return bool(success) image_urls = metadata_generator.retrieve_show_image(img_type_name, show_obj, return_links=True, show_infos=show_infos) if None is image_urls: return False result = None for image_url in image_urls or []: if isinstance(image_url, tuple) and img_type in (self.BANNER, self.POSTER): img_url, thumb_url = image_url else: img_url, thumb_url = image_url, None img_data = sg_helpers.get_url(img_url, nocache=True, as_binary=True) if None is img_data or img_type != self.which_type(img_data, is_binary=True): continue result = metadata_generator.write_image(img_data, dest_path, force=force) if img_type in (self.BANNER, self.POSTER) and dest_thumb_path: thumb_img_data = sg_helpers.get_url(img_url, nocache=True, as_binary=True) thumb_result = None if thumb_img_data: thumb_result = metadata_generator.write_image(thumb_img_data, dest_thumb_path, force=True) if not thumb_result: metadata_generator.write_image(img_data, dest_thumb_path, force=True) break if result: logger.log(f'Saved image type {img_type_name}') return result def fill_cache(self, show_obj, force=False): # type: (TVShow, Optional[bool]) -> Optional[bool] """ Caches all images for the given show. Copies them from the show dir if possible, or downloads them from TV info source if they aren't in the show dir. :param show_obj: TVShow object to cache images for :type show_obj: sickgear.tv.TVShow :param force: :type force: bool """ arg_tvid_prodid = (show_obj.tvid, show_obj.prodid) # check if any images are cached need_images = {self.POSTER: not self.has_poster(*arg_tvid_prodid) or force, self.BANNER: not self.has_banner(*arg_tvid_prodid) or force, self.FANART: 0 < sickgear.FANART_LIMIT and ( force or not self.has_fanart(*arg_tvid_prodid)), # use limit? shows less than a limit of say 50 would fail to fulfill images every day # '%03d.*' % sickgear.FANART_LIMIT self.POSTER_THUMB: not self.has_poster_thumbnail(*arg_tvid_prodid) or force, self.BANNER_THUMB: not self.has_banner_thumbnail(*arg_tvid_prodid) or force} if not any(itervalues(need_images)): logger.log(f'{show_obj.tvid_prodid}: No new cache images needed. Done.') return show_infos = GenericMetadata.gen_show_infos_dict(show_obj) void = False if not void and need_images[self.FANART]: cache_path = self.fanart_path(*arg_tvid_prodid).replace('fanart.jpg', '') # num_images = len(fnmatch.filter(os.listdir(cache_path), '*.jpg')) for cache_dir in glob.glob(cache_path): if show_obj.tvid_prodid in sickgear.FANART_RATINGS: del (sickgear.FANART_RATINGS[show_obj.tvid_prodid]) result = sg_helpers.remove_file(cache_dir, tree=True) if result: logger.debug(f'{result} cache file {cache_dir}') try: checked_files = [] crcs = [] for cur_provider in itervalues(sickgear.metadata_provider_dict): # check the show dir for poster or banner images and use them needed = [] if any([need_images[self.POSTER], need_images[self.BANNER]]): poster_path = cur_provider.get_poster_path(show_obj) if poster_path not in checked_files and os.path.isfile(poster_path): needed += [[False, poster_path]] if need_images[self.FANART]: fanart_path = cur_provider.get_fanart_path(show_obj) if fanart_path not in checked_files and os.path.isfile(fanart_path): needed += [[True, fanart_path]] if 0 == len(needed): break logger.debug(f'Checking for images from optional {cur_provider.name} metadata') for all_meta_provs, path_file in needed: checked_files += [path_file] cache_file_name = os.path.abspath(path_file) with open(cache_file_name, mode='rb') as resource: crc = '%05X' % (zlib.crc32(resource.read()) & 0xFFFFFFFF) if crc in crcs: continue crcs += [crc] cur_file_type = self.which_type(cache_file_name) if None is cur_file_type: continue logger.debug(f'Checking if image {cache_file_name} ' f'(type {str(cur_file_type)}' f' needs metadata: {("No", "Yes")[True is need_images[cur_file_type]]}' f')') if need_images.get(cur_file_type): need_images[cur_file_type] = ( (need_images[cur_file_type] + 1, 1)[isinstance(need_images[cur_file_type], bool)], False)[not all_meta_provs] if self.FANART == cur_file_type and \ (not sickgear.FANART_LIMIT or sickgear.FANART_LIMIT < need_images[cur_file_type]): continue logger.debug(f'Caching image found in the show directory to the image cache: {cache_file_name},' f' type {cur_file_type}') self._cache_image_from_file( cache_file_name, cur_file_type, *arg_tvid_prodid + (('%03d.' % need_images[cur_file_type], '')[ isinstance(need_images[cur_file_type], bool)],)) except exceptions_helper.ShowDirNotFoundException: logger.warning('Unable to search for images in show directory because it doesn\'t exist') # download images from TV info sources for image_type, name_type in [[self.POSTER, 'Poster'], [self.BANNER, 'Banner'], [self.FANART, 'Fanart']]: max_files = (500, sickgear.FANART_LIMIT)[self.FANART == image_type] if not max_files or max_files < need_images[image_type]: continue logger.debug(f'Seeing if we still need an image of type {name_type}:' f' {("No", "Yes")[True is need_images[image_type]]}') if need_images[image_type]: file_num = (need_images[image_type] + 1, 1)[isinstance(need_images[image_type], bool)] if file_num <= max_files: self._cache_info_source_images(show_obj, image_type, file_num, max_files, force=force, show_infos=show_infos) logger.log('Done cache check')