Switch to threaded Movie metadata processing
This commit is contained in:
parent
e86b8c2358
commit
8c18879271
4 changed files with 559 additions and 153 deletions
|
@ -845,6 +845,7 @@ class PlexAPI():
|
||||||
"""
|
"""
|
||||||
plexToken = utils.settings('plexToken')
|
plexToken = utils.settings('plexToken')
|
||||||
users = self.MyPlexListHomeUsers(plexToken)
|
users = self.MyPlexListHomeUsers(plexToken)
|
||||||
|
url = ''
|
||||||
# If an error is encountered, set to False
|
# If an error is encountered, set to False
|
||||||
if not users:
|
if not users:
|
||||||
self.logMsg("Could not get userlist from plex.tv.", 1)
|
self.logMsg("Could not get userlist from plex.tv.", 1)
|
||||||
|
@ -1188,12 +1189,15 @@ class PlexAPI():
|
||||||
mediatype String or list of strings with possible values
|
mediatype String or list of strings with possible values
|
||||||
'movie', 'show', 'artist', 'photo'
|
'movie', 'show', 'artist', 'photo'
|
||||||
Output:
|
Output:
|
||||||
Collection containing only mediatype. List with entry of the form:
|
List with an entry of the form:
|
||||||
{
|
{
|
||||||
'name': xxx Plex title for the media section
|
'name': xxx Plex title for the media section
|
||||||
'type': xxx Plex type: 'movie', 'show', 'artist', 'photo'
|
'type': xxx Plex type: 'movie', 'show', 'artist', 'photo'
|
||||||
'id': xxx Plex unique key for the section
|
'id': xxx Plex unique key for the section (1, 2, 3...)
|
||||||
|
'uuid': xxx Other unique Plex key, e.g.
|
||||||
|
74aec9f2-a312-4723-9436-de2ea43843c1
|
||||||
}
|
}
|
||||||
|
Returns an empty list if nothing is found.
|
||||||
"""
|
"""
|
||||||
collections = []
|
collections = []
|
||||||
url = "{server}/library/sections"
|
url = "{server}/library/sections"
|
||||||
|
@ -1208,17 +1212,19 @@ class PlexAPI():
|
||||||
if contentType in mediatype:
|
if contentType in mediatype:
|
||||||
name = item['title']
|
name = item['title']
|
||||||
contentId = item['key']
|
contentId = item['key']
|
||||||
|
uuid = item['uuid']
|
||||||
collections.append({
|
collections.append({
|
||||||
'name': name,
|
'name': name,
|
||||||
'type': contentType,
|
'type': contentType,
|
||||||
'id': str(contentId)
|
'id': str(contentId),
|
||||||
|
'uuid': uuid
|
||||||
})
|
})
|
||||||
return collections
|
return collections
|
||||||
|
|
||||||
def GetPlexSectionResults(self, viewId):
|
def GetPlexSectionResults(self, viewId):
|
||||||
"""
|
"""
|
||||||
Returns a list (raw API dump) of all Plex movies in the Plex section
|
Returns a list (raw JSON API dump) of all Plex items in the Plex
|
||||||
with key = viewId.
|
section with key = viewId.
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
url = "{server}/library/sections/%s/all" % viewId
|
url = "{server}/library/sections/%s/all" % viewId
|
||||||
|
@ -1226,10 +1232,17 @@ class PlexAPI():
|
||||||
try:
|
try:
|
||||||
result = jsondata['_children']
|
result = jsondata['_children']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.logMsg("Error retrieving all movies for section %s" % viewId, 1)
|
self.logMsg("Error retrieving all items for Plex section %s" % viewId, 1)
|
||||||
pass
|
pass
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def GetPlexSubitems(self, key):
|
||||||
|
"""
|
||||||
|
Returns a list (raw JSON API dump) of all Plex subitems for the key.
|
||||||
|
(e.g. key=/library/metadata/194853/children pointing to a season)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def GetPlexMetadata(self, key):
|
def GetPlexMetadata(self, key):
|
||||||
"""
|
"""
|
||||||
Returns raw API metadata for key.
|
Returns raw API metadata for key.
|
||||||
|
|
|
@ -26,12 +26,7 @@ import PlexAPI
|
||||||
|
|
||||||
class Items(object):
|
class Items(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
def __init__(self, embycursor, kodicursor):
|
|
||||||
|
|
||||||
self.embycursor = embycursor
|
|
||||||
self.kodicursor = kodicursor
|
|
||||||
|
|
||||||
self.clientInfo = clientinfo.ClientInfo()
|
self.clientInfo = clientinfo.ClientInfo()
|
||||||
self.addonName = self.clientInfo.getAddonName()
|
self.addonName = self.clientInfo.getAddonName()
|
||||||
self.doUtils = downloadutils.DownloadUtils()
|
self.doUtils = downloadutils.DownloadUtils()
|
||||||
|
@ -40,11 +35,28 @@ class Items(object):
|
||||||
self.directpath = utils.settings('useDirectPaths') == "1"
|
self.directpath = utils.settings('useDirectPaths') == "1"
|
||||||
self.music_enabled = utils.settings('enableMusic') == "true"
|
self.music_enabled = utils.settings('enableMusic') == "true"
|
||||||
self.contentmsg = utils.settings('newContent') == "true"
|
self.contentmsg = utils.settings('newContent') == "true"
|
||||||
|
|
||||||
self.artwork = artwork.Artwork()
|
self.artwork = artwork.Artwork()
|
||||||
self.emby = embyserver.Read_EmbyServer()
|
self.emby = embyserver.Read_EmbyServer()
|
||||||
self.emby_db = embydb.Embydb_Functions(embycursor)
|
|
||||||
self.kodi_db = kodidb.Kodidb_Functions(kodicursor)
|
def __enter__(self):
|
||||||
|
self.embyconn = utils.kodiSQL('emby')
|
||||||
|
self.embycursor = self.embyconn.cursor()
|
||||||
|
self.kodiconn = utils.kodiSQL('video')
|
||||||
|
self.kodicursor = self.kodiconn.cursor()
|
||||||
|
self.emby_db = embydb.Embydb_Functions(self.embycursor)
|
||||||
|
self.kodi_db = kodidb.Kodidb_Functions(self.kodicursor)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
"""
|
||||||
|
Make sure DB changes are committed and connection to DB is closed.
|
||||||
|
"""
|
||||||
|
self.embyconn.commit()
|
||||||
|
self.kodiconn.commit()
|
||||||
|
self.embyconn.close()
|
||||||
|
self.kodiconn.close()
|
||||||
|
return self
|
||||||
|
|
||||||
def logMsg(self, msg, lvl=1):
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
|
@ -230,10 +242,6 @@ class Items(object):
|
||||||
|
|
||||||
class Movies(Items):
|
class Movies(Items):
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, embycursor, kodicursor):
|
|
||||||
Items.__init__(self, embycursor, kodicursor)
|
|
||||||
|
|
||||||
def added(self, items, pdialog):
|
def added(self, items, pdialog):
|
||||||
|
|
||||||
total = len(items)
|
total = len(items)
|
||||||
|
@ -265,7 +273,6 @@ class Movies(Items):
|
||||||
count += 1
|
count += 1
|
||||||
self.add_updateBoxset(boxset)
|
self.add_updateBoxset(boxset)
|
||||||
|
|
||||||
|
|
||||||
def add_update(self, item, viewtag=None, viewid=None):
|
def add_update(self, item, viewtag=None, viewid=None):
|
||||||
# Process single movie
|
# Process single movie
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
|
@ -296,6 +303,7 @@ class Movies(Items):
|
||||||
kodicursor.execute("select coalesce(max(idMovie),0) from movie")
|
kodicursor.execute("select coalesce(max(idMovie),0) from movie")
|
||||||
movieid = kodicursor.fetchone()[0] + 1
|
movieid = kodicursor.fetchone()[0] + 1
|
||||||
|
|
||||||
|
|
||||||
# if not viewtag or not viewid:
|
# if not viewtag or not viewid:
|
||||||
# # Get view tag from emby
|
# # Get view tag from emby
|
||||||
# viewtag, viewid, mediatype = self.emby.getView_embyId(itemid)
|
# viewtag, viewid, mediatype = self.emby.getView_embyId(itemid)
|
||||||
|
|
|
@ -2,9 +2,8 @@
|
||||||
|
|
||||||
##################################################################################################
|
##################################################################################################
|
||||||
|
|
||||||
import sqlite3
|
|
||||||
import threading
|
import threading
|
||||||
from datetime import datetime, timedelta, time
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import xbmc
|
import xbmc
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
|
@ -22,10 +21,171 @@ import userclient
|
||||||
import videonodes
|
import videonodes
|
||||||
|
|
||||||
import PlexAPI
|
import PlexAPI
|
||||||
|
import Queue
|
||||||
|
import time
|
||||||
|
|
||||||
##################################################################################################
|
##################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadedGetMetadata(threading.Thread):
|
||||||
|
"""
|
||||||
|
Threaded download of Plex XML metadata for a certain library item.
|
||||||
|
Fills the out_queue with the downloaded etree XML objects
|
||||||
|
|
||||||
|
Input:
|
||||||
|
queue Queue.Queue() object that you'll need to fill up
|
||||||
|
with Plex itemIds
|
||||||
|
out_queue Queue.Queue() object where this thread will store
|
||||||
|
the downloaded metadata XMLs as etree objects
|
||||||
|
lock threading.Lock(), used for counting where we are
|
||||||
|
userStop Handle to a function where True is used to stop
|
||||||
|
this Thread
|
||||||
|
"""
|
||||||
|
def __init__(self, queue, out_queue, lock, userStop):
|
||||||
|
self.queue = queue
|
||||||
|
self.out_queue = out_queue
|
||||||
|
self.lock = lock
|
||||||
|
self.userStop = userStop
|
||||||
|
self._shouldstop = threading.Event()
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
plx = PlexAPI.PlexAPI()
|
||||||
|
global getMetadataCount
|
||||||
|
while self.stopped() is False:
|
||||||
|
# grabs Plex item from queue
|
||||||
|
try:
|
||||||
|
itemId = self.queue.get(block=False)
|
||||||
|
# Empty queue
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
# Download Metadata
|
||||||
|
plexElement = plx.GetPlexMetadata(itemId)
|
||||||
|
# place item into out queue
|
||||||
|
self.out_queue.put(plexElement)
|
||||||
|
# Keep track of where we are at
|
||||||
|
with self.lock:
|
||||||
|
getMetadataCount += 1
|
||||||
|
# signals to queue job is done
|
||||||
|
self.queue.task_done()
|
||||||
|
|
||||||
|
def stopThread(self):
|
||||||
|
self._shouldstop.set()
|
||||||
|
|
||||||
|
def stopped(self):
|
||||||
|
return self._shouldstop.isSet() or self.userStop()
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadedProcessMetadata(threading.Thread):
|
||||||
|
"""
|
||||||
|
Not yet implemented - if ever. Only to be called by ONE thread!
|
||||||
|
Processes the XML metadata in the queue
|
||||||
|
|
||||||
|
Input:
|
||||||
|
queue: Queue.Queue() object that you'll need to fill up with
|
||||||
|
the downloaded XML eTree objects
|
||||||
|
data = {
|
||||||
|
'itemType': as used to call functions in itemtypes.py
|
||||||
|
e.g. 'Movies' => itemtypes.Movies()
|
||||||
|
'viewName' Plex str for library view (e.g. 'Movies')
|
||||||
|
'viewId' Plex Id to identifiy the library view
|
||||||
|
}
|
||||||
|
lock: threading.Lock(), used for counting where we are
|
||||||
|
userStop Handle to a function where True is used to stop this Thread
|
||||||
|
"""
|
||||||
|
def __init__(self, queue, data, lock, userStop):
|
||||||
|
self.queue = queue
|
||||||
|
self.lock = lock
|
||||||
|
self.data = data
|
||||||
|
self.userStop = userStop
|
||||||
|
self._shouldstop = threading.Event()
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# Constructs the method name, e.g. itemtypes.Movies
|
||||||
|
itemFkt = getattr(itemtypes, self.data['itemType'])
|
||||||
|
viewName = self.data['viewName']
|
||||||
|
viewId = self.data['viewId']
|
||||||
|
global processMetadataCount
|
||||||
|
with itemFkt() as item:
|
||||||
|
while self.stopped() is False:
|
||||||
|
# grabs item from queue
|
||||||
|
try:
|
||||||
|
plexitem = self.queue.get(block=False)
|
||||||
|
# Empty queue
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
# Do the work; lock to be sure we've only got 1 Thread
|
||||||
|
with self.lock:
|
||||||
|
item.add_update(
|
||||||
|
plexitem,
|
||||||
|
viewtag=viewName,
|
||||||
|
viewid=viewId
|
||||||
|
)
|
||||||
|
# Keep track of where we are at
|
||||||
|
processMetadataCount += 1
|
||||||
|
# signals to queue job is done
|
||||||
|
self.queue.task_done()
|
||||||
|
|
||||||
|
def stopThread(self):
|
||||||
|
self._shouldstop.set()
|
||||||
|
|
||||||
|
def stopped(self):
|
||||||
|
return self._shouldstop.isSet() or self.userStop()
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadedShowSyncInfo(threading.Thread):
|
||||||
|
"""
|
||||||
|
Threaded class to show the Kodi statusbar of the metadata download.
|
||||||
|
|
||||||
|
Input:
|
||||||
|
dialog xbmcgui.DialogProgressBG() object to show progress
|
||||||
|
locks = [downloadLock, processLock] Locks() to the other threads
|
||||||
|
total: Total number of items to get
|
||||||
|
viewName: Name of library we're getting
|
||||||
|
"""
|
||||||
|
def __init__(self, dialog, locks, total, viewName):
|
||||||
|
self.locks = locks
|
||||||
|
self.total = total
|
||||||
|
self.viewName = viewName
|
||||||
|
self.addonName = clientinfo.ClientInfo().getAddonName()
|
||||||
|
self._shouldstop = threading.Event()
|
||||||
|
self.dialog = dialog
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
total = self.total
|
||||||
|
downloadLock = self.locks[0]
|
||||||
|
processLock = self.locks[1]
|
||||||
|
self.dialog.create(
|
||||||
|
self.addonName + ": Sync " + self.viewName,
|
||||||
|
"Starting"
|
||||||
|
)
|
||||||
|
global getMetadataCount
|
||||||
|
global processMetadataCount
|
||||||
|
total = 2 * total
|
||||||
|
totalProgress = 0
|
||||||
|
while self.stopped() is not True:
|
||||||
|
with downloadLock:
|
||||||
|
getMetadataProgress = getMetadataCount
|
||||||
|
with processLock:
|
||||||
|
processMetadataProgress = processMetadataCount
|
||||||
|
totalProgress = getMetadataProgress + processMetadataProgress
|
||||||
|
percentage = int(float(totalProgress) / float(total)*100.0)
|
||||||
|
self.dialog.update(
|
||||||
|
percentage,
|
||||||
|
message="Downloaded: %s, Processed: %s" %
|
||||||
|
(getMetadataProgress, processMetadataProgress)
|
||||||
|
)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
def stopThread(self):
|
||||||
|
self._shouldstop.set()
|
||||||
|
|
||||||
|
def stopped(self):
|
||||||
|
return self._shouldstop.isSet()
|
||||||
|
|
||||||
|
|
||||||
class LibrarySync(threading.Thread):
|
class LibrarySync(threading.Thread):
|
||||||
|
|
||||||
_shared_state = {}
|
_shared_state = {}
|
||||||
|
@ -54,6 +214,7 @@ class LibrarySync(threading.Thread):
|
||||||
self.emby = embyserver.Read_EmbyServer()
|
self.emby = embyserver.Read_EmbyServer()
|
||||||
self.vnodes = videonodes.VideoNodes()
|
self.vnodes = videonodes.VideoNodes()
|
||||||
self.plx = PlexAPI.PlexAPI()
|
self.plx = PlexAPI.PlexAPI()
|
||||||
|
self.syncThreadNumber = int(utils.settings('syncThreadNumber'))
|
||||||
|
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
|
@ -84,14 +245,8 @@ class LibrarySync(threading.Thread):
|
||||||
# Verify if server plugin is installed.
|
# Verify if server plugin is installed.
|
||||||
if utils.settings('serverSync') == "true":
|
if utils.settings('serverSync') == "true":
|
||||||
# Try to use fast start up
|
# Try to use fast start up
|
||||||
url = "{server}/emby/Plugins?format=json"
|
completed = self.fastSync()
|
||||||
result = self.doUtils.downloadUrl(url)
|
|
||||||
|
|
||||||
for plugin in result:
|
|
||||||
if plugin['Name'] == "Emby.Kodi Sync Queue":
|
|
||||||
self.logMsg("Found server plugin.", 2)
|
|
||||||
completed = self.fastSync()
|
|
||||||
|
|
||||||
if not completed:
|
if not completed:
|
||||||
# Fast sync failed or server plugin is not found
|
# Fast sync failed or server plugin is not found
|
||||||
completed = self.fullSync(manualrun=True)
|
completed = self.fullSync(manualrun=True)
|
||||||
|
@ -186,15 +341,7 @@ class LibrarySync(threading.Thread):
|
||||||
connection.commit()
|
connection.commit()
|
||||||
self.logMsg("Commit successful.", 1)
|
self.logMsg("Commit successful.", 1)
|
||||||
|
|
||||||
def fullSync(self, manualrun=False, repair=False):
|
def initializeDBs(self):
|
||||||
# Only run once when first setting up. Can be run manually.
|
|
||||||
emby = self.emby
|
|
||||||
music_enabled = utils.settings('enableMusic') == "true"
|
|
||||||
|
|
||||||
utils.window('emby_dbScan', value="true")
|
|
||||||
# Add sources
|
|
||||||
utils.sourcesXML()
|
|
||||||
|
|
||||||
embyconn = utils.kodiSQL('emby')
|
embyconn = utils.kodiSQL('emby')
|
||||||
embycursor = embyconn.cursor()
|
embycursor = embyconn.cursor()
|
||||||
# Create the tables for the emby database
|
# Create the tables for the emby database
|
||||||
|
@ -210,8 +357,17 @@ class LibrarySync(threading.Thread):
|
||||||
embyconn.commit()
|
embyconn.commit()
|
||||||
|
|
||||||
# content sync: movies, tvshows, musicvideos, music
|
# content sync: movies, tvshows, musicvideos, music
|
||||||
kodiconn = utils.kodiSQL('video')
|
embyconn.close()
|
||||||
kodicursor = kodiconn.cursor()
|
return
|
||||||
|
|
||||||
|
def fullSync(self, manualrun=False, repair=False):
|
||||||
|
# Only run once when first setting up. Can be run manually.
|
||||||
|
self.compare = manualrun
|
||||||
|
music_enabled = utils.settings('enableMusic') == "true"
|
||||||
|
|
||||||
|
utils.window('emby_dbScan', value="true")
|
||||||
|
# Add sources
|
||||||
|
utils.sourcesXML()
|
||||||
|
|
||||||
if manualrun:
|
if manualrun:
|
||||||
message = "Manual sync"
|
message = "Manual sync"
|
||||||
|
@ -220,14 +376,13 @@ class LibrarySync(threading.Thread):
|
||||||
else:
|
else:
|
||||||
message = "Initial sync"
|
message = "Initial sync"
|
||||||
utils.window('emby_initialScan', value="true")
|
utils.window('emby_initialScan', value="true")
|
||||||
|
|
||||||
pDialog = self.progressDialog("%s" % message, forced=True)
|
|
||||||
starttotal = datetime.now()
|
starttotal = datetime.now()
|
||||||
|
|
||||||
# Set views
|
# Set views
|
||||||
# self.maintainViews(embycursor, kodicursor)
|
# self.maintainViews(embycursor, kodicursor)
|
||||||
# embyconn.commit()
|
# embyconn.commit()
|
||||||
|
self.initializeDBs()
|
||||||
# Sync video library
|
# Sync video library
|
||||||
process = {
|
process = {
|
||||||
|
|
||||||
|
@ -238,60 +393,48 @@ class LibrarySync(threading.Thread):
|
||||||
}
|
}
|
||||||
|
|
||||||
process = {
|
process = {
|
||||||
'movies': self.PlexMovies,
|
'movies': self.PlexMovies
|
||||||
|
# 'tvshows': self.PlexTVShows
|
||||||
}
|
}
|
||||||
|
|
||||||
for itemtype in process:
|
for itemtype in process:
|
||||||
startTime = datetime.now()
|
startTime = datetime.now()
|
||||||
completed = process[itemtype](embycursor, kodicursor, pDialog, compare=manualrun)
|
completed = process[itemtype]()
|
||||||
if not completed:
|
if not completed:
|
||||||
|
|
||||||
utils.window('emby_dbScan', clear=True)
|
utils.window('emby_dbScan', clear=True)
|
||||||
if pDialog:
|
|
||||||
pDialog.close()
|
|
||||||
|
|
||||||
embycursor.close()
|
|
||||||
kodicursor.close()
|
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
self.dbCommit(kodiconn)
|
|
||||||
embyconn.commit()
|
|
||||||
elapsedTime = datetime.now() - startTime
|
elapsedTime = datetime.now() - startTime
|
||||||
self.logMsg(
|
self.logMsg(
|
||||||
"SyncDatabase (finished %s in: %s)"
|
"SyncDatabase (finished %s in: %s)"
|
||||||
% (itemtype, str(elapsedTime).split('.')[0]), 1)
|
% (itemtype, str(elapsedTime).split('.')[0]), 0)
|
||||||
|
|
||||||
# sync music
|
# # sync music
|
||||||
if music_enabled:
|
# if music_enabled:
|
||||||
|
|
||||||
musicconn = utils.kodiSQL('music')
|
# musicconn = utils.kodiSQL('music')
|
||||||
musiccursor = musicconn.cursor()
|
# musiccursor = musicconn.cursor()
|
||||||
|
|
||||||
startTime = datetime.now()
|
# startTime = datetime.now()
|
||||||
completed = self.music(embycursor, musiccursor, pDialog, compare=manualrun)
|
# completed = self.music(embycursor, musiccursor, pDialog, compare=manualrun)
|
||||||
if not completed:
|
# if not completed:
|
||||||
|
|
||||||
utils.window('emby_dbScan', clear=True)
|
# utils.window('emby_dbScan', clear=True)
|
||||||
if pDialog:
|
|
||||||
pDialog.close()
|
|
||||||
|
|
||||||
embycursor.close()
|
# embycursor.close()
|
||||||
musiccursor.close()
|
# musiccursor.close()
|
||||||
return False
|
# return False
|
||||||
else:
|
# else:
|
||||||
musicconn.commit()
|
# musicconn.commit()
|
||||||
embyconn.commit()
|
# embyconn.commit()
|
||||||
elapsedTime = datetime.now() - startTime
|
# elapsedTime = datetime.now() - startTime
|
||||||
self.logMsg(
|
# self.logMsg(
|
||||||
"SyncDatabase (finished music in: %s)"
|
# "SyncDatabase (finished music in: %s)"
|
||||||
% (str(elapsedTime).split('.')[0]), 1)
|
# % (str(elapsedTime).split('.')[0]), 1)
|
||||||
musiccursor.close()
|
# musiccursor.close()
|
||||||
|
|
||||||
if pDialog:
|
|
||||||
pDialog.close()
|
|
||||||
|
|
||||||
embycursor.close()
|
|
||||||
kodicursor.close()
|
|
||||||
|
|
||||||
utils.settings('SyncInstallRunDone', value="true")
|
utils.settings('SyncInstallRunDone', value="true")
|
||||||
utils.settings("dbCreatedWithVersion", self.clientInfo.getVersion())
|
utils.settings("dbCreatedWithVersion", self.clientInfo.getVersion())
|
||||||
self.saveLastSync()
|
self.saveLastSync()
|
||||||
|
@ -465,100 +608,176 @@ class LibrarySync(threading.Thread):
|
||||||
# Save total
|
# Save total
|
||||||
utils.window('Emby.nodes.total', str(totalnodes))
|
utils.window('Emby.nodes.total', str(totalnodes))
|
||||||
|
|
||||||
|
def GetUpdatelist(self, elementList):
|
||||||
|
"""
|
||||||
|
Adds items to self.updatelist as well as self.allPlexElementsId dict
|
||||||
|
|
||||||
|
Input:
|
||||||
|
elementList: List of elements, e.g. a list of '_children'
|
||||||
|
movie elements as received via JSON from PMS
|
||||||
|
|
||||||
def PlexMovies(self, embycursor, kodicursor, pdialog, compare=False):
|
Output: self.updatelist, self.allPlexElementsId
|
||||||
|
self.updatelist APPENDED(!!) list itemids (Plex Keys as
|
||||||
|
as received from API.getKey())
|
||||||
|
self.allPlexElementsId APPENDED(!!) dic
|
||||||
|
= {itemid: checksum}
|
||||||
|
"""
|
||||||
|
if self.compare:
|
||||||
|
# Manual sync
|
||||||
|
for plexmovie in elementList:
|
||||||
|
if self.shouldStop():
|
||||||
|
return False
|
||||||
|
API = PlexAPI.API(plexmovie)
|
||||||
|
plex_checksum = API.getChecksum()
|
||||||
|
itemid = API.getKey()
|
||||||
|
self.allPlexElementsId[itemid] = plex_checksum
|
||||||
|
kodi_checksum = self.allKodiElementsId.get(itemid)
|
||||||
|
if kodi_checksum != plex_checksum:
|
||||||
|
# Only update if movie is not in Kodi or checksum is
|
||||||
|
# different
|
||||||
|
self.updatelist.append(itemid)
|
||||||
|
else:
|
||||||
|
# Initial or repair sync: get all Plex movies
|
||||||
|
for plexmovie in elementList:
|
||||||
|
API = PlexAPI.API(plexmovie)
|
||||||
|
itemid = API.getKey()
|
||||||
|
plex_checksum = API.getChecksum()
|
||||||
|
self.allPlexElementsId[itemid] = plex_checksum
|
||||||
|
self.updatelist.append(itemid)
|
||||||
|
# Update the Kodi popup info
|
||||||
|
return self.updatelist, self.allPlexElementsId
|
||||||
|
|
||||||
|
def PlexMovies(self):
|
||||||
|
# Initialize
|
||||||
plx = PlexAPI.PlexAPI()
|
plx = PlexAPI.PlexAPI()
|
||||||
|
compare = self.compare
|
||||||
|
self.updatelist = []
|
||||||
|
self.allPlexElementsId = {}
|
||||||
|
# Initialze DBs
|
||||||
|
embyconn = utils.kodiSQL('emby')
|
||||||
|
embycursor = embyconn.cursor()
|
||||||
|
|
||||||
# Get movies from Plex server
|
# Get movies from Plex server
|
||||||
emby_db = embydb.Embydb_Functions(embycursor)
|
emby_db = embydb.Embydb_Functions(embycursor)
|
||||||
movies = itemtypes.Movies(embycursor, kodicursor)
|
|
||||||
|
|
||||||
views = plx.GetPlexCollections('movie')
|
views = plx.GetPlexCollections('movie')
|
||||||
|
self.logMsg("Media folders: %s" % views, 1)
|
||||||
|
|
||||||
if compare:
|
if compare:
|
||||||
# Pull the list of movies and boxsets in Kodi
|
# Pull the list of movies and boxsets in Kodi
|
||||||
try:
|
try:
|
||||||
all_kodimoviesId = dict(emby_db.getChecksum('Movie'))
|
self.allKodiElementsId = dict(emby_db.getChecksum('Movie'))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
all_kodimoviesId = {}
|
self.allKodiElementsId = {}
|
||||||
all_plexmoviesIds = {}
|
else:
|
||||||
|
# Getting all metadata, hence set Kodi elements to {}
|
||||||
|
self.allKodiElementsId = {}
|
||||||
|
embyconn.close()
|
||||||
|
|
||||||
##### PROCESS MOVIES #####
|
##### PROCESS MOVIES #####
|
||||||
for view in views:
|
for view in views:
|
||||||
updatelist = []
|
self.updatelist = []
|
||||||
if self.shouldStop():
|
if self.shouldStop():
|
||||||
return False
|
return False
|
||||||
# Get items per view
|
# Get items per view
|
||||||
viewId = view['id']
|
viewId = view['id']
|
||||||
viewName = view['name']
|
viewName = view['name']
|
||||||
if pdialog:
|
|
||||||
pdialog.update(
|
|
||||||
heading=self.addonName,
|
|
||||||
message="Gathering movies from view: %s..." % viewName
|
|
||||||
)
|
|
||||||
all_plexmovies = plx.GetPlexSectionResults(viewId)
|
all_plexmovies = plx.GetPlexSectionResults(viewId)
|
||||||
|
# Populate self.updatelist and self.allPlexElementsId
|
||||||
|
self.GetUpdatelist(all_plexmovies)
|
||||||
|
self.logMsg("Processed view %s with ID %s" % (viewName, viewId), 1)
|
||||||
|
|
||||||
if compare:
|
self.logMsg("self.updatelist: %s" % self.updatelist, 2)
|
||||||
# Manual sync
|
self.logMsg("self.allPlexElementsId: %s" % self.allPlexElementsId, 2)
|
||||||
if pdialog:
|
self.logMsg("self.allKodiElementsId: %s" % self.allKodiElementsId, 2)
|
||||||
pdialog.update(
|
|
||||||
heading=self.addonName,
|
|
||||||
message="Comparing movies from view: %s..." % viewName
|
|
||||||
)
|
|
||||||
for plexmovie in all_plexmovies:
|
|
||||||
if self.shouldStop():
|
|
||||||
return False
|
|
||||||
API = PlexAPI.API(plexmovie)
|
|
||||||
plex_checksum = API.getChecksum()
|
|
||||||
itemid = API.getKey()
|
|
||||||
kodi_checksum = all_kodimoviesId.get(itemid)
|
|
||||||
all_plexmoviesIds[itemid] = plex_checksum
|
|
||||||
if kodi_checksum != plex_checksum:
|
|
||||||
# Only update if movie is not in Kodi or checksum is different
|
|
||||||
updatelist.append(itemid)
|
|
||||||
else:
|
|
||||||
# Initial or repair sync: get all Plex movies
|
|
||||||
for plexmovie in all_plexmovies:
|
|
||||||
API = PlexAPI.API(plexmovie)
|
|
||||||
itemid = API.getKey()
|
|
||||||
plex_checksum = API.getChecksum()
|
|
||||||
all_plexmoviesIds[itemid] = plex_checksum
|
|
||||||
updatelist.append(itemid)
|
|
||||||
|
|
||||||
total = len(updatelist)
|
# Run through self.updatelist, get XML metadata per item
|
||||||
if pdialog:
|
# Initiate threads
|
||||||
pdialog.update(heading="Processing %s / %s items" % (viewName, total))
|
self.logMsg("=====================", 1)
|
||||||
|
self.logMsg("Starting sync threads", 1)
|
||||||
|
self.logMsg("=====================", 1)
|
||||||
|
getMetadataQueue = Queue.Queue()
|
||||||
|
processMetadataQueue = Queue.Queue()
|
||||||
|
getMetadataLock = threading.Lock()
|
||||||
|
processMetadataLock = threading.Lock()
|
||||||
|
# To keep track
|
||||||
|
global getMetadataCount
|
||||||
|
getMetadataCount = 0
|
||||||
|
global processMetadataCount
|
||||||
|
processMetadataCount = 0
|
||||||
|
# Spawn GetMetadata threads
|
||||||
|
threads = []
|
||||||
|
for i in range(self.syncThreadNumber):
|
||||||
|
t = ThreadedGetMetadata(
|
||||||
|
getMetadataQueue,
|
||||||
|
processMetadataQueue,
|
||||||
|
getMetadataLock,
|
||||||
|
self.shouldStop
|
||||||
|
)
|
||||||
|
t.setDaemon(True)
|
||||||
|
t.start()
|
||||||
|
threads.append(t)
|
||||||
|
self.logMsg("%s download threads spawned" % self.syncThreadNumber, 1)
|
||||||
|
# Populate queue: GetMetadata
|
||||||
|
for itemId in self.updatelist:
|
||||||
|
getMetadataQueue.put(itemId)
|
||||||
|
self.logMsg("Queue populated", 1)
|
||||||
|
# Spawn one more thread to process Metadata, once downloaded
|
||||||
|
data = {
|
||||||
|
'itemType': 'Movies',
|
||||||
|
'viewName': viewName,
|
||||||
|
'viewId': viewId
|
||||||
|
}
|
||||||
|
thread = ThreadedProcessMetadata(
|
||||||
|
processMetadataQueue,
|
||||||
|
data,
|
||||||
|
processMetadataLock,
|
||||||
|
self.shouldStop
|
||||||
|
)
|
||||||
|
thread.setDaemon(True)
|
||||||
|
thread.start()
|
||||||
|
threads.append(thread)
|
||||||
|
self.logMsg("Processing thread spawned", 1)
|
||||||
|
|
||||||
count = 0
|
# Start one thread to show sync progress
|
||||||
for itemid in updatelist:
|
dialog = xbmcgui.DialogProgressBG()
|
||||||
# Process individual movies
|
total = len(self.updatelist)
|
||||||
if self.shouldStop():
|
thread = ThreadedShowSyncInfo(
|
||||||
return False
|
dialog,
|
||||||
# Download Metadata
|
[getMetadataLock, processMetadataLock],
|
||||||
plexmovie = plx.GetPlexMetadata(itemid)
|
total,
|
||||||
# Check whether metadata is valid
|
viewName
|
||||||
title = plexmovie[0].attrib['title']
|
)
|
||||||
if pdialog:
|
thread.setDaemon(True)
|
||||||
percentage = int((float(count) / float(total))*100)
|
thread.start()
|
||||||
pdialog.update(percentage, message=title)
|
threads.append(thread)
|
||||||
count += 1
|
self.logMsg("Kodi Infobox thread spawned", 1)
|
||||||
# Download individual metadata
|
# Wait until finished
|
||||||
self.logMsg("Start parsing metadata for movie: %s" % title, 0)
|
getMetadataQueue.join()
|
||||||
movies.add_update(plexmovie, viewName, viewId)
|
processMetadataQueue.join()
|
||||||
|
# Kill threads
|
||||||
else:
|
self.logMsg("Waiting to kill threads", 1)
|
||||||
self.logMsg("Movies finished.", 2)
|
for thread in threads:
|
||||||
|
thread.stopThread()
|
||||||
|
self.logMsg("Stop sent to all threads", 1)
|
||||||
|
# Wait till threads are indeed dead
|
||||||
|
for thread in threads:
|
||||||
|
thread.join()
|
||||||
|
dialog.close()
|
||||||
|
self.logMsg("=====================", 1)
|
||||||
|
self.logMsg("Sync threads finished", 1)
|
||||||
|
self.logMsg("=====================", 1)
|
||||||
|
|
||||||
##### PROCESS DELETES #####
|
##### PROCESS DELETES #####
|
||||||
self.logMsg("all_plexmoviesIds: %s" % all_plexmoviesIds, 0)
|
|
||||||
self.logMsg("all_kodimoviesId: %s" % all_kodimoviesId, 0)
|
|
||||||
if compare:
|
if compare:
|
||||||
# Manual sync, process deletes
|
# Manual sync, process deletes
|
||||||
for kodimovie in all_kodimoviesId:
|
itemFkt = getattr(itemtypes, 'Movies')
|
||||||
if kodimovie not in all_plexmoviesIds:
|
with itemFkt() as item:
|
||||||
movies.remove(kodimovie)
|
for kodimovie in self.allKodiElementsId:
|
||||||
else:
|
if kodimovie not in self.allPlexElementsId:
|
||||||
self.logMsg("Movies compare finished.", 1)
|
item.remove(kodimovie)
|
||||||
|
else:
|
||||||
|
self.logMsg("Movies compare finished.", 1)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def movies(self, embycursor, kodicursor, pdialog, compare=False):
|
def movies(self, embycursor, kodicursor, pdialog, compare=False):
|
||||||
|
@ -915,6 +1134,176 @@ class LibrarySync(threading.Thread):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def PlexTVShows(self, embycursor, kodicursor):
|
||||||
|
# Initialize
|
||||||
|
plx = PlexAPI.PlexAPI()
|
||||||
|
compare = self.compare
|
||||||
|
pdialog = self.pDialog
|
||||||
|
self.updatelist = []
|
||||||
|
self.allPlexElementsId = {}
|
||||||
|
# Get shows from emby
|
||||||
|
emby_db = embydb.Embydb_Functions(embycursor)
|
||||||
|
tvshows = itemtypes.TVShows(embycursor, kodicursor)
|
||||||
|
|
||||||
|
views = plx.GetPlexCollections('show')
|
||||||
|
self.logMsg("Media folders: %s" % views, 1)
|
||||||
|
|
||||||
|
self.allKodiElementsId = {}
|
||||||
|
if compare:
|
||||||
|
# Pull the list of TV shows already in Kodi
|
||||||
|
try:
|
||||||
|
all_koditvshows = dict(emby_db.getChecksum('Series'))
|
||||||
|
self.allKodiElementsId.update(all_koditvshows)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
# Same for the episodes (sub-element of shows/series)
|
||||||
|
try:
|
||||||
|
all_kodiepisodes = dict(emby_db.getChecksum('Episode'))
|
||||||
|
self.allKodiElementsId.update(all_kodiepisodes)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for view in views:
|
||||||
|
self.updatelist = []
|
||||||
|
if self.shouldStop():
|
||||||
|
return False
|
||||||
|
# Get items per view
|
||||||
|
viewId = view['id']
|
||||||
|
viewName = view['name']
|
||||||
|
if pdialog:
|
||||||
|
pdialog.update(
|
||||||
|
heading=self.addonName,
|
||||||
|
message="Gathering TV Shows from view: %s..." % viewName)
|
||||||
|
all_plexTvShows = plx.GetPlexSectionResults(viewId)
|
||||||
|
|
||||||
|
# Populate self.updatelist with TV shows and self.allPlexElementsId
|
||||||
|
self.GetUpdatelist(all_plexTvShows, viewName)
|
||||||
|
|
||||||
|
# Run through self.updatelist, get XML metadata per item and safe
|
||||||
|
# to Kodi
|
||||||
|
self.ProcessUpdateList(tvshows, view)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if compare:
|
||||||
|
# Manual sync
|
||||||
|
for plexTvShow in all_plexTvShows:
|
||||||
|
if self.shouldStop():
|
||||||
|
return False
|
||||||
|
API = plx(plexTvShow)
|
||||||
|
plex_checksum = API.getChecksum()
|
||||||
|
itemid = API.getKey()
|
||||||
|
kodi_checksum = all_kodimoviesId.get(itemid)
|
||||||
|
all_plextvshowsIds[itemid] = itemid
|
||||||
|
kodi_checksum = all_koditvshows.get(itemid)
|
||||||
|
#if kodi_checksum != plex_checksum:
|
||||||
|
|
||||||
|
|
||||||
|
if all_koditvshows.get(itemid) != API.getChecksum():
|
||||||
|
# Only update if movie is not in Kodi or checksum is different
|
||||||
|
updatelist.append(itemid)
|
||||||
|
|
||||||
|
self.logMsg("TVShows to update for %s: %s" % (viewName, updatelist), 1)
|
||||||
|
embytvshows = emby.getFullItems(updatelist)
|
||||||
|
total = len(updatelist)
|
||||||
|
del updatelist[:]
|
||||||
|
else:
|
||||||
|
all_embytvshows = emby.getShows(viewId)
|
||||||
|
total = all_embytvshows['TotalRecordCount']
|
||||||
|
embytvshows = all_embytvshows['Items']
|
||||||
|
|
||||||
|
|
||||||
|
if pdialog:
|
||||||
|
pdialog.update(heading="Processing %s / %s items" % (viewName, total))
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
for embytvshow in embytvshows:
|
||||||
|
# Process individual show
|
||||||
|
if self.shouldStop():
|
||||||
|
return False
|
||||||
|
|
||||||
|
itemid = embytvshow['Id']
|
||||||
|
title = embytvshow['Name']
|
||||||
|
if pdialog:
|
||||||
|
percentage = int((float(count) / float(total))*100)
|
||||||
|
pdialog.update(percentage, message=title)
|
||||||
|
count += 1
|
||||||
|
tvshows.add_update(embytvshow, viewName, viewId)
|
||||||
|
|
||||||
|
if not compare:
|
||||||
|
# Process episodes
|
||||||
|
all_episodes = emby.getEpisodesbyShow(itemid)
|
||||||
|
for episode in all_episodes['Items']:
|
||||||
|
|
||||||
|
# Process individual show
|
||||||
|
if self.shouldStop():
|
||||||
|
return False
|
||||||
|
|
||||||
|
episodetitle = episode['Name']
|
||||||
|
if pdialog:
|
||||||
|
pdialog.update(percentage, message="%s - %s" % (title, episodetitle))
|
||||||
|
tvshows.add_updateEpisode(episode)
|
||||||
|
else:
|
||||||
|
if compare:
|
||||||
|
# Get all episodes in view
|
||||||
|
if pdialog:
|
||||||
|
pdialog.update(
|
||||||
|
heading="Emby for Kodi",
|
||||||
|
message="Comparing episodes from view: %s..." % viewName)
|
||||||
|
|
||||||
|
all_embyepisodes = emby.getEpisodes(viewId, basic=True)
|
||||||
|
for embyepisode in all_embyepisodes['Items']:
|
||||||
|
|
||||||
|
if self.shouldStop():
|
||||||
|
return False
|
||||||
|
|
||||||
|
API = api.API(embyepisode)
|
||||||
|
itemid = embyepisode['Id']
|
||||||
|
all_embyepisodesIds.add(itemid)
|
||||||
|
|
||||||
|
if all_kodiepisodes.get(itemid) != API.getChecksum():
|
||||||
|
# Only update if movie is not in Kodi or checksum is different
|
||||||
|
updatelist.append(itemid)
|
||||||
|
|
||||||
|
self.logMsg("Episodes to update for %s: %s" % (viewName, updatelist), 1)
|
||||||
|
embyepisodes = emby.getFullItems(updatelist)
|
||||||
|
total = len(updatelist)
|
||||||
|
del updatelist[:]
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
for episode in embyepisodes:
|
||||||
|
|
||||||
|
# Process individual episode
|
||||||
|
if self.shouldStop():
|
||||||
|
return False
|
||||||
|
|
||||||
|
title = episode['SeriesName']
|
||||||
|
episodetitle = episode['Name']
|
||||||
|
if pdialog:
|
||||||
|
percentage = int((float(count) / float(total))*100)
|
||||||
|
pdialog.update(percentage, message="%s - %s" % (title, episodetitle))
|
||||||
|
count += 1
|
||||||
|
tvshows.add_updateEpisode(episode)
|
||||||
|
else:
|
||||||
|
self.logMsg("TVShows finished.", 2)
|
||||||
|
|
||||||
|
##### PROCESS DELETES #####
|
||||||
|
if compare:
|
||||||
|
# Manual sync, process deletes
|
||||||
|
for koditvshow in all_koditvshows:
|
||||||
|
if koditvshow not in all_embytvshowsIds:
|
||||||
|
tvshows.remove(koditvshow)
|
||||||
|
else:
|
||||||
|
self.logMsg("TVShows compare finished.", 1)
|
||||||
|
|
||||||
|
for kodiepisode in all_kodiepisodes:
|
||||||
|
if kodiepisode not in all_embyepisodesIds:
|
||||||
|
tvshows.remove(kodiepisode)
|
||||||
|
else:
|
||||||
|
self.logMsg("Episodes compare finished.", 1)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def tvshows(self, embycursor, kodicursor, pdialog, compare=False):
|
def tvshows(self, embycursor, kodicursor, pdialog, compare=False):
|
||||||
# Get shows from emby
|
# Get shows from emby
|
||||||
emby = self.emby
|
emby = self.emby
|
||||||
|
@ -1117,16 +1506,11 @@ class LibrarySync(threading.Thread):
|
||||||
for type in types:
|
for type in types:
|
||||||
|
|
||||||
if pdialog:
|
if pdialog:
|
||||||
pdialog.update(
|
pass
|
||||||
heading="Emby for Kodi",
|
|
||||||
message="Gathering %s..." % type)
|
|
||||||
|
|
||||||
if compare:
|
if compare:
|
||||||
# Manual Sync
|
# Manual Sync
|
||||||
if pdialog:
|
if pdialog:
|
||||||
pdialog.update(
|
pass
|
||||||
heading="Emby for Kodi",
|
|
||||||
message="Comparing %s..." % type)
|
|
||||||
|
|
||||||
if type != "artists":
|
if type != "artists":
|
||||||
all_embyitems = process[type][0](basic=True)
|
all_embyitems = process[type][0](basic=True)
|
||||||
|
@ -1165,7 +1549,7 @@ class LibrarySync(threading.Thread):
|
||||||
embyitems = all_embyitems['Items']
|
embyitems = all_embyitems['Items']
|
||||||
|
|
||||||
if pdialog:
|
if pdialog:
|
||||||
pdialog.update(heading="Processing %s / %s items" % (type, total))
|
pass
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
for embyitem in embyitems:
|
for embyitem in embyitems:
|
||||||
|
|
|
@ -68,5 +68,6 @@
|
||||||
<setting id="restartMsg" type="bool" label="Enable server message when it's restarting" default="false" />
|
<setting id="restartMsg" type="bool" label="Enable server message when it's restarting" default="false" />
|
||||||
<setting id="newContent" type="bool" label="Enable new content notification" default="false" />
|
<setting id="newContent" type="bool" label="Enable new content notification" default="false" />
|
||||||
<setting label="30239" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=reset)" option="close" />
|
<setting label="30239" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=reset)" option="close" />
|
||||||
|
<setting id="syncThreadNumber" type="number" label="Number of parallel threads while syncing" default="20" option="int"/>
|
||||||
</category>
|
</category>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
Loading…
Reference in a new issue