diff --git a/addon.xml b/addon.xml index 185e4df6..02d24ba9 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index e1149bd3..cf3281ca 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,23 @@ +version 2.2.1 +- Fix artist/album link for music videos +- Fix progress dialog when the manual sync runs at start up +- Fix encoding error for special characters in emby username +- Offer delete dialog after playback now times out after 2 mins + +version 2.1.4 +- Removed Emby delete via the Kodi context menu. It is exclusively offered via the extended emby context menu which is available for Isengard or higher. This change was necessary, because there was a risk of wiping the entire library if Kodi decides to run a clean database task and paths were set as plugin paths. + +version 2.1.3 +- Fix Live TV to terminate ffmpeg processes. + +version 2.1.2 +- Fix to repair entries if they are deleted by Kodi, but still exists in the Emby database. + +version 2.1.1 +- Update setting - skip emby delete confirmation, it is now under the extras tab. +- Update setting - new content notification, it's now disables the notification if the time is set to 0. +- Prevent manual sync from running if the add-on is not yet connected to the emby server. + version 2.1.0 - Add a throttle (automatically adjust the number of items requested at once) to prevent crashing during the initial sync - Do not update the video library when there's a music-only update diff --git a/contextmenu.py b/contextmenu.py index ab557b9f..66256dca 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -27,7 +27,7 @@ import musicutils as musicutils import api def logMsg(msg, lvl=1): - utils.logMsg("%s %s" % ("Emby", "Contextmenu"), msg, lvl) + utils.logMsg("%s %s" % ("EMBY", "Contextmenu"), msg, lvl) #Kodi contextmenu item to configure the emby settings @@ -127,11 +127,32 @@ if __name__ == '__main__': if options[ret] == utils.language(30409): #delete item from the server - if xbmcgui.Dialog().yesno("Do you really want to delete this item ?", "This will delete the item from the server and the file(s) from disk!"): + delete = True + if utils.settings('skipContextMenu') != "true": + resp = xbmcgui.Dialog().yesno( + heading="Confirm delete", + line1=("Delete file from Emby Server? This will " + "also delete the file(s) from disk!")) + if not resp: + logMsg("User skipped deletion for: %s." % embyid, 1) + delete = False + + if delete: import downloadutils doUtils = downloadutils.DownloadUtils() url = "{server}/emby/Items/%s?format=json" % embyid + logMsg("Deleting request: %s" % embyid, 0) doUtils.downloadUrl(url, type="DELETE") + + '''if utils.settings('skipContextMenu') != "true": + if xbmcgui.Dialog().yesno( + heading="Confirm delete", + line1=("Delete file on Emby Server? This will " + "also delete the file(s) from disk!")): + import downloadutils + doUtils = downloadutils.DownloadUtils() + url = "{server}/emby/Items/%s?format=json" % embyid + doUtils.downloadUrl(url, type="DELETE")''' xbmc.sleep(500) xbmc.executebuiltin("Container.Update") \ No newline at end of file diff --git a/default.py b/default.py index dc77c997..19ffb0b9 100644 --- a/default.py +++ b/default.py @@ -112,7 +112,7 @@ class Main: import librarysync lib = librarysync.LibrarySync() if mode == "manualsync": - librarysync.ManualSync() + librarysync.ManualSync(dialog=True) else: lib.fullSync(repair=True) else: diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 6effb2c2..d20dbf0e 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -18,7 +18,6 @@ Advanced Username - Use SIMPLEJSON instead of JSON Port Number Number of recent Movies to show: @@ -75,7 +74,7 @@ Western Genre Filter - Confirm file delete? + Confirm file deletion Delete this item? This action will delete media and associated data files. Mark Watched @@ -105,11 +104,8 @@ Show Load Progress Loading Content Retrieving Data - Parsing Jason Data - Downloading Jason Data Done Processing Item : - YOUCANUSETHIS Play Error This item is not playable Local path detected @@ -279,14 +275,14 @@ Direct stream music library Playback Mode Force artwork caching - Limit artwork cache threads + Limit artwork cache threads (recommended for rpi) Enable fast startup (requires server plugin) Maximum items to request from the server at once Playback Network credentials Enable Emby cinema mode Ask to play trailers - Skip delete confirmation + Skip Emby delete confirmation for the context menu (use at your own risk) Jump back on resume (in seconds) Force transcode H265 Music metadata options (not compatible with direct stream) @@ -300,6 +296,7 @@ Enable new content notification Duration of the video library pop up (in seconds) Duration of the music library pop up (in seconds) + Server messages Welcome @@ -309,5 +306,30 @@ items added to playlist items queued to playlist Server is restarting + Access is enabled + Enter password for user: + Invalid username or password + Failed to authenticate too many times + Unable to direct play + Direct play failed 3 times. Enabled play from HTTP. + Choose the audio stream + Choose the subtitles stream + Delete file from your Emby server? + Play trailers? + Gathering movies from: + Gathering boxsets + Gathering music videos from: + Gathering tv shows from: + Gathering: + Detected the database needs to be recreated for this version of Emby for Kodi. Proceed? + Emby for Kod may not work correctly until the database is reset. + Cancelling the database syncing process. The current Kodi version is unsupported. + completed in: + Comparing movies from: + Comparing boxsets + Comparing music videos from: + Comparing tv shows from: + Comparing episodes from: + Comparing: diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 43864b82..8f649f88 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -314,6 +314,17 @@ class Movies(Items): kodicursor.execute("select coalesce(max(idMovie),0) from movie") movieid = kodicursor.fetchone()[0] + 1 + else: + # Verification the item is still in Kodi + query = "SELECT * FROM movie WHERE idMovie = ?" + kodicursor.execute(query, (movieid,)) + try: + kodicursor.fetchone()[0] + except TypeError: + # item is not found, let's recreate it. + update_item = False + self.logMsg("movieid: %s missing from Kodi, repairing the entry." % movieid, 1) + if not viewtag or not viewid: # Get view tag from emby viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) @@ -577,6 +588,17 @@ class MusicVideos(Items): kodicursor.execute("select coalesce(max(idMVideo),0) from musicvideo") mvideoid = kodicursor.fetchone()[0] + 1 + else: + # Verification the item is still in Kodi + query = "SELECT * FROM musicvideo WHERE idMVideo = ?" + kodicursor.execute(query, (mvideoid,)) + try: + kodicursor.fetchone()[0] + except TypeError: + # item is not found, let's recreate it. + update_item = False + self.logMsg("mvideoid: %s missing from Kodi, repairing the entry." % mvideoid, 1) + if not viewtag or not viewid: # Get view tag from emby viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) @@ -896,12 +918,38 @@ class TVShows(Items): if not itemid: self.logMsg("Cannot parse XML data for TV show", -1) return + # If the item already exist in the local Kodi DB we'll perform a full item update + # If the item doesn't exist, we'll add it to the database + update_item = True + force_episodes = False + itemid = item['Id'] emby_dbitem = emby_db.getItem_byId(itemid) try: showid = emby_dbitem[0] pathid = emby_dbitem[2] except TypeError: update_item = False + kodicursor.execute("select coalesce(max(idShow),0) from tvshow") + showid = kodicursor.fetchone()[0] + 1 + + else: + # Verification the item is still in Kodi + query = "SELECT * FROM tvshow WHERE idShow = ?" + kodicursor.execute(query, (showid,)) + try: + kodicursor.fetchone()[0] + except TypeError: + # item is not found, let's recreate it. + update_item = False + self.logMsg("showid: %s missing from Kodi, repairing the entry." % showid, 1) + # Force re-add episodes after the show is re-created. + force_episodes = True + + + if viewtag is None or viewid is None: + # Get view tag from emby + viewtag, viewid, mediatype = emby.getView_embyId(itemid) + self.logMsg("View tag found: %s" % viewtag, 2) # fileId information checksum = API.getChecksum() @@ -991,8 +1039,6 @@ class TVShows(Items): pathid = kodi_db.addPath(path) # Create the tvshow entry - kodicursor.execute("select coalesce(max(idShow),0) from tvshow") - showid = kodicursor.fetchone()[0] + 1 query = ( ''' INSERT INTO tvshow( @@ -1035,15 +1081,13 @@ class TVShows(Items): tags = [viewtag] kodi_db.addTags(showid, tags, "tvshow") - def refreshSeasonEntry(self, item, showid): - API = PlexAPI.API(item) - kodicursor = self.kodicursor - kodi_db = self.kodi_db - # Finally, refresh the all season entry - seasonid = kodi_db.addSeason(showid, -1) - # Process artwork for season - allartworks = API.getAllArtwork() - artwork.addArtwork(allartworks, seasonid, "season", kodicursor) + if force_episodes: + # We needed to recreate the show entry. Re-add episodes now. + self.logMsg("Repairing episodes for showid: %s %s" % (showid, title), 1) + all_episodes = emby.getEpisodesbyShow(itemid) + self.added_episode(all_episodes['Items'], None) + + def add_updateSeason(self, item, showid=None): def add_updateSeason(self, item, viewid=None, viewtag=None): API = PlexAPI.API(item) @@ -1109,6 +1153,17 @@ class TVShows(Items): kodicursor.execute("select coalesce(max(idEpisode),0) from episode") episodeid = kodicursor.fetchone()[0] + 1 + else: + # Verification the item is still in Kodi + query = "SELECT * FROM episode WHERE idEpisode = ?" + kodicursor.execute(query, (episodeid,)) + try: + kodicursor.fetchone()[0] + except TypeError: + # item is not found, let's recreate it. + update_item = False + self.logMsg("episodeid: %s missing from Kodi, repairing the entry." % episodeid, 1) + # fileId information checksum = API.getChecksum() dateadded = API.getDateCreated() diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodidb_functions.py index f4697738..00965a8f 100644 --- a/resources/lib/kodidb_functions.py +++ b/resources/lib/kodidb_functions.py @@ -299,6 +299,17 @@ class Kodidb_Functions(): ''' ) cursor.execute(query, (actorid, kodiid, mediatype)) + + elif "Artist" in type: + query = ( + ''' + INSERT OR REPLACE INTO actor_link( + actor_id, media_id, media_type) + + VALUES (?, ?, ?) + ''' + ) + cursor.execute(query, (actorid, kodiid, mediatype)) # Kodi Helix else: query = ' '.join(( @@ -423,6 +434,17 @@ class Kodidb_Functions(): cursor.execute(query, (actorid, kodiid)) + elif "Artist" in type: + query = ( + ''' + INSERT OR REPLACE INTO artistlinkmusicvideo( + idArtist, idMVideo) + + VALUES (?, ?) + ''' + ) + cursor.execute(query, (actorid, kodiid)) + # Add person image to art table if thumb: arttype = type.lower() diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index cf323100..7ad43b20 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -76,7 +76,7 @@ class KodiMonitor(xbmc.Monitor): kodiid = item['id'] type = item['type'] except (KeyError, TypeError): - self.logMsg("Properties already set for item.", 1) + self.logMsg("Item is invalid for playstate update.", 1) else: if ((utils.settings('useDirectPaths') == "1" and not type == "song") or (type == "song" and utils.settings('enableMusic') == "true")): @@ -159,8 +159,11 @@ class KodiMonitor(xbmc.Monitor): elif method == "VideoLibrary.OnRemove": - - try: + # Removed function, because with plugin paths + clean library, it will wipe + # entire library if user has permissions. Instead, use the emby context menu available + # in Isengard and higher version + pass + '''try: kodiid = data['id'] type = data['type'] except (KeyError, TypeError): @@ -176,7 +179,7 @@ class KodiMonitor(xbmc.Monitor): except TypeError: self.logMsg("Could not find itemid in emby database.", 1) else: - if utils.settings('skipConfirmDelete') != "true": + if utils.settings('skipContextMenu') != "true": resp = xbmcgui.Dialog().yesno( heading="Confirm delete", line1="Delete file on Emby Server?") @@ -184,11 +187,12 @@ class KodiMonitor(xbmc.Monitor): self.logMsg("User skipped deletion.", 1) embycursor.close() return + url = "{server}/emby/Items/%s?format=json" % itemid self.logMsg("Deleting request: %s" % itemid) doUtils.downloadUrl(url, type="DELETE") finally: - embycursor.close() + embycursor.close()''' elif method == "System.OnWake": diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index f5c9d8fa..d95aa307 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -213,7 +213,6 @@ class LibrarySync(Thread): self.__dict__ = self._shared_state self.clientInfo = clientinfo.ClientInfo() - self.doUtils = downloadutils.DownloadUtils() self.user = userclient.UserClient() self.emby = embyserver.Read_EmbyServer() self.vnodes = videonodes.VideoNodes() @@ -369,7 +368,6 @@ class LibrarySync(Thread): message = "Repair sync" else: message = "Initial sync" - utils.window('emby_initialScan', value="true") # Set new timestamp NOW because sync might take a while self.saveLastSync() starttotal = datetime.now() @@ -393,8 +391,6 @@ class LibrarySync(Thread): return False else: elapsedTime = datetime.now() - startTime - self.logMsg( - "SyncDatabase (finished %s in: %s)" % (itemtype, str(elapsedTime).split('.')[0]), 1) # Let kodi update the views in any case @@ -803,7 +799,6 @@ class LibrarySync(Thread): mvideos = itemtypes.MusicVideos(embycursor, kodicursor) views = emby_db.getView_byType('musicvideos') - self.logMsg("Media folders: %s" % views, 1) for view in views: @@ -817,7 +812,6 @@ class LibrarySync(Thread): if pdialog: pdialog.update( heading="Emby for Kodi", - message="Gathering musicvideos from view: %s..." % viewName) # Initial or repair sync all_embymvideos = emby.getMusicVideos(viewId, dialog=pdialog) @@ -840,7 +834,6 @@ class LibrarySync(Thread): count += 1 mvideos.add_update(embymvideo, viewName, viewId) else: - self.logMsg("MusicVideos finished.", 2) return True @@ -1023,13 +1016,6 @@ class LibrarySync(Thread): viewName, viewId) - # Remove items from Kodi db? - if self.compare: - # Manual sync, process deletes - with itemtypes.Music() as Music: - for item in self.allKodiElementsId: - if item not in self.allPlexElementsId: - Music.remove(item) def compareDBVersion(self, current, minimum): # It returns True is database is up to date. False otherwise. @@ -1080,13 +1066,9 @@ class LibrarySync(Thread): if (utils.window('emby_dbCheck') != "true" and self.installSyncDone): # Verify the validity of the database - currentVersion = utils.settings('dbCreatedWithVersion') - minVersion = utils.window('emby_minDBVersion') uptoDate = self.compareDBVersion(currentVersion, minVersion) if not uptoDate: - self.logMsg( - "Db version out of date: %s minimum version required: %s" % (currentVersion, minVersion), 0) resp = xbmcgui.Dialog().yesno( @@ -1103,7 +1085,6 @@ class LibrarySync(Thread): else: utils.reset() - utils.window('emby_dbCheck', value="true") if not startupComplete: # Also runs when installed first @@ -1113,8 +1094,6 @@ class LibrarySync(Thread): # Database does not exists self.logMsg( "The current Kodi version is incompatible " - "with the" + self.addonName + " add-on. Please visit " - "https://github.com/croneter/PlexKodiConnect " "to know which Kodi versions are supported.", 0) xbmcgui.Dialog().ok( @@ -1132,8 +1111,6 @@ class LibrarySync(Thread): startTime = datetime.now() librarySync = self.fullSync(manualrun=True) elapsedTime = datetime.now() - startTime - self.logMsg( - "SyncDatabase (finished in: %s) %s" % (str(elapsedTime).split('.')[0], librarySync), 1) # Only try the initial sync once per kodi session regardless # This will prevent an infinite loop in case something goes wrong. diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index 9bf398af..597c044f 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -34,6 +34,7 @@ class PlaybackUtils(): self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() + self.doUtils = downloadutils.DownloadUtils().downloadUrl self.userid = utils.window('emby_currUser') self.server = utils.window('emby_server%s' % self.userid) @@ -44,7 +45,9 @@ class PlaybackUtils(): def play(self, itemid, dbid=None): - self.logMsg("Play called.", 1) + log = self.logMsg + window = utils.window + settings = utils.settings item = self.item # Hack to get only existing entry in PMS response for THIS instance of @@ -54,6 +57,7 @@ class PlaybackUtils(): listitem = xbmcgui.ListItem() playutils = putils.PlayUtils(item[0]) + log("Play called.", 1) playurl = playutils.getPlayUrl() if not playurl: return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) @@ -80,7 +84,7 @@ class PlaybackUtils(): sizePlaylist = playlist.size() self.currentPosition = startPos - propertiesPlayback = utils.window('emby_playbackProps') == "true" + propertiesPlayback = window('emby_playbackProps') == "true" introsPlaylist = False dummyPlaylist = False @@ -96,13 +100,13 @@ class PlaybackUtils(): # Otherwise we get a loop. if not propertiesPlayback: - utils.window('emby_playbackProps', value="true") - self.logMsg("Setting up properties in playlist.", 1) + window('emby_playbackProps', value="true") + log("Setting up properties in playlist.", 1) if (not homeScreen and not seektime and - utils.window('emby_customPlaylist') != "true"): + window('emby_customPlaylist') != "true"): - self.logMsg("Adding dummy file to playlist.", 2) + log("Adding dummy file to playlist.", 2) dummyPlaylist = True playlist.add(playurl, listitem, index=startPos) # Remove the original item from playlist @@ -116,8 +120,8 @@ class PlaybackUtils(): ############### -- CHECK FOR INTROS ################ - if (utils.settings('enableCinema') == "true" and not seektime and - not utils.window('emby_customPlaylist') == "true"): + if (settings('enableCinema') == "true" and not seektime and + not window('emby_customPlaylist') == "true"): # if we have any play them when the movie/show is not being resumed xml = PF.GetPlexPlaylist( itemid, @@ -130,7 +134,7 @@ class PlaybackUtils(): if homeScreen and not seektime and not sizePlaylist: # Extend our current playlist with the actual item to play # only if there's no playlist first - self.logMsg("Adding main item to playlist.", 1) + log("Adding main item to playlist.", 1) self.pl.addtoPlaylist( dbid, PF.GetKodiTypeFromPlex(API.getType())) @@ -146,14 +150,14 @@ class PlaybackUtils(): # Playlist items don't fail on their first call - skip them # here, otherwise we'll get two 1st parts if (counter == 0 and - utils.window('emby_customPlaylist') == "true"): + window('emby_customPlaylist') == "true"): continue # Set listitem and properties for each additional parts API.setPartNumber(counter) additionalListItem = xbmcgui.ListItem() additionalPlayurl = playutils.getPlayUrl( partNumber=counter) - self.logMsg("Adding additional part: %s" % counter, 1) + log("Adding additional part: %s" % counter, 1) self.setProperties(additionalPlayurl, additionalListItem) self.setArtwork(additionalListItem) @@ -168,42 +172,42 @@ class PlaybackUtils(): if dummyPlaylist: # Added a dummy file to the playlist, # because the first item is going to fail automatically. - self.logMsg("Processed as a playlist. First item is skipped.", 1) + log("Processed as a playlist. First item is skipped.", 1) return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) # We just skipped adding properties. Reset flag for next time. elif propertiesPlayback: - self.logMsg("Resetting properties playback flag.", 2) - utils.window('emby_playbackProps', clear=True) + log("Resetting properties playback flag.", 2) + window('emby_playbackProps', clear=True) #self.pl.verifyPlaylist() ########## SETUP MAIN ITEM ########## # For transcoding only, ask for audio/subs pref - if utils.window('emby_%s.playmethod' % playurl) == "Transcode": - utils.window('emby_%s.playmethod' % playurl, clear=True) + if window('emby_%s.playmethod' % playurl) == "Transcode": + window('emby_%s.playmethod' % playurl, clear=True) playurl = playutils.audioSubsPref(playurl, listitem) - utils.window('emby_%s.playmethod' % playurl, value="Transcode") + window('emby_%s.playmethod' % playurl, value="Transcode") listitem.setPath(playurl) self.setProperties(playurl, listitem) ############### PLAYBACK ################ - if homeScreen and seektime and utils.window('emby_customPlaylist') != "true": - self.logMsg("Play as a widget item.", 1) + if homeScreen and seektime and window('emby_customPlaylist') != "true": + log("Play as a widget item.", 1) self.setListItem(listitem) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) - elif ((introsPlaylist and utils.window('emby_customPlaylist') == "true") or - (homeScreen and not sizePlaylist)): + elif ((introsPlaylist and window('emby_customPlaylist') == "true") or + (homeScreen and not sizePlaylist)): # Playlist was created just now, play it. - self.logMsg("Play playlist.", 1) + log("Play playlist.", 1) xbmc.Player().play(playlist, startpos=startPos) else: - self.logMsg("Play as a regular item.", 1) + log("Play as a regular item.", 1) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) def AddTrailers(self, xml): @@ -250,29 +254,31 @@ class PlaybackUtils(): return True def setProperties(self, playurl, listitem): + + window = utils.window # Set all properties necessary for plugin path playback itemid = self.API.getRatingKey() itemtype = self.API.getType() resume, runtime = self.API.getRuntime() embyitem = "emby_%s" % playurl - utils.window('%s.runtime' % embyitem, value=str(runtime)) - utils.window('%s.type' % embyitem, value=itemtype) - utils.window('%s.itemid' % embyitem, value=itemid) + window('%s.runtime' % embyitem, value=str(runtime)) + window('%s.type' % embyitem, value=itemtype) + window('%s.itemid' % embyitem, value=itemid) # We need to keep track of playQueueItemIDs for Plex Companion - utils.window( + window( 'plex_%s.playQueueItemID' % playurl, self.API.GetPlayQueueItemID()) - utils.window( + window( 'plex_%s.guid' % playurl, self.API.getGuid()) if itemtype == "episode": - utils.window('%s.refreshid' % embyitem, + window('%s.refreshid' % embyitem, value=self.API.getParentRatingKey()) else: - utils.window('%s.refreshid' % embyitem, value=itemid) + window('%s.refreshid' % embyitem, value=itemid) def externalSubs(self, playurl): diff --git a/resources/lib/player.py b/resources/lib/player.py index 44e2c79e..d2f3767f 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -31,7 +31,7 @@ class Player(xbmc.Player): self.__dict__ = self._shared_state self.clientInfo = clientinfo.ClientInfo() - self.doUtils = downloadutils.DownloadUtils() + self.doUtils = downloadutils.DownloadUtils().downloadUrl self.xbmcplayer = xbmc.Player() self.logMsg("Starting playback monitor.", 2) @@ -47,7 +47,10 @@ class Player(xbmc.Player): def GetPlayStats(self): return self.playStats - def onPlayBackStarted( self ): + def onPlayBackStarted(self): + + log = self.logMsg + window = utils.window # Will be called when xbmc starts playing a file xbmcplayer = self.xbmcplayer self.stopAll() @@ -66,7 +69,7 @@ class Player(xbmc.Player): except: pass if count == 5: # try 5 times - self.logMsg("Cancelling playback report...", 1) + log("Cancelling playback report...", 1) break else: count += 1 @@ -77,42 +80,37 @@ class Player(xbmc.Player): # Save currentFile for cleanup later utils.window('plex_lastPlayedFiled', value=currentFile) # We may need to wait for info to be set in kodi monitor - itemId = utils.window("emby_%s.itemid" % currentFile) + itemId = window("emby_%s.itemid" % currentFile) tryCount = 0 while not itemId: xbmc.sleep(200) - itemId = utils.window("emby_%s.itemid" % currentFile) + itemId = window("emby_%s.itemid" % currentFile) if tryCount == 20: # try 20 times or about 10 seconds - self.logMsg("Could not find itemId, cancelling playback report...", 1) + log("Could not find itemId, cancelling playback report...", 1) break else: tryCount += 1 else: - utils.window('Plex_currently_playing_itemid', value=itemId) - self.logMsg("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0) + window('Plex_currently_playing_itemid', value=itemId) + log("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0) # Only proceed if an itemId was found. embyitem = "emby_%s" % currentFile - runtime = utils.window("%s.runtime" % embyitem) - refresh_id = utils.window("%s.refreshid" % embyitem) - playMethod = utils.window("%s.playmethod" % embyitem) - itemType = utils.window("%s.type" % embyitem) - utils.window('emby_skipWatched%s' % itemId, value="true") - # Suspend library sync thread while movie is playing - self.logMsg("Playing itemtype is: %s" % itemType, 1) + customseek = window('emby_customPlaylist.seektime') + # Suspend library sync thread while movie is playing + log("Playing itemtype is: %s" % itemType, 1) if itemType in ['movie', 'audio']: - self.logMsg("Suspending library sync while playing", 1) - utils.window('suspend_LibraryThread', value='true') + log("Suspending library sync while playing", 1) + window('suspend_LibraryThread', value='true') - if (utils.window('emby_customPlaylist') == "true" and - utils.window('emby_customPlaylist.seektime')): + if (window('emby_customPlaylist') == "true" and + customseek)): # Start at, when using custom playlist (play to Kodi from webclient) - seektime = utils.window('emby_customPlaylist.seektime') - self.logMsg("Seeking to: %s" % seektime, 1) - xbmcplayer.seekTime(int(seektime)) - utils.window('emby_customPlaylist.seektime', clear=True) + log("Seeking to: %s" % customseek, 1) + xbmcplayer.seekTime(int(customseek)) + window('emby_customPlaylist.seektime', clear=True) seekTime = xbmcplayer.getTime() @@ -151,9 +149,8 @@ class Player(xbmc.Player): # Get the current audio track and subtitles if playMethod == "Transcode": # property set in PlayUtils.py - postdata['AudioStreamIndex'] = utils.window("%sAudioStreamIndex" % currentFile) - postdata['SubtitleStreamIndex'] = utils.window("%sSubtitleStreamIndex" - % currentFile) + postdata['AudioStreamIndex'] = window("%sAudioStreamIndex" % currentFile) + postdata['SubtitleStreamIndex'] = window("%sSubtitleStreamIndex" % currentFile) else: # Get the current kodi audio and subtitles and convert to Emby equivalent tracks_query = { @@ -194,11 +191,11 @@ class Player(xbmc.Player): # Number of audiotracks to help get Emby Index audioTracks = len(xbmc.Player().getAvailableAudioStreams()) - mapping = utils.window("%s.indexMapping" % embyitem) + mapping = window("%s.indexMapping" % embyitem) if mapping: # Set in playbackutils.py - self.logMsg("Mapping for external subtitles index: %s" % mapping, 2) + log("Mapping for external subtitles index: %s" % mapping, 2) externalIndex = json.loads(mapping) if externalIndex.get(str(indexSubs)): @@ -216,15 +213,15 @@ class Player(xbmc.Player): # Post playback to server - # self.logMsg("Sending POST play started: %s." % postdata, 2) - # self.doUtils.downloadUrl(url, postBody=postdata, type="POST") + # log("Sending POST play started: %s." % postdata, 2) + # self.doUtils(url, postBody=postdata, type="POST") # Ensure we do have a runtime try: runtime = int(runtime) except ValueError: runtime = xbmcplayer.getTotalTime() - self.logMsg("Runtime is missing, Kodi runtime: %s" % runtime, 1) + log("Runtime is missing, Kodi runtime: %s" % runtime, 1) playQueueVersion = utils.window( 'playQueueVersion') @@ -249,7 +246,7 @@ class Player(xbmc.Player): } self.played_info[currentFile] = data - self.logMsg("ADDING_FILE: %s" % self.played_info, 1) + log("ADDING_FILE: %s" % self.played_info, 1) # log some playback stats '''if(itemType != None): @@ -271,7 +268,7 @@ class Player(xbmc.Player): if not self.doNotify: return - self.logMsg("reportPlayback Called", 2) + log("reportPlayback Called", 2) xbmcplayer = self.xbmcplayer # Get current file @@ -300,7 +297,7 @@ class Player(xbmc.Player): "properties": ["volume", "muted"] } - } + } result = xbmc.executeJSONRPC(json.dumps(volume_query)) result = json.loads(result) result = result.get('result') @@ -389,7 +386,7 @@ class Player(xbmc.Player): if mapping: # Set in PlaybackUtils.py - self.logMsg("Mapping for external subtitles index: %s" % mapping, 2) + log("Mapping for external subtitles index: %s" % mapping, 2) externalIndex = json.loads(mapping) if externalIndex.get(str(indexSubs)): @@ -410,7 +407,7 @@ class Player(xbmc.Player): # Report progress via websocketclient # postdata = json.dumps(postdata) # self.ws.sendProgressUpdate(postdata) - self.doUtils.downloadUrl( + self.doUtils( "{server}/:/timeline?" + urlencode(postdata), type="GET") def onPlayBackPaused( self ): @@ -445,13 +442,16 @@ class Player(xbmc.Player): self.reportPlayback() def onPlayBackStopped( self ): + + log = self.logMsg + window = utils.window # Will be called when user stops xbmc playing a file - self.logMsg("ONPLAYBACK_STOPPED", 2) - utils.window('emby_customPlaylist', clear=True) - utils.window('emby_customPlaylist.seektime', clear=True) - utils.window('emby_playbackProps', clear=True) - self.logMsg("Clear playlist properties.", 1) - utils.window('suspend_LibraryThread', clear=True) + log("ONPLAYBACK_STOPPED", 2) + window('emby_customPlaylist', clear=True) + window('emby_customPlaylist.seektime', clear=True) + window('emby_playbackProps', clear=True) + window('suspend_LibraryThread', clear=True) + log("Clear playlist properties.", 1) self.stopAll() def onPlayBackEnded( self ): @@ -463,20 +463,24 @@ class Player(xbmc.Player): def stopAll(self): + log = self.logMsg + lang = utils.language + settings = utils.settings + doUtils = self.doUtils if not self.played_info: return - self.logMsg("Played_information: %s" % self.played_info, 1) + log("Played_information: %s" % self.played_info, 1) # Process each items for item in self.played_info: data = self.played_info.get(item) if data: - self.logMsg("Item path: %s" % item, 2) - self.logMsg("Item data: %s" % data, 2) + log("Item path: %s" % item, 2) + log("Item data: %s" % data, 2) runtime = data['runtime'] currentPosition = data['currentPosition'] @@ -493,9 +497,8 @@ class Player(xbmc.Player): # Runtime is 0. percentComplete = 0 - markPlayedAt = float(utils.settings('markPlayed')) / 100 - self.logMsg( - "Percent complete: %s Mark played at: %s" + markPlayedAt = float(settings('markPlayed')) / 100 + log("Percent complete: %s Mark played at: %s" % (percentComplete, markPlayedAt), 1) # Prevent manually mark as watched in Kodi monitor @@ -504,36 +507,33 @@ class Player(xbmc.Player): self.stopPlayback(data) # Stop transcoding if playMethod == "Transcode": - self.logMsg("Transcoding for %s terminated." % itemid, 1) + log("Transcoding for %s terminated." % itemid, 1) url = "{server}/video/:/transcode/universal/stop?session=%s" % self.clientInfo.getDeviceId() - doUtils.downloadUrl(url, type="GET") + doUtils(url, type="GET") # Send the delete action to the server. offerDelete = False - if type == "Episode" and utils.settings('deleteTV') == "true": + if type == "Episode" and settings('deleteTV') == "true": offerDelete = True - elif type == "Movie" and utils.settings('deleteMovies') == "true": + elif type == "Movie" and settings('deleteMovies') == "true": offerDelete = True - if utils.settings('offerDelete') != "true": + if settings('offerDelete') != "true": # Delete could be disabled, even if the subsetting is enabled. offerDelete = False # Plex: never delete offerDelete = False if percentComplete >= markPlayedAt and offerDelete: - if utils.settings('skipConfirmDelete') != "true": - resp = xbmcgui.Dialog().yesno( - heading="Confirm delete", - line1="Delete file on Emby Server?") - if not resp: - self.logMsg("User skipped deletion.", 1) - continue + resp = xbmcgui.Dialog().yesno(lang(30091), lang(33015), autoclose=120000) + if not resp: + log("User skipped deletion.", 1) + continue url = "{server}/emby/Items/%s?format=json" % itemid - self.logMsg("Deleting request: %s" % itemid) - doUtils.downloadUrl(url, type="DELETE") + log("Deleting request: %s" % itemid, 1) + doUtils(url, type="DELETE") # Clean the WINDOW properties for filename in self.played_info: @@ -567,4 +567,4 @@ class Player(xbmc.Player): 'duration': int(duration) } url = url + urlencode(args) - self.doUtils.downloadUrl(url, type="GET") + self.doUtils(url, type="GET") diff --git a/resources/lib/playlist.py b/resources/lib/playlist.py index bb42a9e1..322a752c 100644 --- a/resources/lib/playlist.py +++ b/resources/lib/playlist.py @@ -28,19 +28,26 @@ class Playlist(): self.emby = embyserver.Read_EmbyServer() def playAll(self, itemids, startat): + log = self.logMsg + window = utils.window + + embyconn = utils.kodiSQL('emby') + embycursor = embyconn.cursor() + emby_db = embydb.Embydb_Functions(embycursor) + player = xbmc.Player() playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) playlist.clear() - self.logMsg("---*** PLAY ALL ***---", 1) - self.logMsg("Items: %s and start at: %s" % (itemids, startat)) + log("---*** PLAY ALL ***---", 1) + log("Items: %s and start at: %s" % (itemids, startat), 1) started = False - utils.window('emby_customplaylist', value="true") + window('emby_customplaylist', value="true") if startat != 0: # Seek to the starting position - utils.window('emby_customplaylist.seektime', str(startat)) + window('emby_customplaylist.seektime', str(startat)) with embydb.GetEmbyDB() as emby_db: for itemid in itemids: @@ -67,12 +74,14 @@ class Playlist(): def modifyPlaylist(self, itemids): + log = self.logMsg + embyconn = utils.kodiSQL('emby') embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) - self.logMsg("---*** ADD TO PLAYLIST ***---", 1) - self.logMsg("Items: %s" % itemids, 1) + log("---*** ADD TO PLAYLIST ***---", 1) + log("Items: %s" % itemids, 1) player = xbmc.Player() playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) @@ -90,7 +99,7 @@ class Playlist(): # Add to playlist self.addtoPlaylist(dbid, mediatype) - self.logMsg("Adding %s to playlist." % itemid, 1) + log("Adding %s to playlist." % itemid, 1) self.verifyPlaylist() embycursor.close() diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index f60bac12..31e9622e 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -37,11 +37,14 @@ class PlayUtils(): Returns the playurl for the part with number partNumber (movie might consist of several files) """ + log = self.logMsg + window = utils.window + self.API.setPartNumber(partNumber) playurl = None if self.isDirectPlay(): - self.logMsg("File is direct playing.", 1) + log("File is direct playing.", 1) playurl = self.API.getTranscodeVideoPath('DirectPlay') playurl = playurl.encode('utf-8') # Set playmethod property @@ -55,7 +58,7 @@ class PlayUtils(): # utils.window('emby_%s.playmethod' % playurl, "DirectStream") elif self.isTranscoding(): - self.logMsg("File is transcoding.", 1) + log("File is transcoding.", 1) quality = { 'maxVideoBitrate': self.getBitrate(), 'videoResolution': self.getResolution(), @@ -64,7 +67,7 @@ class PlayUtils(): playurl = self.API.getTranscodeVideoPath('Transcode', quality=quality) # Set playmethod property - utils.window('emby_%s.playmethod' % playurl, value="Transcode") + window('emby_%s.playmethod' % playurl, value="Transcode") self.logMsg("The playurl is: %s" % playurl, 1) return playurl @@ -128,24 +131,26 @@ class PlayUtils(): def fileExists(self): + log = self.logMsg + if 'Path' not in self.item: # File has no path defined in server return False # Convert path to direct play path = self.directPlay() - self.logMsg("Verifying path: %s" % path, 1) + log("Verifying path: %s" % path, 1) if xbmcvfs.exists(path): - self.logMsg("Path exists.", 1) + log("Path exists.", 1) return True elif ":" not in path: - self.logMsg("Can't verify path, assumed linux. Still try to direct play.", 1) + log("Can't verify path, assumed linux. Still try to direct play.", 1) return True else: - self.logMsg("Failed to find file.") + log("Failed to find file.", 1) return False def h265enabled(self): @@ -186,7 +191,7 @@ class PlayUtils(): # Verify the bitrate if not self.isNetworkSufficient(): - self.logMsg("The network speed is insufficient to direct stream file.", 1) + log("The network speed is insufficient to direct stream file.", 1) return False return True @@ -298,6 +303,9 @@ class PlayUtils(): return res[chosen] def audioSubsPref(self, listitem, url, part=None): + log = self.logMsg + lang = utils.language + dialog = xbmcgui.Dialog() # For transcoding only # Present the list of audio to select from audioStreamsList = [] @@ -365,7 +373,7 @@ class PlayUtils(): subNum += 1 if audioNum > 1: - resp = xbmcgui.Dialog().select("Choose the audio stream", audioStreams) + resp = dialog.select(lang(33013), audioStreams) if resp > -1: # User selected audio playurlprefs['audioStreamID'] = audioStreamsList[resp] @@ -378,7 +386,7 @@ class PlayUtils(): playurlprefs['audioBoost'] = utils.settings('audioBoost') if subNum > 1: - resp = xbmcgui.Dialog().select("Choose the subtitle stream", subtitleStreams) + resp = dialog.select(lang(33014), subtitleStreams) if resp == 0: # User selected no subtitles playurlprefs["skipSubtitles"] = 1 diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index 6e808a7a..6cb47b3b 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -174,6 +174,8 @@ class Read_EmbyServer(): def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None): + log = self.logMsg + doUtils = self.doUtils items = { @@ -199,7 +201,7 @@ class Read_EmbyServer(): items['TotalRecordCount'] = total except TypeError: # Failed to retrieve - self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2) + log("%s:%s Failed to retrieve the server response." % (url, params), 2) else: index = 0 @@ -241,27 +243,27 @@ class Read_EmbyServer(): # Something happened to the connection if not throttled: throttled = True - self.logMsg("Throttle activated.", 1) + log("Throttle activated.", 1) if jump == highestjump: # We already tried with the highestjump, but it failed. Reset value. - self.logMsg("Reset highest value.", 1) + log("Reset highest value.", 1) highestjump = 0 # Lower the number by half if highestjump: throttled = False jump = highestjump - self.logMsg("Throttle deactivated.", 1) + log("Throttle deactivated.", 1) else: jump = int(jump/4) - self.logMsg("Set jump limit to recover: %s" % jump, 1) + log("Set jump limit to recover: %s" % jump, 2) retry = 0 while utils.window('emby_online') != "true": # Wait server to come back online if retry == 3: - self.logMsg("Unable to reconnect to server. Abort process.", 1) + log("Unable to reconnect to server. Abort process.", 1) return retry += 1 @@ -289,7 +291,7 @@ class Read_EmbyServer(): increment = 10 jump += increment - self.logMsg("Increase jump limit to: %s" % jump, 1) + log("Increase jump limit to: %s" % jump, 1) return items def getViews(self, type, root=False): diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index e1d09160..ee7126b9 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -73,42 +73,46 @@ class UserClient(threading.Thread): def getUserId(self): + log = self.logMsg + window = utils.window + settings = utils.settings + username = self.getUsername() - w_userId = utils.window('emby_userId%s' % username) - s_userId = utils.settings('userId%s' % username) + w_userId = window('emby_currUser') + s_userId = settings('userId%s' % username) # Verify the window property if w_userId: if not s_userId: # Save access token if it's missing from settings - utils.settings('userId%s' % username, value=w_userId) - self.logMsg( - "Returning userId from WINDOW for username: %s UserId: %s" + settings('userId%s' % username, value=w_userId) + log("Returning userId from WINDOW for username: %s UserId: %s" % (username, w_userId), 2) return w_userId # Verify the settings elif s_userId: - self.logMsg( - "Returning userId from SETTINGS for username: %s userId: %s" + log("Returning userId from SETTINGS for username: %s userId: %s" % (username, s_userId), 2) return s_userId # No userId found else: - self.logMsg("No userId saved for username: %s." % username, 1) + log("No userId saved for username: %s." % username, 1) def getServer(self, prefix=True): - alternate = utils.settings('altip') == "true" + settings = utils.settings + + alternate = settings('altip') == "true" if alternate: # Alternate host - HTTPS = utils.settings('secondhttps') == "true" - host = utils.settings('secondipaddress') - port = utils.settings('secondport') + HTTPS = settings('secondhttps') == "true" + host = settings('secondipaddress') + port = settings('secondport') else: # Original host - HTTPS = utils.settings('https') == "true" - host = utils.settings('ipaddress') - port = utils.settings('port') + HTTPS = settings('https') == "true" + host = settings('ipaddress') + port = settings('port') server = host + ":" + port @@ -134,33 +138,40 @@ class UserClient(threading.Thread): def getToken(self): + log = self.logMsg + window = utils.window + settings = utils.settings + username = self.getUsername() - w_token = utils.window('emby_accessToken%s' % username) - s_token = utils.settings('accessToken') + userId = self.getUserId() + w_token = window('emby_accessToken%s' % userId) + s_token = settings('accessToken') # Verify the window property if w_token: if not s_token: # Save access token if it's missing from settings - utils.settings('accessToken', value=w_token) - self.logMsg("Returning accessToken from WINDOW for username: %s " - "accessToken: xxxxx" % username, 2) + settings('accessToken', value=w_token) + log("Returning accessToken from WINDOW for username: %s accessToken: %s" + % (username, w_token), 2) return w_token # Verify the settings elif s_token: - self.logMsg("Returning accessToken from SETTINGS for username: %s " - "accessToken: xxxxx" % username, 2) - utils.window('emby_accessToken%s' % username, value=s_token) + log("Returning accessToken from SETTINGS for username: %s accessToken: %s" + % (username, s_token), 2) + window('emby_accessToken%s' % username, value=s_token) return s_token else: - self.logMsg("No token found.", 1) + log("No token found.", 1) return "" def getSSLverify(self): # Verify host certificate - s_sslverify = utils.settings('sslverify') - if utils.settings('altip') == "true": - s_sslverify = utils.settings('secondsslverify') + settings = utils.settings + + s_sslverify = settings('sslverify') + if settings('altip') == "true": + s_sslverify = settings('secondsslverify') if s_sslverify == "true": return True @@ -169,9 +180,11 @@ class UserClient(threading.Thread): def getSSL(self): # Client side certificate - s_cert = utils.settings('sslcert') - if utils.settings('altip') == "true": - s_cert = utils.settings('secondsslcert') + settings = utils.settings + + s_cert = settings('sslcert') + if settings('altip') == "true": + s_cert = settings('secondsslcert') if s_cert == "None": return None @@ -204,10 +217,33 @@ class UserClient(threading.Thread): return False def hasAccess(self): - return True + return True + # hasAccess is verified in service.py + log = self.logMsg + window = utils.window + + url = "{server}/emby/Users?format=json" + result = self.doUtils.downloadUrl(url) + + if result == False: + # Access is restricted, set in downloadutils.py via exception + log("Access is restricted.", 1) + self.HasAccess = False + + elif window('emby_online') != "true": + # Server connection failed + pass + + elif window('emby_serverStatus') == "restricted": + log("Access is granted.", 1) + self.HasAccess = True + window('emby_serverStatus', clear=True) + xbmcgui.Dialog().notification("Emby for Kodi", utils.language(33007)) def loadCurrUser(self, authenticated=False): + window = utils.window + doUtils = self.doUtils username = self.getUsername() userId = self.getUserId() @@ -259,6 +295,13 @@ class UserClient(threading.Thread): return True def authenticate(self): + + log = self.logMsg + lang = utils.language + window = utils.window + settings = utils.settings + dialog = xbmcgui.Dialog() + # Get /profile/addon_data plx = PlexAPI.PlexAPI() addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8') @@ -270,7 +313,7 @@ class UserClient(threading.Thread): # If there's no settings.xml if not hasSettings: - self.logMsg("No settings.xml found.", 0) + log("No settings.xml found.", 1) self.auth = False return # If no user information @@ -332,22 +375,20 @@ class UserClient(threading.Thread): self.logMsg("Error: user authentication failed.", -1) utils.settings('accessToken', value="") utils.settings('userId%s' % username, value="") + log("Too many retries. " + "You can retry by resetting attempts in the addon settings.", 1) + window('emby_serverStatus', value="Stop") + dialog.ok(lang(33001), lang(33010)) # Give 3 attempts at entering password / selecting user if self.retry == 3: - utils.window('emby_serverStatus', value="Stop") - xbmcgui.Dialog().ok(heading=self.addonName, - line1="Failed to authenticate too many" - "times.", - line2="You can retry by resetting attempts" - " in the addon settings.") + self.retry += 1 self.auth = False def resetClient(self): - self.logMsg("Reset UserClient authentication.", 1) - username = self.getUsername() + log = self.logMsg utils.settings('accessToken', value="") utils.window('emby_accessToken%s' % username, clear=True) @@ -359,7 +400,7 @@ class UserClient(threading.Thread): def run(self): - self.logMsg("----===## Starting UserClient ##===----", 0) + log("----===## Starting UserClient ##===----", 0) while not self.threadStopped(): while self.threadSuspended(): @@ -367,7 +408,7 @@ class UserClient(threading.Thread): break xbmc.sleep(3000) - status = utils.window('emby_serverStatus') + status = window('emby_serverStatus') if status: # Verify the connection status to server if status == "restricted": @@ -376,12 +417,12 @@ class UserClient(threading.Thread): elif status == "401": # Unauthorized access, revoke token - utils.window('emby_serverStatus', value="Auth") + window('emby_serverStatus', value="Auth") self.resetClient() if self.auth and (self.currUser is None): # Try to authenticate user - status = utils.window('emby_serverStatus') + status = window('emby_serverStatus') if not status or status == "Auth": # Set auth flag because we no longer need # to authenticate the user @@ -393,13 +434,13 @@ class UserClient(threading.Thread): # If authenticate failed. server = self.getServer() username = self.getUsername() - status = utils.window('emby_serverStatus') + status = window('emby_serverStatus') # The status Stop is for when user cancelled password dialog. if server and username and status != "Stop": # Only if there's information found to login - self.logMsg("Server found: %s" % server, 2) - self.logMsg("Username found: %s" % username, 2) + log("Server found: %s" % server, 2) + log("Username found: %s" % username, 2) self.auth = True self.doUtils.stopSession() diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py index c2b92e8d..e15109da 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -17,7 +17,7 @@ import utils class VideoNodes(object): def __init__(self): - self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2]) + self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) def commonRoot(self, order, label, tagname, roottype=1): @@ -63,7 +63,7 @@ class VideoNodes(object): xbmcvfs.exists(path) # Create the node directory - if not xbmcvfs.exists(nodepath) and not mediatype=="photos": + if not xbmcvfs.exists(nodepath) and not mediatype == "photos": # We need to copy over the default items xbmcvfs.mkdirs(nodepath) else: @@ -90,7 +90,7 @@ class VideoNodes(object): window('Emby.nodes.%s.index' % indexnumber, value=path) # Root - if not mediatype=="photos": + if not mediatype == "photos": root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0) try: utils.indent(root) @@ -169,7 +169,7 @@ class VideoNodes(object): nodeXML = "%s%s_%s.xml" % (nodepath, cleantagname, nodetype) # Get label stringid = nodes[node] - if node != '1': + if node != "1": label = utils.language(stringid) if not label: label = xbmc.getLocalizedString(stringid) diff --git a/resources/settings.xml b/resources/settings.xml index 4fe24e11..177dadf2 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -56,7 +56,6 @@ - @@ -72,8 +71,9 @@ + - + diff --git a/service.py b/service.py index 88dc4e2c..30150e1d 100644 --- a/service.py +++ b/service.py @@ -180,13 +180,13 @@ class Service(): else: add = "" xbmcgui.Dialog().notification( - heading=self.addonName, - message="%s %s%s!" - % (lang(33000), user.currUser, add), - icon="special://home/addons/plugin.video." - "plexkodiconnect/icon.png", - time=2000, - sound=False) + heading=self.addonName, + message=("%s %s%s!" + % (lang(33000), user.currUser.decode('utf-8'), + add.decode('utf-8'))), + icon="special://home/addons/plugin.video.emby/icon.png", + time=2000, + sound=False) # Start monitoring kodi events if not self.kodimonitor_running: