diff --git a/README.md b/README.md index 3851f8ef..70b2f9cb 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ Some people argue that PKC is 'hacky' because of the way it directly accesses th - Support for Kodi 18 Leia and Kodi 19 Matrix - [Amazon Alexa voice recognition](https://www.plex.tv/apps/streaming-devices/amazon-alexa) - [Cinema Trailers & Extras](https://support.plex.tv/articles/202934883-cinema-trailers-extras/) +- If Plex did not provide a trailer, automatically get one using the Kodi add-on [The Movie Database](https://kodi.wiki/view/Add-on:The_Movie_Database) - [Plex Watch Later / Plex It!](https://support.plex.tv/hc/en-us/sections/200211783-Plex-It-) - [Plex Companion](https://support.plex.tv/hc/en-us/sections/200276908-Plex-Companion): fling Plex media (or anything else) from other Plex devices to PlexKodiConnect - Automatically sync Plex playlists to Kodi playlists and vice-versa diff --git a/addon.xml b/addon.xml index 78af201a..5839bce8 100644 --- a/addon.xml +++ b/addon.xml @@ -6,6 +6,7 @@ + video audio image diff --git a/resources/lib/app/application.py b/resources/lib/app/application.py index ef93f779..13760126 100644 --- a/resources/lib/app/application.py +++ b/resources/lib/app/application.py @@ -46,8 +46,8 @@ class App(object): self.player = None # All thread instances self.threads = [] - # Instance of FanartThread() - self.fanart_thread = None + # Instance of MetadataThread() + self.metadata_thread = None # Instance of ImageCachingThread() self.caching_thread = None @@ -59,24 +59,24 @@ class App(object): def is_playing_video(self): return self.player.isPlayingVideo() == 1 - def register_fanart_thread(self, thread): - self.fanart_thread = thread + def register_metadata_thread(self, thread): + self.metadata_thread = thread self.threads.append(thread) - def deregister_fanart_thread(self, thread): - self.fanart_thread.unblock_callers() - self.fanart_thread = None + def deregister_metadata_thread(self, thread): + self.metadata_thread.unblock_callers() + self.metadata_thread = None self.threads.remove(thread) - def suspend_fanart_thread(self, block=True): + def suspend_metadata_thread(self, block=True): try: - self.fanart_thread.suspend(block=block) + self.metadata_thread.suspend(block=block) except AttributeError: pass - def resume_fanart_thread(self): + def resume_metadata_thread(self): try: - self.fanart_thread.resume() + self.metadata_thread.resume() except AttributeError: pass diff --git a/resources/lib/itemtypes/movies.py b/resources/lib/itemtypes/movies.py index 0d732b54..45f4b48a 100644 --- a/resources/lib/itemtypes/movies.py +++ b/resources/lib/itemtypes/movies.py @@ -132,6 +132,7 @@ class Movie(ItemBase): kodi_id=kodi_id, kodi_fileid=file_id, kodi_pathid=kodi_pathid, + trailer_synced=bool(api.trailer()), last_sync=self.last_sync) def remove(self, plex_id, plex_type=None): diff --git a/resources/lib/itemtypes/movies_tmdb.py b/resources/lib/itemtypes/movies_tmdb.py new file mode 100644 index 00000000..05f4e6da --- /dev/null +++ b/resources/lib/itemtypes/movies_tmdb.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import logging +import os +import sys + +import xbmcvfs +import xbmcaddon + +# Import the existing Kodi add-on metadata.themoviedb.org.python +__ADDON__ = xbmcaddon.Addon(id='metadata.themoviedb.org.python') +__TEMP_PATH__ = os.path.join(__ADDON__.getAddonInfo('path'), 'python', 'lib') +__BASE__ = xbmcvfs.translatePath(__TEMP_PATH__) +sys.path.append(__BASE__) +import tmdbscraper.tmdb as tmdb + +logger = logging.getLogger('PLEX.movies_tmdb') + + +def get_tmdb_scraper(settings): + language = settings.getSettingString('language') + certcountry = settings.getSettingString('tmdbcertcountry') + return tmdb.TMDBMovieScraper(__ADDON__, language, certcountry) + + +# Instantiate once in order to prevent having to re-read the add-on settings +# for every single movie +__SCRAPER__ = get_tmdb_scraper(__ADDON__) + +def get_tmdb_details(unique_ids): + details = __SCRAPER__.get_details(unique_ids) + if 'error' in details: + logger.debug('Could not get tmdb details for %s. Error: %s', + unique_ids, details) + return details diff --git a/resources/lib/kodi_db/video.py b/resources/lib/kodi_db/video.py index f307bb54..6ea2412e 100644 --- a/resources/lib/kodi_db/video.py +++ b/resources/lib/kodi_db/video.py @@ -477,6 +477,31 @@ class KodiVideoDB(common.KodiDBBase): (kodi_id, kodi_type)) return dict(self.cursor.fetchall()) + def get_trailer(self, kodi_id, kodi_type): + """ + Returns the trailer's URL for kodi_type from the Kodi database or None + """ + if kodi_type == v.KODI_TYPE_MOVIE: + self.cursor.execute('SELECT c19 FROM movie WHERE idMovie=?', + (kodi_id, )) + else: + raise NotImplementedError(f'trailers for {kodi_type} not implemented') + try: + return self.cursor.fetchone()[0] + except TypeError: + pass + + @db.catch_operationalerrors + def set_trailer(self, kodi_id, kodi_type, url): + """ + Writes the trailer's url to the Kodi DB + """ + if kodi_type == v.KODI_TYPE_MOVIE: + self.cursor.execute('UPDATE movie SET c19=? WHERE idMovie=?', + (url, kodi_id)) + else: + raise NotImplementedError(f'trailers for {kodi_type} not implemented') + @db.catch_operationalerrors def modify_streams(self, fileid, streamdetails=None, runtime=None): """ diff --git a/resources/lib/library_sync/__init__.py b/resources/lib/library_sync/__init__.py index 885450a9..de9f0bb3 100644 --- a/resources/lib/library_sync/__init__.py +++ b/resources/lib/library_sync/__init__.py @@ -3,5 +3,5 @@ from .full_sync import start from .websocket import store_websocket_message, process_websocket_messages, \ WEBSOCKET_MESSAGES, PLAYSTATE_SESSIONS from .common import update_kodi_library, PLAYLIST_SYNC_ENABLED -from .fanart import FanartThread, FanartTask +from .additional_metadata import MetadataThread, ProcessMetadataTask from .sections import force_full_sync, delete_files, clear_window_vars diff --git a/resources/lib/library_sync/additional_metadata.py b/resources/lib/library_sync/additional_metadata.py new file mode 100644 index 00000000..70f5b575 --- /dev/null +++ b/resources/lib/library_sync/additional_metadata.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +from logging import getLogger + +from . import additional_metadata_tmdb +from ..plex_db import PlexDB +from .. import backgroundthread, utils +from .. import variables as v, app + + +logger = getLogger('PLEX.sync.metadata') + +BATCH_SIZE = 500 + +SUPPORTED_METADATA = { + v.PLEX_TYPE_MOVIE: ( + ('missing_trailers', additional_metadata_tmdb.process_trailers), + ('missing_fanart', additional_metadata_tmdb.process_fanart), + ), + v.PLEX_TYPE_SHOW: ( + ('missing_fanart', additional_metadata_tmdb.process_fanart), + ), +} + + +class ProcessingNotDone(Exception): + """Exception to detect whether we've completed our sync and did not have to + abort or suspend.""" + pass + + +def processing_is_activated(item_getter): + """Checks the PKC settings whether processing is even activated.""" + if item_getter == 'missing_fanart': + return utils.settings('FanartTV') == 'true' + return True + + +class MetadataThread(backgroundthread.KillableThread): + """This will potentially take hours!""" + def __init__(self, callback, refresh=False): + self.callback = callback + self.refresh = refresh + super(MetadataThread, self).__init__() + + def should_suspend(self): + return self._suspended or app.APP.is_playing_video + + def _process_in_batches(self, item_getter, processor, plex_type): + offset = 0 + while True: + with PlexDB() as plexdb: + # Keep DB connection open only for a short period of time! + if self.refresh: + # Simply grab every single item if we want to refresh + func = plexdb.every_plex_id + else: + func = getattr(plexdb, item_getter) + batch = list(func(plex_type, offset, BATCH_SIZE)) + for plex_id in batch: + # Do the actual, time-consuming processing + if self.should_suspend() or self.should_cancel(): + raise ProcessingNotDone() + processor(plex_id, plex_type, self.refresh) + if len(batch) < BATCH_SIZE: + break + offset += BATCH_SIZE + + def _loop(self): + for plex_type in SUPPORTED_METADATA: + for item_getter, processor in SUPPORTED_METADATA[plex_type]: + if not processing_is_activated(item_getter): + continue + self._process_in_batches(item_getter, processor, plex_type) + + def _run(self): + finished = False + while not finished: + try: + self._loop() + except ProcessingNotDone: + finished = False + else: + finished = True + if self.wait_while_suspended(): + break + logger.info('MetadataThread finished completely: %s', finished) + self.callback(finished) + + def run(self): + logger.info('Starting MetadataThread') + app.APP.register_metadata_thread(self) + try: + self._run() + except Exception: + utils.ERROR(notify=True) + finally: + app.APP.deregister_metadata_thread(self) + + +class ProcessMetadataTask(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): + if self.plex_type not in SUPPORTED_METADATA: + return + for item_getter, processor in SUPPORTED_METADATA[self.plex_type]: + if self.should_cancel(): + # Just don't process this item at all. Next full sync will + # take care of it + return + if not processing_is_activated(item_getter): + continue + processor(self.plex_id, self.plex_type, self.refresh) diff --git a/resources/lib/library_sync/additional_metadata_tmdb.py b/resources/lib/library_sync/additional_metadata_tmdb.py new file mode 100644 index 00000000..4afec456 --- /dev/null +++ b/resources/lib/library_sync/additional_metadata_tmdb.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import logging +import os +import sys + +import xbmcvfs +import xbmcaddon + +from ..plex_api import API +from ..kodi_db import KodiVideoDB +from ..plex_db import PlexDB +from .. import itemtypes, plex_functions as PF, utils, variables as v + +# Import the existing Kodi add-on metadata.themoviedb.org.python +__ADDON__ = xbmcaddon.Addon(id='metadata.themoviedb.org.python') +__TEMP_PATH__ = os.path.join(__ADDON__.getAddonInfo('path'), 'python', 'lib') +__BASE__ = xbmcvfs.translatePath(__TEMP_PATH__) +sys.path.append(__BASE__) +import tmdbscraper.tmdb as tmdb + +logger = logging.getLogger('PLEX.metadata_movies') +PREFER_KODI_COLLECTION_ART = utils.settings('PreferKodiCollectionArt') == 'false' +TMDB_SUPPORTED_IDS = ('tmdb', 'imdb') + + +def get_tmdb_scraper(settings): + language = settings.getSettingString('language') + certcountry = settings.getSettingString('tmdbcertcountry') + return tmdb.TMDBMovieScraper(settings, language, certcountry) + + +def get_tmdb_details(unique_ids): + settings = xbmcaddon.Addon(id='metadata.themoviedb.org.python') + details = get_tmdb_scraper(settings).get_details(unique_ids) + if 'error' in details: + logger.debug('Could not get tmdb details for %s. Error: %s', + unique_ids, details) + return details + + +def process_trailers(plex_id, plex_type, refresh=False): + done = True + try: + with PlexDB() as plexdb: + db_item = plexdb.item_by_id(plex_id, plex_type) + if not db_item: + logger.error('Could not get Kodi id for %s %s', plex_type, plex_id) + done = False + return + with KodiVideoDB() as kodidb: + trailer = kodidb.get_trailer(db_item['kodi_id'], + db_item['kodi_type']) + if trailer and (trailer.startswith(f'plugin://{v.ADDON_ID}') or + not refresh): + # No need to get a trailer + return + logger.debug('Processing trailer for %s %s', plex_type, plex_id) + xml = PF.GetPlexMetadata(plex_id) + try: + xml[0].attrib + except (TypeError, IndexError, AttributeError): + logger.warn('Could not get metadata for %s. Skipping that %s ' + 'for now', plex_id, plex_type) + done = False + return + api = API(xml[0]) + if (not api.guids or + not [x for x in api.guids if x in TMDB_SUPPORTED_IDS]): + logger.debug('No unique ids found for %s %s, cannot get a trailer', + plex_type, api.title()) + return + trailer = get_tmdb_details(api.guids) + trailer = trailer.get('info', {}).get('trailer') + if trailer: + with KodiVideoDB() as kodidb: + kodidb.set_trailer(db_item['kodi_id'], + db_item['kodi_type'], + trailer) + logger.debug('Found a new trailer for %s %s: %s', + plex_type, api.title(), trailer) + else: + logger.debug('No trailer found for %s %s', plex_type, api.title()) + finally: + if done is True: + with PlexDB() as plexdb: + plexdb.set_trailer_synced(plex_id, plex_type) + + +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 = True + try: + artworks = None + with PlexDB() as plexdb: + db_item = plexdb.item_by_id(plex_id, plex_type) + if not db_item: + logger.error('Could not get Kodi id for %s %s', plex_type, plex_id) + done = False + 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: + return + xml = PF.GetPlexMetadata(plex_id) + try: + xml[0].attrib + except (TypeError, IndexError, AttributeError): + logger.debug('Could not get metadata for %s %s. Skipping that ' + 'item for now', plex_type, plex_id) + done = False + 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(): + logger.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) + finally: + if done is True: + with PlexDB() as plexdb: + plexdb.set_fanart_synced(plex_id, plex_type) diff --git a/resources/lib/library_sync/fanart.py b/resources/lib/library_sync/fanart.py deleted file mode 100644 index 44bb6bb9..00000000 --- a/resources/lib/library_sync/fanart.py +++ /dev/null @@ -1,153 +0,0 @@ -# -*- coding: utf-8 -*- -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) diff --git a/resources/lib/library_sync/websocket.py b/resources/lib/library_sync/websocket.py index 27cb711e..bddb23bb 100644 --- a/resources/lib/library_sync/websocket.py +++ b/resources/lib/library_sync/websocket.py @@ -3,7 +3,7 @@ from logging import getLogger from .common import update_kodi_library, PLAYLIST_SYNC_ENABLED -from .fanart import SYNC_FANART, FanartTask +from .additional_metadata import ProcessMetadataTask from ..plex_api import API from ..plex_db import PlexDB from .. import kodi_db @@ -84,9 +84,8 @@ def process_websocket_messages(): continue else: successful, video, music = process_new_item_message(message) - if (successful and SYNC_FANART and - message['plex_type'] in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)): - task = FanartTask() + if successful: + task = ProcessMetadataTask() task.setup(message['plex_id'], message['plex_type'], refresh=False) diff --git a/resources/lib/migration.py b/resources/lib/migration.py index bbc0411f..2e5ecf69 100644 --- a/resources/lib/migration.py +++ b/resources/lib/migration.py @@ -22,80 +22,13 @@ def check_migration(): LOG.info('Already migrated to PKC version %s' % v.ADDON_VERSION) return - if not utils.compare_version(last_migration, '1.8.2'): - LOG.info('Migrating to version 1.8.1') - # Set the new PKC theMovieDB key - utils.settings('themoviedbAPIKey', - value='19c90103adb9e98f2172c6a6a3d85dc4') - - if not utils.compare_version(last_migration, '2.0.25'): - LOG.info('Migrating to version 2.0.24') - # Need to re-connect with PMS to pick up on plex.direct URIs - utils.settings('ipaddress', value='') - utils.settings('port', value='') - - if not utils.compare_version(last_migration, '2.7.6'): - LOG.info('Migrating to version 2.7.5') - from .library_sync.sections import delete_files - delete_files() - - if not utils.compare_version(last_migration, '2.8.3'): - LOG.info('Migrating to version 2.8.2') - from .library_sync import sections - sections.clear_window_vars() - sections.delete_videonode_files() - - if not utils.compare_version(last_migration, '2.8.7'): - LOG.info('Migrating to version 2.8.6') - # Need to delete the UNIQUE index that prevents creating several - # playlist entries with the same kodi_hash + if not utils.compare_version(last_migration, '3.0.5'): + LOG.info('Migrating to version 3.0.4') + # Add an additional column `trailer_synced` in the Plex movie table from .plex_db import PlexDB with PlexDB() as plexdb: - plexdb.cursor.execute('DROP INDEX IF EXISTS ix_playlists_3') + query = 'ALTER TABLE movie ADD trailer_synced BOOLEAN' + plexdb.cursor.execute(query) # Index will be automatically recreated on next PKC startup - if not utils.compare_version(last_migration, '2.8.9'): - LOG.info('Migrating to version 2.8.8') - from .library_sync import sections - sections.clear_window_vars() - sections.delete_videonode_files() - - if not utils.compare_version(last_migration, '2.9.3'): - LOG.info('Migrating to version 2.9.2') - # Re-sync all playlists to Kodi - from .playlists import remove_synced_playlists - remove_synced_playlists() - - if not utils.compare_version(last_migration, '2.9.7'): - LOG.info('Migrating to version 2.9.6') - # Allow for a new "Direct Stream" setting (number 2), so shift the - # last setting for "force transcoding" - current_playback_type = utils.cast(int, utils.settings('playType')) or 0 - if current_playback_type == 2: - current_playback_type = 3 - utils.settings('playType', value=str(current_playback_type)) - - if not utils.compare_version(last_migration, '2.9.8'): - LOG.info('Migrating to version 2.9.7') - # Force-scan every single item in the library - seems like we could - # loose some recently added items otherwise - # Caused by 65a921c3cc2068c4a34990d07289e2958f515156 - from . import library_sync - library_sync.force_full_sync() - - if not utils.compare_version(last_migration, '2.11.3'): - LOG.info('Migrating to version 2.11.2') - # Re-sync all playlists to Kodi - from .playlists import remove_synced_playlists - remove_synced_playlists() - - if not utils.compare_version(last_migration, '2.12.2'): - LOG.info('Migrating to version 2.12.1') - # Sign user out to make sure he needs to sign in again - utils.settings('username', value='') - utils.settings('userid', value='') - utils.settings('plex_restricteduser', value='') - utils.settings('accessToken', value='') - utils.settings('plexAvatar', value='') - utils.settings('last_migrated_PKC_version', value=v.ADDON_VERSION) diff --git a/resources/lib/plex_db/common.py b/resources/lib/plex_db/common.py index fee74a21..d3d39a14 100644 --- a/resources/lib/plex_db/common.py +++ b/resources/lib/plex_db/common.py @@ -159,6 +159,19 @@ class PlexDBBase(object): ''' % (plex_type, limit, offset) return (x[0] for x in self.cursor.execute(query)) + def missing_trailers(self, plex_type, offset, limit): + """ + Returns an iterator for plex_type for all plex_id, where trailer_synced + has not yet been set to 1 + Will start with records at DB position offset [int] and return limit + [int] number of items + """ + query = ''' + SELECT plex_id FROM %s WHERE trailer_synced = 0 + LIMIT %s OFFSET %s + ''' % (plex_type, limit, offset) + return (x[0] for x in self.cursor.execute(query)) + def set_fanart_synced(self, plex_id, plex_type): """ Toggles fanart_synced to 1 for plex_id @@ -166,6 +179,13 @@ class PlexDBBase(object): self.cursor.execute('UPDATE %s SET fanart_synced = 1 WHERE plex_id = ?' % plex_type, (plex_id, )) + def set_trailer_synced(self, plex_id, plex_type): + """ + Toggles fanart_synced to 1 for plex_id + """ + self.cursor.execute('UPDATE %s SET trailer_synced = 1 WHERE plex_id = ?' % plex_type, + (plex_id, )) + def plexid_by_sectionid(self, section_id, plex_type, limit): query = ''' SELECT plex_id FROM %s WHERE section_id = ? LIMIT %s @@ -209,6 +229,7 @@ def initialize(): kodi_fileid INTEGER, kodi_pathid INTEGER, fanart_synced INTEGER, + trailer_synced BOOLEAN, last_sync INTEGER) ''') plexdb.cursor.execute(''' diff --git a/resources/lib/plex_db/movies.py b/resources/lib/plex_db/movies.py index a2898746..38c7a551 100644 --- a/resources/lib/plex_db/movies.py +++ b/resources/lib/plex_db/movies.py @@ -5,7 +5,7 @@ from .. import variables as v class Movies(object): def add_movie(self, plex_id, checksum, section_id, kodi_id, kodi_fileid, - kodi_pathid, last_sync): + kodi_pathid, trailer_synced, last_sync): """ Appends or replaces an entry into the plex table for movies """ @@ -18,8 +18,9 @@ class Movies(object): kodi_fileid, kodi_pathid, fanart_synced, + trailer_synced, last_sync) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ''' self.cursor.execute( query, @@ -30,6 +31,7 @@ class Movies(object): kodi_fileid, kodi_pathid, 0, + trailer_synced, last_sync)) def movie(self, plex_id): diff --git a/resources/lib/sync.py b/resources/lib/sync.py index 50a23ac7..43eb0a74 100644 --- a/resources/lib/sync.py +++ b/resources/lib/sync.py @@ -21,7 +21,7 @@ class Sync(backgroundthread.KillableThread): def __init__(self): self.sync_successful = False self.last_full_sync = 0 - self.fanart_thread = None + self.metadata_thread = None self.image_cache_thread = None # Lock used to wait on a full sync, e.g. on initial sync # self.lock = backgroundthread.threading.Lock() @@ -51,7 +51,7 @@ class Sync(backgroundthread.KillableThread): utils.lang(39223), utils.lang(39224), # refresh all utils.lang(39225)) == 0 - if not self.start_fanart_download(refresh=refresh): + if not self.start_additional_metadata(refresh=refresh): # Fanart download already running utils.dialog('notification', heading='{plex}', @@ -75,33 +75,31 @@ class Sync(backgroundthread.KillableThread): self.last_full_sync = timing.unix_timestamp() if not successful: LOG.warn('Could not finish scheduled full sync') - app.APP.resume_fanart_thread() + app.APP.resume_metadata_thread() app.APP.resume_caching_thread() def start_library_sync(self, show_dialog=None, repair=False, block=False): - app.APP.suspend_fanart_thread(block=True) + app.APP.suspend_metadata_thread(block=True) app.APP.suspend_caching_thread(block=True) show_dialog = show_dialog if show_dialog is not None else app.SYNC.sync_dialog library_sync.start(show_dialog, repair, self.on_library_scan_finished) - def start_fanart_download(self, refresh): - if not utils.settings('FanartTV') == 'true': - LOG.info('Additional fanart download is deactivated') - return False + def start_additional_metadata(self, refresh): if not app.SYNC.artwork: LOG.info('Not synching Plex PMS artwork, not getting artwork') return False - elif self.fanart_thread is None or not self.fanart_thread.is_alive(): - LOG.info('Start downloading additional fanart with refresh %s', + elif self.metadata_thread is None or not self.metadata_thread.is_alive(): + LOG.info('Start downloading additional metadata with refresh %s', refresh) - self.fanart_thread = library_sync.FanartThread(self.on_fanart_download_finished, refresh) - self.fanart_thread.start() + self.metadata_thread = library_sync.MetadataThread(self.on_metadata_finished, refresh) + self.metadata_thread.start() return True else: - LOG.info('Still downloading fanart') + LOG.info('Still downloading metadata') return False - def on_fanart_download_finished(self, successful): + @staticmethod + def on_metadata_finished(successful): # FanartTV lookup completed if successful: # Toggled to "Yes" @@ -188,7 +186,7 @@ class Sync(backgroundthread.KillableThread): xbmc.executebuiltin('ReloadSkin()') if library_sync.PLAYLIST_SYNC_ENABLED: playlist_monitor = playlists.kodi_playlist_monitor() - self.start_fanart_download(refresh=False) + self.start_additional_metadata(refresh=False) self.start_image_cache_thread() else: LOG.error('Initial start-up full sync unsuccessful') @@ -205,7 +203,7 @@ class Sync(backgroundthread.KillableThread): LOG.info('Done initial sync on Kodi startup') if library_sync.PLAYLIST_SYNC_ENABLED: playlist_monitor = playlists.kodi_playlist_monitor() - self.start_fanart_download(refresh=False) + self.start_additional_metadata(refresh=False) self.start_image_cache_thread() else: LOG.info('Startup sync has not yet been successful')