From 40c02395a257c4af85176d3e88a686799e75d8c5 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Thu, 14 Jan 2016 16:17:24 +0100 Subject: [PATCH] Added UpdateUserdata to Movies and TV Shows --- resources/lib/PlexAPI.py | 57 ++++++++------- resources/lib/itemtypes.py | 132 +++++++++++------------------------ resources/lib/librarysync.py | 40 +++++++++-- 3 files changed, 108 insertions(+), 121 deletions(-) diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 1488c194..e0a8dbb2 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -1412,37 +1412,44 @@ class PlexAPI(): }) return collections - def GetPlexSectionResults(self, viewId): + def GetPlexSectionResults(self, viewId, headerOptions={}): """ Returns a list (raw JSON API dump) of all Plex items in the Plex section with key = viewId. """ result = [] url = "{server}/library/sections/%s/all" % viewId - jsondata = self.doUtils.downloadUrl(url) + jsondata = self.doUtils.downloadUrl(url, headerOptions=headerOptions) try: result = jsondata['_children'] - except KeyError: - self.logMsg("Error retrieving all items for Plex section %s" % viewId, -1) + except TypeError: + # Received an XML pass + except: + self.logMsg("Error retrieving all items for Plex section %s" + % viewId, -1) + return [] return result - def GetAllPlexLeaves(self, key): + def GetAllPlexLeaves(self, viewId, headerOptions={}): """ Returns a list (raw JSON API dump) of all Plex subitems for the key. - (e.g. /library/metadata/194853/allLeaves pointing to all episodes - of a TV show) + (e.g. /library/sections/2/allLeaves pointing to all TV shows) Input: - key Key to a Plex item, e.g. 12345 + viewId Id of Plex library, e.g. '2' """ result = [] - url = "{server}/library/metadata/%s/allLeaves" % key - jsondata = self.doUtils.downloadUrl(url) + url = "{server}/library/sections/%s/allLeaves" % viewId + jsondata = self.doUtils.downloadUrl(url, headerOptions=headerOptions) try: result = jsondata['_children'] + except TypeError: + # received an XMl + pass except KeyError: - self.logMsg("Error retrieving all children for Plex item %s" % key, -1) + self.logMsg("Error retrieving all children for Plex viewId %s" + % viewId, -1) pass return result @@ -1517,6 +1524,8 @@ class API(): self.server = utils.window('emby_server%s' % self.userId) self.token = utils.window('emby_accessToken%s' % self.userId) + self.jumpback = int(utils.settings('resumeJumpBack')) + def logMsg(self, msg, lvl=1): className = self.__class__.__name__ utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) @@ -1643,6 +1652,7 @@ class API(): 'Played': played, # True/False 'LastPlayedDate': lastPlayedDate, 'Resume': resume, # Resume time in seconds + 'Runtime': runtime, 'Rating': rating } """ @@ -1670,17 +1680,14 @@ class API(): except KeyError: lastPlayedDate = None - try: - resume = float(item['viewOffset']) * 1.0/1000.0 - resume = round(resume, 6) - except KeyError: - resume = 0.0 + resume, runtime = self.getRuntime() return { 'Favorite': favorite, 'PlayCount': playcount, 'Played': played, 'LastPlayedDate': lastPlayedDate, 'Resume': resume, + 'Runtime': runtime, 'Rating': rating } @@ -1890,6 +1897,14 @@ class API(): resume = float(item['viewOffset']) except KeyError: resume = 0.0 + + # Adjust the resume point by x seconds as chosen by the user in the + # settings + if resume: + # To avoid negative bookmark + if resume > self.jumpback: + resume = resume - self.jumpback + runtime = runtime * time_factor resume = resume * time_factor resume = round(resume, 6) @@ -2465,16 +2480,6 @@ class API(): urlencode(args) return url - def adjustResume(self, resume_seconds): - resume = 0 - if resume_seconds: - resume = round(float(resume_seconds), 6) - jumpback = int(utils.settings('resumeJumpBack')) - if resume > jumpback: - # To avoid negative bookmark - resume = resume - jumpback - return resume - def externalSubs(self, playurl): externalsubs = [] mapping = {} diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 5c3cdd61..c274ae24 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -281,6 +281,26 @@ class Movies(Items): count += 1 self.add_updateBoxset(boxset) + def updateUserdata(self, itemList): + """ + Updates the Kodi watched state of the item from PMS. Also retrieves + Plex resume points for movies in progress. + """ + API = PlexAPI.API(itemList) + for itemNumber in range(0, len(itemList)): + API.setChildNumber(itemNumber) + itemid = API.getKey() + # Get key and db entry on the Kodi db side + fileid = self.emby_db.getItem_byId(itemid)[1] + # Grab the user's viewcount, resume points etc. from PMS' answer + userdata = API.getUserData() + # Write to Kodi DB + self.kodi_db.addPlaystate(fileid, + userdata['Resume'], + userdata['Runtime'], + userdata['PlayCount'], + userdata['LastPlayedDate']) + def add_update(self, item, viewtag=None, viewid=None): # Process single movie kodicursor = self.kodicursor @@ -485,48 +505,8 @@ class Movies(Items): # tags.append("Favorite movies") kodi_db.addTags(movieid, tags, "movie") # Process playstates - resume = API.adjustResume(userdata['Resume']) kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed) - def updateUserdata(self, item): - # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks - # Poster with progress bar - emby_db = self.emby_db - kodi_db = self.kodi_db - API = api.API(item) - - # Get emby information - itemid = item['Id'] - checksum = API.getChecksum() - userdata = API.getUserData() - runtime = API.getRuntime() - - # Get Kodi information - emby_dbitem = emby_db.getItem_byId(itemid) - try: - movieid = emby_dbitem[0] - fileid = emby_dbitem[1] - self.logMsg( - "Update playstate for movie: %s fileid: %s" - % (item['Name'], fileid), 1) - except TypeError: - return - - # Process favorite tags - if userdata['Favorite']: - kodi_db.addTag(movieid, "Favorite movies", "movie") - else: - kodi_db.removeTag(movieid, "Favorite movies", "movie") - - # Process playstates - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - resume = API.adjustResume(userdata['Resume']) - total = round(float(runtime), 6) - - kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) - emby_db.updateReference(itemid, checksum) - def remove(self, itemid): # Remove movieid, fileid, emby reference emby_db = self.emby_db @@ -1171,6 +1151,26 @@ class TVShows(Items): if not pdialog and self.contentmsg: self.contentPop(title) + def updateUserdata(self, itemList): + """ + Updates the Kodi watched state of the item from PMS. Also retrieves + Plex resume points for movies in progress. + """ + API = PlexAPI.API(itemList) + for itemNumber in range(0, len(itemList)): + API.setChildNumber(itemNumber) + itemid = API.getKey() + # Get key and db entry on the Kodi db side + fileid = self.emby_db.getItem_byId(itemid)[1] + # Grab the user's viewcount, resume points etc. from PMS' answer + userdata = API.getUserData() + # Write to Kodi DB + self.kodi_db.addPlaystate(fileid, + userdata['Resume'], + userdata['Runtime'], + userdata['PlayCount'], + userdata['LastPlayedDate']) + def add_update(self, item, viewtag=None, viewid=None): # Process single tvshow kodicursor = self.kodicursor @@ -1611,9 +1611,7 @@ class TVShows(Items): streams = API.getMediaStreams() kodi_db.addStreams(fileid, streams, runtime) # Process playstates - resume = API.adjustResume(userdata['Resume']) - total = round(float(runtime), 6) - kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) + kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed) if not self.directpath and resume: # Create additional entry for widgets. This is only required for plugin/episode. temppathid = kodi_db.getPath("plugin://plugin.video.plexkodiconnect.tvshows/") @@ -1625,53 +1623,7 @@ class TVShows(Items): "WHERE idFile = ?" )) kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid)) - kodi_db.addPlaystate(tempfileid, resume, total, playcount, dateplayed) - - def updateUserdata(self, item): - # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks - # Poster with progress bar - emby_db = self.emby_db - kodi_db = self.kodi_db - API = api.API(item) - - # Get emby information - itemid = item['Id'] - checksum = API.getChecksum() - userdata = API.getUserData() - runtime = API.getRuntime() - - # Get Kodi information - emby_dbitem = emby_db.getItem_byId(itemid) - try: - kodiid = emby_dbitem[0] - fileid = emby_dbitem[1] - mediatype = emby_dbitem[4] - self.logMsg( - "Update playstate for %s: %s fileid: %s" - % (mediatype, item['Name'], fileid), 1) - except TypeError: - return - - # Process favorite tags - if mediatype == "tvshow": - if userdata['Favorite']: - kodi_db.addTag(kodiid, "Favorite tvshows", "tvshow") - else: - kodi_db.removeTag(kodiid, "Favorite tvshows", "tvshow") - - # Process playstates - if mediatype == "episode": - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - resume = API.adjustResume(userdata['Resume']) - total = round(float(runtime), 6) - - kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) - if not self.directpath and not resume: - # Make sure there's no other bookmarks created by widget. - filename = kodi_db.getFile(fileid) - kodi_db.removeFile("plugin://plugin.video.plexkodiconnect.tvshows/", filename) - emby_db.updateReference(itemid, checksum) + kodi_db.addPlaystate(tempfileid, resume, runtime, playcount, dateplayed) def remove(self, itemid): # Remove showid, fileid, pathid, emby reference diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 19c509ce..e0b1fdb6 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -302,7 +302,6 @@ class LibrarySync(threading.Thread): # Save last sync time overlap = 2 - self.logMsg("An exception occurred: %s" % e, 1) time_now = datetime.utcnow()-timedelta(minutes=overlap) lastSync = time_now.strftime('%Y-%m-%dT%H:%M:%SZ') self.logMsg("New sync time: client time -%s min: %s" @@ -755,6 +754,10 @@ class LibrarySync(threading.Thread): viewId) self.GetAndProcessXMLs(itemType) self.logMsg("Processed view %s with ID %s" % (viewName, viewId), 1) + # Update viewstate + for view in views: + self.PlexUpdateWatched(view['id'], itemType) + ##### PROCESS DELETES ##### if self.compare: # Manual sync, process deletes @@ -765,6 +768,27 @@ class LibrarySync(threading.Thread): self.logMsg("%s sync is finished." % itemType, 1) return True + def PlexUpdateWatched(self, viewId, itemType): + """ + Updates ALL plex elements' view status ('watched' or 'unwatched') and + also updates resume times. + + This is done by downloading one XML for ALL elements with viewId + """ + starttotal = datetime.now() + plx = PlexAPI.PlexAPI() + # Download XML, not JSON + headerOptions = {'Accept': 'application/xml'} + plexItems = plx.GetAllPlexLeaves(viewId, + headerOptions=headerOptions) + itemMth = getattr(itemtypes, itemType) + with itemMth() as method: + method.UpdateWatched(plexItems) + + elapsedtotal = datetime.now() - starttotal + self.logMsg("Syncing userdata for itemtype %s and viewid %s took " + "%s seconds" % (itemType, viewId, elapsedtotal), 0) + def musicvideos(self, embycursor, kodicursor, pdialog, compare=False): # Get musicvideos from emby emby = self.emby @@ -1022,22 +1046,24 @@ class LibrarySync(threading.Thread): 'add_updateSeason', None, tvShowId) # send showId instead of viewid - self.logMsg("Analyzed all seasons of TV show with Plex Id %s" % tvShowId, 1) + self.logMsg("Analyzed all seasons of TV show with Plex Id %s" + % tvShowId, 1) ##### PROCESS TV Episodes ##### # Cycle through tv shows - for tvShowId in allPlexTvShowsId: + for view in views: if self.shouldStop(): return False # Grab all episodes to tvshow from PMS - episodes = plx.GetAllPlexLeaves(tvShowId) + episodes = plx.GetAllPlexLeaves(view['id']) # Populate self.updatelist and self.allPlexElementsId self.GetUpdatelist(episodes, itemType, 'add_updateEpisode', None, None) - self.logMsg("Analyzed all episodes of TV show with Plex Id %s" % tvShowId, 1) + self.logMsg("Analyzed all episodes of TV show with Plex Id %s" + % tvShowId, 1) # Process self.updatelist self.GetAndProcessXMLs(itemType) @@ -1050,6 +1076,10 @@ class LibrarySync(threading.Thread): TVshow.refreshSeasonEntry(XMLtvshow, tvShowId) self.logMsg("Season info refreshed", 1) + # Update viewstate: + for view in views: + self.PlexUpdateWatched(view['id'], itemType) + ##### PROCESS DELETES ##### if self.compare: # Manual sync, process deletes