# -*- coding: utf-8 -*- from __future__ import absolute_import, division, unicode_literals from logging import getLogger from ..plex_api import API from ..plex_db import PlexDB from ..kodi_db import KodiVideoDB from .. import backgroundthread, utils from .. import itemtypes, plex_functions as PF, variables as v, app LOG = getLogger('PLEX.sync.fanart') SUPPORTED_TYPES = (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW) SYNC_FANART = (utils.settings('FanartTV') == 'true' and utils.settings('usePlexArtwork') == 'true') PREFER_KODI_COLLECTION_ART = utils.settings('PreferKodiCollectionArt') == 'false' BATCH_SIZE = 500 class FanartThread(backgroundthread.KillableThread): """ This will potentially take hours! """ def __init__(self, callback, refresh=False): self.callback = callback self.refresh = refresh super(FanartThread, self).__init__() def should_suspend(self): return self._suspended or app.APP.is_playing_video def run(self): LOG.info('Starting FanartThread') app.APP.register_fanart_thread(self) try: self._run() except Exception: utils.ERROR(notify=True) finally: app.APP.deregister_fanart_thread(self) def _loop(self): for typus in SUPPORTED_TYPES: offset = 0 while True: with PlexDB() as plexdb: # Keep DB connection open only for a short period of time! if self.refresh: batch = list(plexdb.every_plex_id(typus, offset, BATCH_SIZE)) else: batch = list(plexdb.missing_fanart(typus, offset, BATCH_SIZE)) for plex_id in batch: # Do the actual, time-consuming processing if self.should_suspend() or self.should_cancel(): return False process_fanart(plex_id, typus, self.refresh) if len(batch) < BATCH_SIZE: break offset += BATCH_SIZE return True def _run(self): finished = False while not finished: finished = self._loop() if self.wait_while_suspended(): break LOG.info('FanartThread finished: %s', finished) self.callback(finished) class FanartTask(backgroundthread.Task): """ This task will also be executed while library sync is suspended! """ def setup(self, plex_id, plex_type, refresh=False): self.plex_id = plex_id self.plex_type = plex_type self.refresh = refresh def run(self): process_fanart(self.plex_id, self.plex_type, self.refresh) def process_fanart(plex_id, plex_type, refresh=False): """ Will look for additional fanart for the plex_type item with plex_id. Will check if we already got all artwork and only look if some are indeed missing. Will set the fanart_synced flag in the Plex DB if successful. """ done = False try: artworks = None with PlexDB() as plexdb: db_item = plexdb.item_by_id(plex_id, plex_type) if not db_item: LOG.error('Could not get Kodi id for plex id %s', plex_id) return if not refresh: with KodiVideoDB() as kodidb: artworks = kodidb.get_art(db_item['kodi_id'], db_item['kodi_type']) # Check if we even need to get additional art for key in v.ALL_KODI_ARTWORK: if key not in artworks: break else: done = True return xml = PF.GetPlexMetadata(plex_id) try: xml[0].attrib except (TypeError, IndexError, AttributeError): LOG.warn('Could not get metadata for %s. Skipping that item ' 'for now', plex_id) return api = API(xml[0]) if artworks is None: artworks = api.artwork() # Get additional missing artwork from fanart artwork sites artworks = api.fanart_artwork(artworks) with itemtypes.ITEMTYPE_FROM_PLEXTYPE[plex_type](None) as context: context.set_fanart(artworks, db_item['kodi_id'], db_item['kodi_type']) # Additional fanart for sets/collections if plex_type == v.PLEX_TYPE_MOVIE: for _, setname in api.collections(): LOG.debug('Getting artwork for movie set %s', setname) with KodiVideoDB() as kodidb: setid = kodidb.create_collection(setname) external_set_artwork = api.set_artwork() if external_set_artwork and PREFER_KODI_COLLECTION_ART: kodi_artwork = api.artwork(kodi_id=setid, kodi_type=v.KODI_TYPE_SET) for art in kodi_artwork: if art in external_set_artwork: del external_set_artwork[art] with itemtypes.Movie(None) as movie: movie.kodidb.modify_artwork(external_set_artwork, setid, v.KODI_TYPE_SET) done = True finally: if done is True: with PlexDB() as plexdb: plexdb.set_fanart_synced(plex_id, plex_type)