Speed up sync - download art in the background

This commit is contained in:
tomkat83 2016-09-10 19:49:03 +02:00
parent 24d6514b7f
commit 2fbdd54324
5 changed files with 259 additions and 22 deletions

View file

@ -1841,16 +1841,14 @@ class API():
'Backdrop' : LIST with the first entry xml key "art" 'Backdrop' : LIST with the first entry xml key "art"
} }
""" """
item = self.item.attrib
allartworks = { allartworks = {
'Primary': "", 'Primary': "", # corresponds to Plex poster ('thumb')
'Art': "", 'Art': "",
'Banner': "", 'Banner': "", # corresponds to Plex banner ('banner') for series
'Logo': "", 'Logo': "",
'Thumb': "", 'Thumb': "", # corresponds to Plex (grand)parent posters (thumb)
'Disc': "", 'Disc': "",
'Backdrop': [] 'Backdrop': [] # Corresponds to Plex fanart ('art')
} }
# Process backdrops # Process backdrops
# Get background artwork URL # Get background artwork URL
@ -1870,11 +1868,23 @@ class API():
self.__getOneArtwork('parentArt')) self.__getOneArtwork('parentArt'))
if not allartworks['Primary']: if not allartworks['Primary']:
allartworks['Primary'] = self.__getOneArtwork('parentThumb') allartworks['Primary'] = self.__getOneArtwork('parentThumb')
return allartworks
# Plex does not get much artwork - go ahead and get the rest from def getFanartArtwork(self, allartworks, parentInfo=False):
# fanart tv only for movie or tv show """
if settings('FanartTV') == 'true': Downloads additional fanart from third party sources (well, link to
if item.get('type') in ('movie', 'show'): fanart only).
allartworks = {
'Primary': "",
'Art': "",
'Banner': "",
'Logo': "",
'Thumb': "",
'Disc': "",
'Backdrop': []
}
"""
externalId = self.getExternalItemId() externalId = self.getExternalItemId()
if externalId is not None: if externalId is not None:
allartworks = self.getFanartTVArt(externalId, allartworks) allartworks = self.getFanartTVArt(externalId, allartworks)

View file

@ -373,3 +373,30 @@ class Embydb_Functions():
query = "DELETE FROM emby WHERE emby_id LIKE ?" query = "DELETE FROM emby WHERE emby_id LIKE ?"
self.embycursor.execute(query, (plexid+"%",)) self.embycursor.execute(query, (plexid+"%",))
def itemsByType(self, plextype):
"""
Returns a list of dictionaries for all Kodi DB items present for
plextype. One dict is of the type
{
'plexId': the Plex id
'kodiId': the Kodi id
'kodi_type': e.g. 'movie', 'tvshow'
'plex_type': e.g. 'Movie', 'Series', the input plextype
}
"""
query = ' '.join((
"SELECT emby_id, kodi_id, media_type",
"FROM emby",
"WHERE emby_type = ?",
))
self.embycursor.execute(query, (plextype, ))
result = []
for row in self.embycursor.fetchall():
result.append({
'plexId': row[0],
'kodiId': row[1],
'kodi_type': row[2],
'plex_type': plextype
})
return result

View file

@ -65,6 +65,28 @@ class Items(object):
self.kodiconn.close() self.kodiconn.close()
return self return self
@CatchExceptions(warnuser=True)
def getfanart(self, item, kodiId, mediaType, allartworks=None):
"""
"""
API = PlexAPI.API(item)
if allartworks is None:
allartworks = API.getAllArtwork()
self.artwork.addArtwork(API.getFanartArtwork(allartworks),
kodiId,
mediaType,
self.kodicursor)
# Also get artwork for collections/movie sets
if mediaType == 'movie':
for setname in API.getCollections():
log.debug('Getting artwork for movie set %s' % setname)
setid = self.kodi_db.createBoxset(setname)
self.artwork.addArtwork(API.getSetArtwork(),
setid,
"set",
self.kodicursor)
self.kodi_db.assignBoxset(setid, kodiId)
def itemsbyId(self, items, process, pdialog=None): def itemsbyId(self, items, process, pdialog=None):
# Process items by itemid. Process can be added, update, userdata, remove # Process items by itemid. Process can be added, update, userdata, remove
embycursor = self.embycursor embycursor = self.embycursor
@ -485,7 +507,7 @@ class Movies(Items):
tags.append("Favorite movies") tags.append("Favorite movies")
self.kodi_db.addTags(movieid, tags, "movie") self.kodi_db.addTags(movieid, tags, "movie")
# Add any sets from Plex collection tags # Add any sets from Plex collection tags
self.kodi_db.addSets(movieid, collections, kodicursor, API) self.kodi_db.addSets(movieid, collections, kodicursor)
# Process playstates # Process playstates
self.kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed) self.kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)

View file

@ -508,6 +508,48 @@ class Kodidb_Functions():
self.artwork.addOrUpdateArt(thumb, actorid, arttype, "thumb", self.cursor) self.artwork.addOrUpdateArt(thumb, actorid, arttype, "thumb", self.cursor)
def existingArt(self, kodiId, mediaType, refresh=False):
"""
For kodiId, returns an artwork dict with already existing art from
the Kodi db
"""
# Only get EITHER poster OR thumb (should have same URL)
kodiToPKC = {
'banner': 'Banner',
'clearart': 'Art',
'clearlogo': 'Logo',
'discart': 'Disc',
'landscape': 'Thumb',
'thumb': 'Primary'
}
# BoxRear yet unused
result = {'BoxRear': ''}
for art in kodiToPKC:
query = ' '.join((
"SELECT url",
"FROM art",
"WHERE media_id = ?",
"AND media_type = ?",
"AND type = ?"
))
self.cursor.execute(query, (kodiId, mediaType, art,))
try:
url = self.cursor.fetchone()[0]
except TypeError:
url = ""
result[kodiToPKC[art]] = url
# There may be several fanart URLs saved
query = ' '.join((
"SELECT url",
"FROM art",
"WHERE media_id = ?",
"AND media_type = ?",
"AND type LIKE ?"
))
data = self.cursor.execute(query, (kodiId, mediaType, "fanart%",))
result['Backdrop'] = [d[0] for d in data]
return result
def addGenres(self, kodiid, genres, mediatype): def addGenres(self, kodiid, genres, mediatype):
@ -1180,15 +1222,9 @@ class Kodidb_Functions():
)) ))
self.cursor.execute(query, (kodiid, mediatype, tag_id,)) self.cursor.execute(query, (kodiid, mediatype, tag_id,))
def addSets(self, movieid, collections, kodicursor, API): def addSets(self, movieid, collections, kodicursor):
for setname in collections: for setname in collections:
setid = self.createBoxset(setname) setid = self.createBoxset(setname)
# Process artwork
if settings('setFanartTV') == 'true':
self.artwork.addArtwork(API.getSetArtwork(),
setid,
"set",
kodicursor)
self.assignBoxset(setid, movieid) self.assignBoxset(setid, movieid)
def createBoxset(self, boxsetname): def createBoxset(self, boxsetname):

View file

@ -5,6 +5,7 @@
import logging import logging
from threading import Thread, Lock from threading import Thread, Lock
import Queue import Queue
from random import shuffle
import xbmc import xbmc
import xbmcgui import xbmcgui
@ -255,6 +256,100 @@ class ThreadedShowSyncInfo(Thread):
log.debug('Dialog Infobox thread terminated') log.debug('Dialog Infobox thread terminated')
@ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
@ThreadMethodsAdditionalStop('plex_shouldStop')
@ThreadMethods
class ProcessFanartThread(Thread):
"""
Threaded download of additional fanart in the background
Input:
queue Queue.Queue() object that you will need to fill with
dicts of the following form:
{
'itemId': the Plex id as a string
'class': the itemtypes class, e.g. 'Movies'
'mediaType': the kodi media type, e.g. 'movie'
'refresh': True/False if true, will overwrite any 3rd party
fanart. If False, will only get missing
}
"""
def __init__(self, queue):
self.queue = queue
Thread.__init__(self)
def run(self):
threadStopped = self.threadStopped
threadSuspended = self.threadSuspended
queue = self.queue
log.debug('Started Fanart thread')
while not threadStopped():
# In the event the server goes offline
while threadSuspended():
# Set in service.py
if threadStopped():
# Abort was requested while waiting. We should exit
log.debug('Fanart thread terminated while suspended')
break
xbmc.sleep(1000)
# grabs Plex item from queue
try:
item = queue.get(block=False)
except Queue.Empty:
xbmc.sleep(50)
continue
if item['refresh'] is True:
# Leave the Plex art untouched
allartworks = None
else:
with embydb.GetEmbyDB() as emby_db:
try:
kodiId = emby_db.getItem_byId(item['itemId'])[0]
except TypeError:
log.error('Could not get Kodi id for plex id %s'
% item['itemId'])
queue.task_done()
continue
with kodidb.GetKodiDB('video') as kodi_db:
allartworks = kodi_db.existingArt(kodiId,
item['mediaType'])
# Check if we even need to get additional art
needsupdate = False
for key, value in allartworks.iteritems():
if not value and not key == 'BoxRear':
needsupdate = True
if needsupdate is False:
log.debug('Already got all art for Plex id %s'
% item['itemId'])
queue.task_done()
continue
log.debug('Getting additional fanart for Plex id %s'
% item['itemId'])
# Download Metadata
xml = PF.GetPlexMetadata(item['itemId'])
if xml is None:
# Did not receive a valid XML - skip that item for now
log.warn("Could not get metadata for %s. Skipping that item "
"for now" % item['itemId'])
queue.task_done()
continue
elif xml == 401:
log.warn('HTTP 401 returned by PMS. Too much strain? '
'Cancelling sync for now')
# Kill remaining items in queue (for main thread to cont.)
queue.task_done()
continue
# Do the work
with getattr(itemtypes, item['class'])() as cls:
cls.getfanart(xml[0], kodiId, item['mediaType'], allartworks)
# signals to queue job is done
log.debug('Done getting fanart for Plex id %s' % item['itemId'])
queue.task_done()
log.debug('Fanart thread terminated')
@ThreadMethodsAdditionalSuspend('suspend_LibraryThread') @ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
@ThreadMethodsAdditionalStop('plex_shouldStop') @ThreadMethodsAdditionalStop('plex_shouldStop')
@ThreadMethods @ThreadMethods
@ -275,6 +370,9 @@ class LibrarySync(Thread):
self.queue = queue self.queue = queue
self.itemsToProcess = [] self.itemsToProcess = []
self.sessionKeys = [] self.sessionKeys = []
if settings('FanartTV') == 'true':
self.fanartqueue = Queue.Queue()
self.fanartthread = ProcessFanartThread(self.fanartqueue)
# How long should we wait at least to process new/changed PMS items? # How long should we wait at least to process new/changed PMS items?
self.saftyMargin = int(settings('saftyMargin')) self.saftyMargin = int(settings('saftyMargin'))
@ -887,6 +985,16 @@ class LibrarySync(Thread):
except: except:
pass pass
log.info("Sync threads finished") log.info("Sync threads finished")
if settings('FanartTV') == 'true':
# Save to queue for later processing
typus = {'Movies': 'movie', 'TVShows': 'tvshow'}[itemType]
for item in self.updatelist:
self.fanartqueue.put({
'itemId': item['itemId'],
'class': itemType,
'mediaType': typus,
'refresh': False
})
self.updatelist = [] self.updatelist = []
@LogTime @LogTime
@ -1484,6 +1592,30 @@ class LibrarySync(Thread):
with itemFkt() as Fkt: with itemFkt() as Fkt:
Fkt.updatePlaystate(item) Fkt.updatePlaystate(item)
def fanartSync(self, refresh=False):
"""
Checks all Plex movies and TV shows whether they still need fanart
refresh=True Force refresh all external fanart
"""
items = []
typus = {
'Movie': 'Movies',
'Series': 'TVShows'
}
with embydb.GetEmbyDB() as emby_db:
for plextype in typus:
items.extend(emby_db.itemsByType(plextype))
# Shuffle the list to not always start out identically
items = shuffle(items)
for item in items:
self.fanartqueue.put({
'itemId': item['plexId'],
'mediaType': item['kodi_type'],
'class': typus[item['plex_type']],
'refresh': refresh
})
def run(self): def run(self):
try: try:
self.run_internal() self.run_internal()
@ -1508,6 +1640,7 @@ class LibrarySync(Thread):
fullSyncInterval = self.fullSyncInterval fullSyncInterval = self.fullSyncInterval
lastSync = 0 lastSync = 0
lastTimeSync = 0 lastTimeSync = 0
lastFanartSync = 0
lastProcessing = 0 lastProcessing = 0
oneDay = 60*60*24 oneDay = 60*60*24
@ -1527,6 +1660,9 @@ class LibrarySync(Thread):
if self.enableMusic: if self.enableMusic:
advancedSettingsXML() advancedSettingsXML()
if settings('FanartTV') == 'true':
self.fanartthread.start()
while not threadStopped(): while not threadStopped():
# In the event the server goes offline # In the event the server goes offline
@ -1661,6 +1797,12 @@ class LibrarySync(Thread):
window('plex_dbScan', value="true") window('plex_dbScan', value="true")
self.syncPMStime() self.syncPMStime()
window('plex_dbScan', clear=True) window('plex_dbScan', clear=True)
elif (now - lastFanartSync > oneDay and
settings('FanartTV') == 'true'):
lastFanartSync = now
log.info('Starting daily fanart sync')
self.fanartSync()
log.info('Finished init of daily fanart sync')
elif enableBackgroundSync: elif enableBackgroundSync:
# Check back whether we should process something # Check back whether we should process something
# Only do this once every 10 seconds # Only do this once every 10 seconds