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')