From 54e998d579f2c1ffcb3d11579a46d72bcd516855 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 12 Feb 2016 16:30:20 -0600 Subject: [PATCH 01/22] Update skip delete message Github wiki updated accordingly --- resources/language/English/strings.xml | 7 +++++-- resources/lib/kodimonitor.py | 9 ++++++--- resources/lib/player.py | 2 +- resources/settings.xml | 6 ++++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 6effb2c2..21b92ce4 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -279,14 +279,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 Jump back on resume (in seconds) Force transcode H265 Music metadata options (not compatible with direct stream) @@ -300,6 +300,9 @@ Enable new content notification Duration of the video library pop up (in seconds) Duration of the music library pop up (in seconds) + For the context menu + After playback (Caution!) + Server messages Welcome diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index ac781e5a..76420092 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -179,17 +179,20 @@ class KodiMonitor(xbmc.Monitor): except TypeError: self.logMsg("Could not find itemid in emby database.", 1) else: - if utils.settings('skipConfirmDelete') != "true": - resp = xbmcgui.Dialog().yesno( + if utils.settings('skipContextMenu') != "true": + self.logMsg("BAM!") + '''resp = xbmcgui.Dialog().yesno( heading="Confirm delete", line1="Delete file on Emby Server?") if not resp: 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") + doUtils.downloadUrl(url, type="DELETE")''' + self.logMsg("BAm!") finally: embycursor.close() diff --git a/resources/lib/player.py b/resources/lib/player.py index 354b8984..ecc6ac64 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -482,7 +482,7 @@ class Player(xbmc.Player): offerDelete = False if percentComplete >= markPlayedAt and offerDelete: - if utils.settings('skipConfirmDelete') != "true": + if utils.settings('skipPlayback') != "true": resp = xbmcgui.Dialog().yesno( heading="Confirm delete", line1="Delete file on Emby Server?") diff --git a/resources/settings.xml b/resources/settings.xml index 3889f837..40ce0a7d 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -49,7 +49,6 @@ - @@ -64,7 +63,10 @@ - + + + + From d3f2969e805b66eabb7197bbd55f49ccbdfc886c Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 12 Feb 2016 16:31:01 -0600 Subject: [PATCH 02/22] Fix typo --- resources/lib/kodimonitor.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index 76420092..3e54b77d 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -180,8 +180,7 @@ class KodiMonitor(xbmc.Monitor): self.logMsg("Could not find itemid in emby database.", 1) else: if utils.settings('skipContextMenu') != "true": - self.logMsg("BAM!") - '''resp = xbmcgui.Dialog().yesno( + resp = xbmcgui.Dialog().yesno( heading="Confirm delete", line1="Delete file on Emby Server?") if not resp: @@ -191,8 +190,7 @@ class KodiMonitor(xbmc.Monitor): url = "{server}/emby/Items/%s?format=json" % itemid self.logMsg("Deleting request: %s" % itemid) - doUtils.downloadUrl(url, type="DELETE")''' - self.logMsg("BAm!") + doUtils.downloadUrl(url, type="DELETE") finally: embycursor.close() From 26836fb53656dc2f543c0c4ba5b2be7b4c62eb57 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 12 Feb 2016 16:49:21 -0600 Subject: [PATCH 03/22] Remove skip deletion after playback I think it must be a mistake on my part. Checking history, it looks like a copy paste during the refactor. --- resources/language/English/strings.xml | 6 ++---- resources/lib/player.py | 13 ++++++------- resources/settings.xml | 6 ++---- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 21b92ce4..cabd3e9b 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -286,7 +286,7 @@ Network credentials Enable Emby cinema mode Ask to play trailers - Skip Emby delete confirmation + Skip Emby delete confirmation for the context menu Jump back on resume (in seconds) Force transcode H265 Music metadata options (not compatible with direct stream) @@ -300,9 +300,7 @@ Enable new content notification Duration of the video library pop up (in seconds) Duration of the music library pop up (in seconds) - For the context menu - After playback (Caution!) - Server messages + Server messages Welcome diff --git a/resources/lib/player.py b/resources/lib/player.py index ecc6ac64..ed3fb414 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -482,13 +482,12 @@ class Player(xbmc.Player): offerDelete = False if percentComplete >= markPlayedAt and offerDelete: - if utils.settings('skipPlayback') != "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( + heading="Confirm delete", + line1="Delete file on Emby Server?") + if not resp: + self.logMsg("User skipped deletion.", 1) + continue url = "{server}/emby/Items/%s?format=json" % itemid self.logMsg("Deleting request: %s" % itemid) diff --git a/resources/settings.xml b/resources/settings.xml index 40ce0a7d..7f7482f5 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -62,11 +62,9 @@ + - - - - + From 03d016797c2fd6fb60b4e09df5f47f2329f501c4 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 12 Feb 2016 17:23:07 -0600 Subject: [PATCH 04/22] Allow popup time setting 0 to disable notification For the newly added content notification --- resources/lib/itemtypes.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index ae121021..aacac99b 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -211,12 +211,15 @@ class Items(object): return (True, update_videolibrary) def contentPop(self, name, time=5000): - xbmcgui.Dialog().notification( - heading="Emby for Kodi", - message="Added: %s" % name, - icon="special://home/addons/plugin.video.emby/icon.png", - time=time, - sound=False) + + if time: + # It's possible for the time to be 0. It should be considered disabled in this case. + xbmcgui.Dialog().notification( + heading="Emby for Kodi", + message="Added: %s" % name, + icon="special://home/addons/plugin.video.emby/icon.png", + time=time, + sound=False) class Movies(Items): From e1d5bff89ea02d85a5d9f38eac9609f5825d4933 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 12 Feb 2016 17:48:09 -0600 Subject: [PATCH 05/22] Version bump 2.1.1 --- addon.xml | 2 +- changelog.txt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index cdece919..d19c315f 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index e1149bd3..79ca0f25 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +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 From 1bcba15cd983cc167ee818e416c22c29bcc5dce6 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 13 Feb 2016 22:42:16 -0600 Subject: [PATCH 06/22] Repair entries For the video library. This is for the scenario where Kodi erases the entry, but it still exists in the emby database. --- resources/lib/itemtypes.py | 58 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index aacac99b..98c9ac5e 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -283,6 +283,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) @@ -665,6 +676,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) @@ -991,6 +1013,7 @@ class TVShows(Items): # 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: @@ -1001,6 +1024,22 @@ class TVShows(Items): except TypeError: update_item = False self.logMsg("showid: %s not found." % itemid, 2) + 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 @@ -1099,8 +1138,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( @@ -1154,6 +1191,12 @@ class TVShows(Items): # Process artwork artwork.addArtwork(artwork.getAllArtwork(item), 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): kodicursor = self.kodicursor @@ -1214,6 +1257,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() From ffeec4bc5affedc513cdf8aacd2f5850041b40bb Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 13 Feb 2016 23:04:56 -0600 Subject: [PATCH 07/22] Version bump 2.1.2 --- addon.xml | 2 +- changelog.txt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index d19c315f..75e7e90f 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index 79ca0f25..bc793c5a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,6 @@ +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. From 6c801c326c265695fcc162d542af6df3290482d3 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 13 Feb 2016 23:29:01 -0600 Subject: [PATCH 08/22] Fix typo --- resources/lib/kodimonitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index 3e54b77d..b94befe5 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -82,7 +82,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")): From 8164c49bdf48ce31b5b1ebe2d2ed4af856d33468 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 14 Feb 2016 00:26:42 -0600 Subject: [PATCH 09/22] Fix live tv It is transcoding, mark as such to delete ffmpeg process when playback ends. --- resources/lib/playutils.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index 7978b567..5617a6e9 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -36,14 +36,15 @@ class PlayUtils(): item = self.item playurl = None - if item.get('Type') in ["Recording","TvChannel"] and item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http": - #Is this the right way to play a Live TV or recordings ? + if (item.get('Type') in ("Recording", "TvChannel") and + item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http"): + # Play LiveTV or recordings self.logMsg("File protocol is http (livetv).", 1) playurl = "%s/emby/Videos/%s/live.m3u8?static=true" % (self.server, item['Id']) - utils.window('emby_%s.playmethod' % playurl, value="DirectPlay") + utils.window('emby_%s.playmethod' % playurl, value="Transcode") elif item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http": - # Only play as http + # Only play as http, used for channels, or online hosting of content self.logMsg("File protocol is http.", 1) playurl = self.httpPlay() utils.window('emby_%s.playmethod' % playurl, value="DirectStream") From 077dbc120caf3e20aa96b3ac3b47501f3f360332 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 14 Feb 2016 00:28:22 -0600 Subject: [PATCH 10/22] Version bump 2.1.3 --- addon.xml | 2 +- changelog.txt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 75e7e90f..07197a35 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index bc793c5a..ae51d757 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,6 @@ +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. From 48332fdbb7de24e7536b436b6f9d81108bb5bd86 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Tue, 16 Feb 2016 01:05:58 -0600 Subject: [PATCH 11/22] Update string for skip delete Added use at your own risk and added other various strings --- resources/language/English/strings.xml | 33 +++++++++++++++++++++----- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index cabd3e9b..15ecc74e 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 @@ -286,7 +282,7 @@ Network credentials Enable Emby cinema mode Ask to play trailers - Skip Emby delete confirmation for the context menu + 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) @@ -310,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 on 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: From 228501d99c493ec65c7394c907fdbe9383840318 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Tue, 16 Feb 2016 22:13:10 -0600 Subject: [PATCH 12/22] Move removal to context menu There was a typo in the delete as well. Remove videolibrary.on.remove due to potential issue if clean database runs and user is using plugin paths. --- contextmenu.py | 25 +++++++++++++++++++++++-- resources/lib/kodimonitor.py | 9 ++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/contextmenu.py b/contextmenu.py index ab557b9f..febae78d 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.", 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/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index b94befe5..696827cc 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -162,8 +162,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): @@ -192,7 +195,7 @@ class KodiMonitor(xbmc.Monitor): self.logMsg("Deleting request: %s" % itemid) doUtils.downloadUrl(url, type="DELETE") finally: - embycursor.close() + embycursor.close()''' elif method == "System.OnWake": From 13667f6a849e428f36ed3f79f396d0f795c1d35f Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Tue, 16 Feb 2016 22:22:14 -0600 Subject: [PATCH 13/22] Add more logging --- contextmenu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contextmenu.py b/contextmenu.py index febae78d..66256dca 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -134,7 +134,7 @@ if __name__ == '__main__': line1=("Delete file from Emby Server? This will " "also delete the file(s) from disk!")) if not resp: - logMsg("User skipped deletion.", 1) + logMsg("User skipped deletion for: %s." % embyid, 1) delete = False if delete: From 6a1814847a76f27333cca82513a65334434186fc Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Wed, 17 Feb 2016 00:06:06 -0600 Subject: [PATCH 14/22] Version bump 2.1.4 Final beta before stable release --- addon.xml | 2 +- changelog.txt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 07197a35..159b00b0 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index ae51d757..7f19ba1a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,6 @@ +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. From 0ba1ac410459f17b3bd64d15e76e02004b7c49f1 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Wed, 17 Feb 2016 01:11:51 -0600 Subject: [PATCH 15/22] Stable - Version bump 2.2.0 --- addon.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 159b00b0..0b1f3403 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ From 52a5d352957d62e4f066de48faa4a184627adc8c Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Wed, 17 Feb 2016 02:13:37 -0600 Subject: [PATCH 16/22] Update for strings --- resources/lib/librarysync.py | 287 ++++++++++++++++-------------- resources/lib/playbackutils.py | 79 ++++---- resources/lib/player.py | 116 ++++++------ resources/lib/playlist.py | 23 ++- resources/lib/playutils.py | 117 ++++++------ resources/lib/read_embyserver.py | 22 ++- resources/lib/userclient.py | 204 +++++++++++---------- resources/lib/videonodes.py | 10 +- resources/lib/websocket_client.py | 2 +- service.py | 6 +- 10 files changed, 477 insertions(+), 389 deletions(-) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index ecbb63cf..261e6c48 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -47,7 +47,7 @@ class LibrarySync(threading.Thread): self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() - self.doUtils = downloadutils.DownloadUtils() + self.doUtils = downloadutils.DownloadUtils().downloadUrl self.user = userclient.UserClient() self.emby = embyserver.Read_EmbyServer() self.vnodes = videonodes.VideoNodes() @@ -72,17 +72,19 @@ class LibrarySync(threading.Thread): return dialog def startSync(self): + + settings = utils.settings # Run at start up - optional to use the server plugin - if utils.settings('SyncInstallRunDone') == "true": + if settings('SyncInstallRunDone') == "true": # Validate views self.refreshViews() completed = False # Verify if server plugin is installed. - if utils.settings('serverSync') == "true": + if settings('serverSync') == "true": # Try to use fast start up url = "{server}/emby/Plugins?format=json" - result = self.doUtils.downloadUrl(url) + result = self.doUtils(url) for plugin in result: if plugin['Name'] == "Emby.Kodi Sync Queue": @@ -100,30 +102,43 @@ class LibrarySync(threading.Thread): def fastSync(self): + log = self.logMsg + + doUtils = self.doUtils + lastSync = utils.settings('LastIncrementalSync') if not lastSync: lastSync = "2010-01-01T00:00:00Z" lastSyncTime = utils.convertdate(lastSync) - self.logMsg("Last sync run: %s" % lastSyncTime, 1) + log("Last sync run: %s" % lastSyncTime, 1) # get server RetentionDateTime url = "{server}/Emby.Kodi.SyncQueue/GetServerDateTime?format=json" - result = self.doUtils.downloadUrl(url) + result = doUtils(url) retention_time = "2010-01-01T00:00:00Z" if result and result.get('RetentionDateTime'): retention_time = result['RetentionDateTime'] + + #Try/except equivalent + ''' + try: + retention_time = result['RetentionDateTime'] + except (TypeError, KeyError): + retention_time = "2010-01-01T00:00:00Z" + ''' + retention_time = utils.convertdate(retention_time) - self.logMsg("RetentionDateTime: %s" % retention_time, 1) + log("RetentionDateTime: %s" % retention_time, 1) # if last sync before retention time do a full sync if retention_time > lastSyncTime: - self.logMsg("Fast sync server retention insurficient, fall back to full sync", 1) + log("Fast sync server retention insufficient, fall back to full sync", 1) return False url = "{server}/emby/Emby.Kodi.SyncQueue/{UserId}/GetItems?format=json" params = {'LastUpdateDT': lastSync} - result = self.doUtils.downloadUrl(url, parameters=params) + result = doUtils(url, parameters=params) try: processlist = { @@ -135,36 +150,38 @@ class LibrarySync(threading.Thread): } except (KeyError, TypeError): - self.logMsg("Failed to retrieve latest updates using fast sync.", 1) + log("Failed to retrieve latest updates using fast sync.", 1) return False else: - self.logMsg("Fast sync changes: %s" % result, 1) + log("Fast sync changes: %s" % result, 1) for action in processlist: self.triage_items(action, processlist[action]) return True def saveLastSync(self): + + log = self.logMsg # Save last sync time overlap = 2 url = "{server}/Emby.Kodi.SyncQueue/GetServerDateTime?format=json" - result = self.doUtils.downloadUrl(url) + result = self.doUtils(url) try: # datetime fails when used more than once, TypeError server_time = result['ServerDateTime'] server_time = utils.convertdate(server_time) except Exception as e: # If the server plugin is not installed or an error happened. - self.logMsg("An exception occurred: %s" % e, 1) + log("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" % (overlap, lastSync), 1) + log("New sync time: client time -%s min: %s" % (overlap, lastSync), 1) else: lastSync = (server_time - timedelta(minutes=overlap)).strftime('%Y-%m-%dT%H:%M:%SZ') - self.logMsg("New sync time: server time -%s min: %s" % (overlap, lastSync), 1) + log("New sync time: server time -%s min: %s" % (overlap, lastSync), 1) finally: utils.settings('LastIncrementalSync', value=lastSync) @@ -179,32 +196,39 @@ class LibrarySync(threading.Thread): return False def dbCommit(self, connection): + + log = self.logMsg + window = utils.window # Central commit, verifies if Kodi database update is running - kodidb_scan = utils.window('emby_kodiScan') == "true" + kodidb_scan = window('emby_kodiScan') == "true" while kodidb_scan: - self.logMsg("Kodi scan is running. Waiting...", 1) - kodidb_scan = utils.window('emby_kodiScan') == "true" + log("Kodi scan is running. Waiting...", 1) + kodidb_scan = window('emby_kodiScan') == "true" if self.shouldStop(): - self.logMsg("Commit unsuccessful. Sync terminated.", 1) + log("Commit unsuccessful. Sync terminated.", 1) break if self.monitor.waitForAbort(1): # Abort was requested while waiting. We should exit - self.logMsg("Commit unsuccessful.", 1) + log("Commit unsuccessful.", 1) break else: connection.commit() - self.logMsg("Commit successful.", 1) + log("Commit successful.", 1) def fullSync(self, manualrun=False, repair=False): + + log = self.logMsg + window = utils.window + settings = utils.settings # 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") + window('emby_dbScan', value="true") # Add sources utils.sourcesXML() @@ -232,7 +256,7 @@ class LibrarySync(threading.Thread): message = "Repair sync" else: message = "Initial sync" - utils.window('emby_initialScan', value="true") + window('emby_initialScan', value="true") pDialog = self.progressDialog("%s" % message, forced=True) starttotal = datetime.now() @@ -253,7 +277,7 @@ class LibrarySync(threading.Thread): completed = process[itemtype](embycursor, kodicursor, pDialog) if not completed: - utils.window('emby_dbScan', clear=True) + window('emby_dbScan', clear=True) if pDialog: pDialog.close() @@ -264,8 +288,7 @@ class LibrarySync(threading.Thread): self.dbCommit(kodiconn) embyconn.commit() elapsedTime = datetime.now() - startTime - self.logMsg( - "SyncDatabase (finished %s in: %s)" + log("SyncDatabase (finished %s in: %s)" % (itemtype, str(elapsedTime).split('.')[0]), 1) else: # Close the Kodi cursor @@ -281,7 +304,7 @@ class LibrarySync(threading.Thread): completed = self.music(embycursor, musiccursor, pDialog) if not completed: - utils.window('emby_dbScan', clear=True) + window('emby_dbScan', clear=True) if pDialog: pDialog.close() @@ -292,8 +315,7 @@ class LibrarySync(threading.Thread): musicconn.commit() embyconn.commit() elapsedTime = datetime.now() - startTime - self.logMsg( - "SyncDatabase (finished music in: %s)" + log("SyncDatabase (finished music in: %s)" % (str(elapsedTime).split('.')[0]), 1) musiccursor.close() @@ -302,18 +324,18 @@ class LibrarySync(threading.Thread): embycursor.close() - utils.settings('SyncInstallRunDone', value="true") - utils.settings("dbCreatedWithVersion", self.clientInfo.getVersion()) + settings('SyncInstallRunDone', value="true") + settings("dbCreatedWithVersion", self.clientInfo.getVersion()) self.saveLastSync() xbmc.executebuiltin('UpdateLibrary(video)') elapsedtotal = datetime.now() - starttotal - utils.window('emby_dbScan', clear=True) - utils.window('emby_initialScan', clear=True) + window('emby_dbScan', clear=True) + window('emby_initialScan', clear=True) xbmcgui.Dialog().notification( heading="Emby for Kodi", - message="%s completed in: %s" % - (message, str(elapsedtotal).split('.')[0]), + message="%s %s %s" % + (message, utils.language(33025), str(elapsedtotal).split('.')[0]), icon="special://home/addons/plugin.video.emby/icon.png", sound=False) return True @@ -336,6 +358,8 @@ class LibrarySync(threading.Thread): embycursor.close() def maintainViews(self, embycursor, kodicursor): + + log = self.logMsg # Compare the views to emby emby_db = embydb.Embydb_Functions(embycursor) kodi_db = kodidb.Kodidb_Functions(kodicursor) @@ -344,14 +368,14 @@ class LibrarySync(threading.Thread): # Get views url = "{server}/emby/Users/{UserId}/Views?format=json" - result = doUtils.downloadUrl(url) + result = doUtils(url) grouped_views = result['Items'] try: groupedFolders = self.user.userSettings['Configuration']['GroupedFolders'] except TypeError: url = "{server}/emby/Users/{UserId}?format=json" - result = doUtils.downloadUrl(url) + result = doUtils(url) groupedFolders = result['Configuration']['GroupedFolders'] # total nodes for window properties @@ -402,7 +426,7 @@ class LibrarySync(threading.Thread): emby_db.addView(folderid, foldername, viewtype, tagid) else: - self.logMsg(' '.join(( + log(' '.join(( "Found viewid: %s" % folderid, "viewname: %s" % current_viewname, @@ -411,7 +435,7 @@ class LibrarySync(threading.Thread): # View was modified, update with latest info if current_viewname != foldername: - self.logMsg("viewid: %s new viewname: %s" % (folderid, foldername), 1) + log("viewid: %s new viewname: %s" % (folderid, foldername), 1) tagid = kodi_db.createTag(foldername) # Update view with new info @@ -466,6 +490,9 @@ class LibrarySync(threading.Thread): utils.window('Emby.nodes.total', str(totalnodes)) def movies(self, embycursor, kodicursor, pdialog): + + log = self.logMsg + lang = utils.language # Get movies from emby emby = self.emby emby_db = embydb.Embydb_Functions(embycursor) @@ -473,7 +500,7 @@ class LibrarySync(threading.Thread): views = emby_db.getView_byType('movies') views += emby_db.getView_byType('mixed') - self.logMsg("Media folders: %s" % views, 1) + log("Media folders: %s" % views, 1) ##### PROCESS MOVIES ##### for view in views: @@ -488,7 +515,7 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update( heading="Emby for Kodi", - message="Gathering movies from view: %s..." % viewName) + message="%s %s..." % (lang(33017), viewName)) # Initial or repair sync all_embymovies = emby.getMovies(viewId, dialog=pdialog) @@ -511,12 +538,12 @@ class LibrarySync(threading.Thread): count += 1 movies.add_update(embymovie, viewName, viewId) else: - self.logMsg("Movies finished.", 2) + log("Movies finished.", 2) ##### PROCESS BOXSETS ##### if pdialog: - pdialog.update(heading="Emby for Kodi", message="Gathering boxsets from server...") + pdialog.update(heading="Emby for Kodi", message=lang(33018)) boxsets = emby.getBoxset(dialog=pdialog) total = boxsets['TotalRecordCount'] @@ -538,18 +565,20 @@ class LibrarySync(threading.Thread): count += 1 movies.add_updateBoxset(boxset) else: - self.logMsg("Boxsets finished.", 2) + log("Boxsets finished.", 2) return True def musicvideos(self, embycursor, kodicursor, pdialog): + + log = self.logMsg # Get musicvideos from emby emby = self.emby emby_db = embydb.Embydb_Functions(embycursor) mvideos = itemtypes.MusicVideos(embycursor, kodicursor) views = emby_db.getView_byType('musicvideos') - self.logMsg("Media folders: %s" % views, 1) + log("Media folders: %s" % views, 1) for view in views: @@ -563,7 +592,7 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update( heading="Emby for Kodi", - message="Gathering musicvideos from view: %s..." % viewName) + message="%s %s..." % (utils.language(33019), viewName)) # Initial or repair sync all_embymvideos = emby.getMusicVideos(viewId, dialog=pdialog) @@ -586,11 +615,13 @@ class LibrarySync(threading.Thread): count += 1 mvideos.add_update(embymvideo, viewName, viewId) else: - self.logMsg("MusicVideos finished.", 2) + log("MusicVideos finished.", 2) return True def tvshows(self, embycursor, kodicursor, pdialog): + + log = self.logMsg # Get shows from emby emby = self.emby emby_db = embydb.Embydb_Functions(embycursor) @@ -598,7 +629,7 @@ class LibrarySync(threading.Thread): views = emby_db.getView_byType('tvshows') views += emby_db.getView_byType('mixed') - self.logMsg("Media folders: %s" % views, 1) + log("Media folders: %s" % views, 1) for view in views: @@ -612,7 +643,7 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update( heading="Emby for Kodi", - message="Gathering tvshows from view: %s..." % viewName) + message="%s %s..." % (utils.language(33020), viewName)) all_embytvshows = emby.getShows(viewId, dialog=pdialog) total = all_embytvshows['TotalRecordCount'] @@ -648,7 +679,7 @@ class LibrarySync(threading.Thread): pdialog.update(percentage, message="%s - %s" % (title, episodetitle)) tvshows.add_updateEpisode(episode) else: - self.logMsg("TVShows finished.", 2) + log("TVShows finished.", 2) return True @@ -665,19 +696,19 @@ class LibrarySync(threading.Thread): 'songs': [emby.getSongs, music.add_updateSong] } types = ['artists', 'albums', 'songs'] - for type in types: + for itemtype in types: if pdialog: pdialog.update( heading="Emby for Kodi", - message="Gathering %s..." % type) + message="%s %s..." % (utils.language(33021), itemtype)) - all_embyitems = process[type][0](dialog=pdialog) + all_embyitems = process[itemtype][0](dialog=pdialog) total = all_embyitems['TotalRecordCount'] embyitems = all_embyitems['Items'] if pdialog: - pdialog.update(heading="Processing %s / %s items" % (type, total)) + pdialog.update(heading="Processing %s / %s items" % (itemtype, total)) count = 0 for embyitem in embyitems: @@ -691,9 +722,9 @@ class LibrarySync(threading.Thread): pdialog.update(percentage, message=title) count += 1 - process[type][1](embyitem) + process[itemtype][1](embyitem) else: - self.logMsg("%s finished." % type, 2) + self.logMsg("%s finished." % itemtype, 2) return True @@ -719,6 +750,8 @@ class LibrarySync(threading.Thread): def incrementalSync(self): + log = self.logMsg + embyconn = utils.kodiSQL('emby') embycursor = embyconn.cursor() kodiconn = utils.kodiSQL('video') @@ -788,7 +821,7 @@ class LibrarySync(threading.Thread): if update_embydb: update_embydb = False - self.logMsg("Updating emby database.", 1) + log("Updating emby database.", 1) embyconn.commit() self.saveLastSync() @@ -797,7 +830,7 @@ class LibrarySync(threading.Thread): self.forceLibraryUpdate = False self.dbCommit(kodiconn) - self.logMsg("Updating video library.", 1) + log("Updating video library.", 1) utils.window('emby_kodiScan', value="true") xbmc.executebuiltin('UpdateLibrary(video)') @@ -839,10 +872,16 @@ class LibrarySync(threading.Thread): def run_internal(self): + log = self.logMsg + lang = utils.language + window = utils.window + settings = utils.settings + dialog = xbmcgui.Dialog() + startupComplete = False monitor = self.monitor - self.logMsg("---===### Starting LibrarySync ###===---", 0) + log("---===### Starting LibrarySync ###===---", 0) while not monitor.abortRequested(): @@ -853,36 +892,24 @@ class LibrarySync(threading.Thread): # Abort was requested while waiting. We should exit break - if (utils.window('emby_dbCheck') != "true" and - utils.settings('SyncInstallRunDone') == "true"): - + if (window('emby_dbCheck') != "true" and settings('SyncInstallRunDone') == "true"): # Verify the validity of the database - currentVersion = utils.settings('dbCreatedWithVersion') - minVersion = utils.window('emby_minDBVersion') + currentVersion = settings('dbCreatedWithVersion') + minVersion = window('emby_minDBVersion') uptoDate = self.compareDBVersion(currentVersion, minVersion) if not uptoDate: - self.logMsg( - "Db version out of date: %s minimum version required: %s" + log("Database version out of date: %s minimum version required: %s" % (currentVersion, minVersion), 0) - resp = xbmcgui.Dialog().yesno( - heading="Db Version", - line1=( - "Detected the database needs to be " - "recreated for this version of Emby for Kodi. " - "Proceed?")) + resp = dialog.yesno("Emby for Kodi", lang(33022)) if not resp: - self.logMsg("Db version out of date! USER IGNORED!", 0) - xbmcgui.Dialog().ok( - heading="Emby for Kodi", - line1=( - "Emby for Kodi may not work correctly " - "until the database is reset.")) + log("Database version is out of date! USER IGNORED!", 0) + dialog.ok("Emby for Kodi", lang(33023)) else: utils.reset() - utils.window('emby_dbCheck', value="true") + window('emby_dbCheck', value="true") if not startupComplete: @@ -890,58 +917,52 @@ class LibrarySync(threading.Thread): videoDb = utils.getKodiVideoDBPath() if not xbmcvfs.exists(videoDb): # Database does not exists - self.logMsg( - "The current Kodi version is incompatible " - "with the Emby for Kodi add-on. Please visit " - "https://github.com/MediaBrowser/Emby.Kodi/wiki " - "to know which Kodi versions are supported.", 0) + log( + "The current Kodi version is incompatible " + "with the Emby for Kodi add-on. Please visit " + "https://github.com/MediaBrowser/Emby.Kodi/wiki " + "to know which Kodi versions are supported.", 0) - xbmcgui.Dialog().ok( - heading="Emby Warning", - line1=( - "Cancelling the database syncing process. " - "Current Kodi versoin: %s is unsupported. " - "Please verify your logs for more info." - % xbmc.getInfoLabel('System.BuildVersion'))) + dialog.ok( + heading="Emby for Kodi", + line1=lang(33024)) break # Run start up sync - self.logMsg("Db version: %s" % utils.settings('dbCreatedWithVersion'), 0) - self.logMsg("SyncDatabase (started)", 1) + log("Database version: %s" % settings('dbCreatedWithVersion'), 0) + log("SyncDatabase (started)", 1) startTime = datetime.now() librarySync = self.startSync() elapsedTime = datetime.now() - startTime - self.logMsg( - "SyncDatabase (finished in: %s) %s" + log("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. startupComplete = True # Process updates - if utils.window('emby_dbScan') != "true": + if window('emby_dbScan') != "true": self.incrementalSync() - if (utils.window('emby_onWake') == "true" and - utils.window('emby_online') == "true"): + if window('emby_onWake') == "true" and window('emby_online') == "true": # Kodi is waking up # Set in kodimonitor.py - utils.window('emby_onWake', clear=True) - if utils.window('emby_syncRunning') != "true": - self.logMsg("SyncDatabase onWake (started)", 0) + window('emby_onWake', clear=True) + if window('emby_syncRunning') != "true": + log("SyncDatabase onWake (started)", 0) librarySync = self.startSync() - self.logMsg("SyncDatabase onWake (finished) %s" % librarySync, 0) + log("SyncDatabase onWake (finished) %s" % librarySync, 0) if self.stop_thread: # Set in service.py - self.logMsg("Service terminated thread.", 2) + log("Service terminated thread.", 2) break if monitor.waitForAbort(1): # Abort was requested while waiting. We should exit break - self.logMsg("###===--- LibrarySync Stopped ---===###", 0) + log("###===--- LibrarySync Stopped ---===###", 0) def stopThread(self): self.stop_thread = True @@ -966,6 +987,9 @@ class ManualSync(LibrarySync): def movies(self, embycursor, kodicursor, pdialog): + + log = self.logMsg + lang = utils.language # Get movies from emby emby = self.emby emby_db = embydb.Embydb_Functions(embycursor) @@ -973,7 +997,7 @@ class ManualSync(LibrarySync): views = emby_db.getView_byType('movies') views += emby_db.getView_byType('mixed') - self.logMsg("Media folders: %s" % views, 1) + log("Media folders: %s" % views, 1) # Pull the list of movies and boxsets in Kodi try: @@ -1003,7 +1027,7 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( heading="Emby for Kodi", - message="Comparing movies from view: %s..." % viewName) + message="%s %s..." % (lang(33026), viewName)) all_embymovies = emby.getMovies(viewId, basic=True, dialog=pdialog) for embymovie in all_embymovies['Items']: @@ -1020,7 +1044,7 @@ class ManualSync(LibrarySync): # Only update if movie is not in Kodi or checksum is different updatelist.append(itemid) - self.logMsg("Movies to update for %s: %s" % (viewName, updatelist), 1) + log("Movies to update for %s: %s" % (viewName, updatelist), 1) embymovies = emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1047,9 +1071,7 @@ class ManualSync(LibrarySync): embyboxsets = [] if pdialog: - pdialog.update( - heading="Emby for Kodi", - message="Comparing boxsets...") + pdialog.update("Emby for Kodi", lang(33027)) for boxset in boxsets['Items']: @@ -1066,7 +1088,7 @@ class ManualSync(LibrarySync): updatelist.append(itemid) embyboxsets.append(boxset) - self.logMsg("Boxsets to update: %s" % updatelist, 1) + log("Boxsets to update: %s" % updatelist, 1) total = len(updatelist) if pdialog: @@ -1091,24 +1113,26 @@ class ManualSync(LibrarySync): if kodimovie not in all_embymoviesIds: movies.remove(kodimovie) else: - self.logMsg("Movies compare finished.", 1) + log("Movies compare finished.", 1) for boxset in all_kodisets: if boxset not in all_embyboxsetsIds: movies.remove(boxset) else: - self.logMsg("Boxsets compare finished.", 1) + log("Boxsets compare finished.", 1) return True def musicvideos(self, embycursor, kodicursor, pdialog): + + log = self.logMsg # Get musicvideos from emby emby = self.emby emby_db = embydb.Embydb_Functions(embycursor) mvideos = itemtypes.MusicVideos(embycursor, kodicursor) views = emby_db.getView_byType('musicvideos') - self.logMsg("Media folders: %s" % views, 1) + log("Media folders: %s" % views, 1) # Pull the list of musicvideos in Kodi try: @@ -1131,7 +1155,7 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( heading="Emby for Kodi", - message="Comparing musicvideos from view: %s..." % viewName) + message="%s %s..." % (utils.language(33028), viewName)) all_embymvideos = emby.getMusicVideos(viewId, basic=True, dialog=pdialog) for embymvideo in all_embymvideos['Items']: @@ -1148,7 +1172,7 @@ class ManualSync(LibrarySync): # Only update if musicvideo is not in Kodi or checksum is different updatelist.append(itemid) - self.logMsg("MusicVideos to update for %s: %s" % (viewName, updatelist), 1) + log("MusicVideos to update for %s: %s" % (viewName, updatelist), 1) embymvideos = emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1176,11 +1200,14 @@ class ManualSync(LibrarySync): if kodimvideo not in all_embymvideosIds: mvideos.remove(kodimvideo) else: - self.logMsg("MusicVideos compare finished.", 1) + log("MusicVideos compare finished.", 1) return True def tvshows(self, embycursor, kodicursor, pdialog): + + log = self.logMsg + lang = utils.language # Get shows from emby emby = self.emby emby_db = embydb.Embydb_Functions(embycursor) @@ -1188,7 +1215,7 @@ class ManualSync(LibrarySync): views = emby_db.getView_byType('tvshows') views += emby_db.getView_byType('mixed') - self.logMsg("Media folders: %s" % views, 1) + log("Media folders: %s" % views, 1) # Pull the list of tvshows and episodes in Kodi try: @@ -1218,7 +1245,7 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( heading="Emby for Kodi", - message="Comparing tvshows from view: %s..." % viewName) + message="%s %s..." % (lang(33029), viewName)) all_embytvshows = emby.getShows(viewId, basic=True, dialog=pdialog) for embytvshow in all_embytvshows['Items']: @@ -1235,7 +1262,7 @@ class ManualSync(LibrarySync): # 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) + log("TVShows to update for %s: %s" % (viewName, updatelist), 1) embytvshows = emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1263,7 +1290,7 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( heading="Emby for Kodi", - message="Comparing episodes from view: %s..." % viewName) + message="%s %s..." % (lang(33030), viewName)) all_embyepisodes = emby.getEpisodes(viewId, basic=True, dialog=pdialog) for embyepisode in all_embyepisodes['Items']: @@ -1279,7 +1306,7 @@ class ManualSync(LibrarySync): # 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) + log("Episodes to update for %s: %s" % (viewName, updatelist), 1) embyepisodes = emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1305,17 +1332,19 @@ class ManualSync(LibrarySync): if koditvshow not in all_embytvshowsIds: tvshows.remove(koditvshow) else: - self.logMsg("TVShows compare finished.", 1) + log("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) + log("Episodes compare finished.", 1) return True def music(self, embycursor, kodicursor, pdialog): + + log = self.logMsg # Get music from emby emby = self.emby emby_db = embydb.Embydb_Functions(embycursor) @@ -1354,7 +1383,7 @@ class ManualSync(LibrarySync): if pdialog: pdialog.update( heading="Emby for Kodi", - message="Comparing %s..." % type) + message="%s %s..." % (utils.language(33031), type)) if type != "artists": all_embyitems = process[type][0](basic=True, dialog=pdialog) @@ -1383,7 +1412,7 @@ class ManualSync(LibrarySync): # Only update if songs is not in Kodi or checksum is different updatelist.append(itemid) - self.logMsg("%s to update: %s" % (type, updatelist), 1) + log("%s to update: %s" % (type, updatelist), 1) embyitems = emby.getFullItems(updatelist) total = len(updatelist) del updatelist[:] @@ -1411,18 +1440,18 @@ class ManualSync(LibrarySync): if kodiartist not in all_embyartistsIds and all_kodiartists[kodiartist] is not None: music.remove(kodiartist) else: - self.logMsg("Artist compare finished.", 1) + log("Artist compare finished.", 1) for kodialbum in all_kodialbums: if kodialbum not in all_embyalbumsIds: music.remove(kodialbum) else: - self.logMsg("Albums compare finished.", 1) + log("Albums compare finished.", 1) for kodisong in all_kodisongs: if kodisong not in all_embysongsIds: music.remove(kodisong) else: - self.logMsg("Songs compare finished.", 1) + log("Songs compare finished.", 1) return True \ No newline at end of file diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index 65c1d8b9..93b8273a 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -31,7 +31,7 @@ class PlaybackUtils(): self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() - self.doUtils = downloadutils.DownloadUtils() + self.doUtils = downloadutils.DownloadUtils().downloadUrl self.userid = utils.window('emby_currUser') self.server = utils.window('emby_server%s' % self.userid) @@ -48,7 +48,9 @@ class PlaybackUtils(): def play(self, itemid, dbid=None): - self.logMsg("Play called.", 1) + log = self.logMsg + window = utils.window + settings = utils.settings doUtils = self.doUtils item = self.item @@ -56,6 +58,7 @@ class PlaybackUtils(): listitem = xbmcgui.ListItem() playutils = putils.PlayUtils(item) + log("Play called.", 1) playurl = playutils.getPlayUrl() if not playurl: return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) @@ -74,13 +77,13 @@ class PlaybackUtils(): sizePlaylist = playlist.size() currentPosition = startPos - propertiesPlayback = utils.window('emby_playbackProps') == "true" + propertiesPlayback = window('emby_playbackProps') == "true" introsPlaylist = False dummyPlaylist = False - self.logMsg("Playlist start position: %s" % startPos, 1) - self.logMsg("Playlist plugin position: %s" % currentPosition, 1) - self.logMsg("Playlist size: %s" % sizePlaylist, 1) + log("Playlist start position: %s" % startPos, 2) + log("Playlist plugin position: %s" % currentPosition, 2) + log("Playlist size: %s" % sizePlaylist, 2) ############### RESUME POINT ################ @@ -91,13 +94,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 @@ -108,27 +111,27 @@ class PlaybackUtils(): ############### -- CHECK FOR INTROS ################ - if utils.settings('enableCinema') == "true" and not seektime: + if settings('enableCinema') == "true" and not seektime: # if we have any play them when the movie/show is not being resumed url = "{server}/emby/Users/{UserId}/Items/%s/Intros?format=json" % itemid - intros = doUtils.downloadUrl(url) + intros = doUtils(url) if intros['TotalRecordCount'] != 0: getTrailers = True - if utils.settings('askCinema') == "true": - resp = xbmcgui.Dialog().yesno("Emby Cinema Mode", "Play trailers?") + if settings('askCinema') == "true": + resp = xbmcgui.Dialog().yesno("Emby for Kodi", utils.language(33016)) if not resp: # User selected to not play trailers getTrailers = False - self.logMsg("Skip trailers.", 1) + log("Skip trailers.", 1) if getTrailers: for intro in intros['Items']: # The server randomly returns intros, process them. introListItem = xbmcgui.ListItem() introPlayurl = putils.PlayUtils(intro).getPlayUrl() - self.logMsg("Adding Intro: %s" % introPlayurl, 1) + log("Adding Intro: %s" % introPlayurl, 1) # Set listitem and properties for intros pbutils = PlaybackUtils(intro) @@ -144,7 +147,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, item['Type'].lower()) # Ensure that additional parts are played after the main item @@ -156,12 +159,12 @@ class PlaybackUtils(): # Only add to the playlist after intros have played partcount = item['PartCount'] url = "{server}/emby/Videos/%s/AdditionalParts?format=json" % itemid - parts = doUtils.downloadUrl(url) + parts = doUtils(url) for part in parts['Items']: additionalListItem = xbmcgui.ListItem() additionalPlayurl = putils.PlayUtils(part).getPlayUrl() - self.logMsg("Adding additional part: %s" % partcount, 1) + log("Adding additional part: %s" % partcount, 1) # Set listitem and properties for each additional parts pbutils = PlaybackUtils(part) @@ -175,58 +178,60 @@ 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": + if window('emby_%s.playmethod' % playurl) == "Transcode": 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 setProperties(self, playurl, listitem): + + window = utils.window # Set all properties necessary for plugin path playback item = self.item itemid = item['Id'] itemtype = item['Type'] embyitem = "emby_%s" % playurl - utils.window('%s.runtime' % embyitem, value=str(item.get('RunTimeTicks'))) - utils.window('%s.type' % embyitem, value=itemtype) - utils.window('%s.itemid' % embyitem, value=itemid) + window('%s.runtime' % embyitem, value=str(item.get('RunTimeTicks'))) + window('%s.type' % embyitem, value=itemtype) + window('%s.itemid' % embyitem, value=itemid) if itemtype == "Episode": - utils.window('%s.refreshid' % embyitem, value=item.get('SeriesId')) + window('%s.refreshid' % embyitem, value=item.get('SeriesId')) else: - utils.window('%s.refreshid' % embyitem, value=itemid) + window('%s.refreshid' % embyitem, value=itemid) # Append external subtitles to stream playmethod = utils.window('%s.playmethod' % embyitem) @@ -316,7 +321,7 @@ class PlaybackUtils(): def setListItem(self, listItem): item = self.item - type = item['Type'] + itemtype = item['Type'] API = self.API people = API.getPeople() studios = API.getStudios() @@ -336,7 +341,7 @@ class PlaybackUtils(): 'votes': item.get('VoteCount') } - if "Episode" in type: + if "Episode" in itemtype: # Only for tv shows thumbId = item.get('SeriesId') season = item.get('ParentIndexNumber', -1) diff --git a/resources/lib/player.py b/resources/lib/player.py index ed3fb414..d4b2e624 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -32,7 +32,7 @@ class Player(xbmc.Player): self.clientInfo = clientinfo.ClientInfo() self.addonName = self.clientInfo.getAddonName() - self.doUtils = downloadutils.DownloadUtils() + self.doUtils = downloadutils.DownloadUtils().downloadUrl self.ws = wsc.WebSocket_Client() self.xbmcplayer = xbmc.Player() @@ -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 @@ -76,36 +79,34 @@ class Player(xbmc.Player): self.currentFile = 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: - self.logMsg("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId), 0) + 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") + runtime = window("%s.runtime" % embyitem) + refresh_id = window("%s.refreshid" % embyitem) + playMethod = window("%s.playmethod" % embyitem) + itemType = window("%s.type" % embyitem) + window('emby_skipWatched%s' % itemId, value="true") - - if (utils.window('emby_customPlaylist') == "true" and - utils.window('emby_customPlaylist.seektime')): + customseek = 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)/10000000.0) - utils.window('emby_customPlaylist.seektime', clear=True) + log("Seeking to: %s" % customseek, 1) + xbmcplayer.seekTime(int(customseek)/10000000.0) + window('emby_customPlaylist.seektime', clear=True) seekTime = xbmcplayer.getTime() @@ -144,9 +145,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 = { @@ -187,11 +187,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)): @@ -209,15 +209,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) # Save data map for updates and position calls data = { @@ -234,7 +234,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): @@ -253,7 +253,9 @@ class Player(xbmc.Player): def reportPlayback(self): - self.logMsg("reportPlayback Called", 2) + log = self.logMsg + + log("reportPlayback Called", 2) xbmcplayer = self.xbmcplayer # Get current file @@ -281,7 +283,7 @@ class Player(xbmc.Player): "properties": ["volume", "muted"] } - } + } result = xbmc.executeJSONRPC(json.dumps(volume_query)) result = json.loads(result) result = result.get('result') @@ -352,7 +354,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)): @@ -372,7 +374,7 @@ class Player(xbmc.Player): # Report progress via websocketclient postdata = json.dumps(postdata) - self.logMsg("Report: %s" % postdata, 2) + log("Report: %s" % postdata, 2) self.ws.sendProgressUpdate(postdata) def onPlayBackPaused( self ): @@ -407,12 +409,15 @@ 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) + logMsg("ONPLAYBACK_STOPPED", 2) + window('emby_customPlaylist', clear=True) + window('emby_customPlaylist.seektime', clear=True) + window('emby_playbackProps', clear=True) + logMsg("Clear playlist properties.", 1) self.stopAll() def onPlayBackEnded( self ): @@ -423,20 +428,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'] @@ -453,9 +462,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 @@ -464,34 +472,32 @@ 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) deviceId = self.clientInfo.getDeviceId() url = "{server}/emby/Videos/ActiveEncodings?DeviceId=%s" % deviceId - doUtils.downloadUrl(url, type="DELETE") + doUtils(url, type="DELETE") # 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 if percentComplete >= markPlayedAt and offerDelete: - resp = xbmcgui.Dialog().yesno( - heading="Confirm delete", - line1="Delete file on Emby Server?") + resp = xbmcgui.Dialog().yesno(lang(30091), lang(33015)) if not resp: - self.logMsg("User skipped deletion.", 1) + 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") self.played_info.clear() @@ -510,4 +516,4 @@ class Player(xbmc.Player): 'MediaSourceId': itemId, 'PositionTicks': positionTicks } - self.doUtils.downloadUrl(url, postBody=postdata, type="POST") \ No newline at end of file + self.doUtils(url, postBody=postdata, type="POST") \ No newline at end of file diff --git a/resources/lib/playlist.py b/resources/lib/playlist.py index c265cb79..383d34a8 100644 --- a/resources/lib/playlist.py +++ b/resources/lib/playlist.py @@ -39,6 +39,9 @@ class Playlist(): def playAll(self, itemids, startat): + log = self.logMsg + window = utils.window + embyconn = utils.kodiSQL('emby') embycursor = embyconn.cursor() emby_db = embydb.Embydb_Functions(embycursor) @@ -47,15 +50,15 @@ class Playlist(): 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)) for itemid in itemids: embydb_item = emby_db.getItem_byId(itemid) @@ -64,14 +67,14 @@ class Playlist(): mediatype = embydb_item[4] except TypeError: # Item is not found in our database, add item manually - self.logMsg("Item was not found in the database, manually adding item.", 1) + log("Item was not found in the database, manually adding item.", 1) item = self.emby.getItem(itemid) self.addtoPlaylist_xbmc(playlist, item) else: # Add to playlist self.addtoPlaylist(dbid, mediatype) - self.logMsg("Adding %s to playlist." % itemid, 1) + log("Adding %s to playlist." % itemid, 1) if not started: started = True @@ -82,12 +85,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) @@ -105,7 +110,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 5617a6e9..37332b16 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -33,43 +33,46 @@ class PlayUtils(): def getPlayUrl(self): + log = self.logMsg + window = utils.window + item = self.item playurl = None if (item.get('Type') in ("Recording", "TvChannel") and item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http"): # Play LiveTV or recordings - self.logMsg("File protocol is http (livetv).", 1) + log("File protocol is http (livetv).", 1) playurl = "%s/emby/Videos/%s/live.m3u8?static=true" % (self.server, item['Id']) - utils.window('emby_%s.playmethod' % playurl, value="Transcode") + window('emby_%s.playmethod' % playurl, value="Transcode") elif item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http": # Only play as http, used for channels, or online hosting of content - self.logMsg("File protocol is http.", 1) + log("File protocol is http.", 1) playurl = self.httpPlay() - utils.window('emby_%s.playmethod' % playurl, value="DirectStream") + window('emby_%s.playmethod' % playurl, value="DirectStream") elif self.isDirectPlay(): - self.logMsg("File is direct playing.", 1) + log("File is direct playing.", 1) playurl = self.directPlay() playurl = playurl.encode('utf-8') # Set playmethod property - utils.window('emby_%s.playmethod' % playurl, value="DirectPlay") + window('emby_%s.playmethod' % playurl, value="DirectPlay") elif self.isDirectStream(): - self.logMsg("File is direct streaming.", 1) + log("File is direct streaming.", 1) playurl = self.directStream() # Set playmethod property - utils.window('emby_%s.playmethod' % playurl, value="DirectStream") + window('emby_%s.playmethod' % playurl, value="DirectStream") elif self.isTranscoding(): - self.logMsg("File is transcoding.", 1) + log("File is transcoding.", 1) playurl = self.transcoding() # Set playmethod property - utils.window('emby_%s.playmethod' % playurl, value="Transcode") + window('emby_%s.playmethod' % playurl, value="Transcode") return playurl @@ -90,16 +93,21 @@ class PlayUtils(): def isDirectPlay(self): + log = self.logMsg + lang = utils.language + settings = utils.settings + dialog = xbmcgui.Dialog() + item = self.item # Requirement: Filesystem, Accessible path - if utils.settings('playFromStream') == "true": + if settings('playFromStream') == "true": # User forcing to play via HTTP - self.logMsg("Can't direct play, play from HTTP enabled.", 1) + log("Can't direct play, play from HTTP enabled.", 1) return False videotrack = item['MediaSources'][0]['Name'] - transcodeH265 = utils.settings('transcodeH265') + transcodeH265 = settings('transcodeH265') if transcodeH265 in ("1", "2", "3") and ("HEVC" in videotrack or "H265" in videotrack): # Avoid H265/HEVC depending on the resolution @@ -110,46 +118,45 @@ class PlayUtils(): '2': 720, '3': 1080 } - self.logMsg("Resolution is: %sP, transcode for resolution: %sP+" - % (resolution, res[transcodeH265]), 1) + log("Resolution is: %sP, transcode for resolution: %sP+" + % (resolution, res[transcodeH265]), 1) if res[transcodeH265] <= resolution: return False canDirectPlay = item['MediaSources'][0]['SupportsDirectPlay'] # Make sure direct play is supported by the server if not canDirectPlay: - self.logMsg("Can't direct play, server doesn't allow/support it.", 1) + log("Can't direct play, server doesn't allow/support it.", 1) return False location = item['LocationType'] if location == "FileSystem": # Verify the path if not self.fileExists(): - self.logMsg("Unable to direct play.") + log("Unable to direct play.") try: - count = int(utils.settings('failCount')) + count = int(settings('failCount')) except ValueError: count = 0 - self.logMsg("Direct play failed: %s times." % count, 1) + log("Direct play failed: %s times." % count, 1) if count < 2: # Let the user know that direct play failed - utils.settings('failCount', value=str(count+1)) - xbmcgui.Dialog().notification( - heading="Emby server", - message="Unable to direct play.", - icon="special://home/addons/plugin.video.emby/icon.png", - sound=False) - elif utils.settings('playFromStream') != "true": + settings('failCount', value=str(count+1)) + dialog.notification( + heading="Emby for Kodi", + message=lang(33011), + icon="special://home/addons/plugin.video.emby/icon.png", + sound=False) + elif settings('playFromStream') != "true": # Permanently set direct stream as true - utils.settings('playFromStream', value="true") - utils.settings('failCount', value="0") - xbmcgui.Dialog().notification( - heading="Emby server", - message=("Direct play failed 3 times. Enabled play " - "from HTTP in the add-on settings."), - icon="special://home/addons/plugin.video.emby/icon.png", - sound=False) + settings('playFromStream', value="true") + settings('failCount', value="0") + dialog.notification( + heading="Emby for Kodi", + message=lang(33012), + icon="special://home/addons/plugin.video.emby/icon.png", + sound=False) return False return True @@ -185,28 +192,32 @@ 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 isDirectStream(self): + log = self.logMsg + item = self.item videotrack = item['MediaSources'][0]['Name'] @@ -221,8 +232,8 @@ class PlayUtils(): '2': 720, '3': 1080 } - self.logMsg("Resolution is: %sP, transcode for resolution: %sP+" - % (resolution, res[transcodeH265]), 1) + log("Resolution is: %sP, transcode for resolution: %sP+" + % (resolution, res[transcodeH265]), 1) if res[transcodeH265] <= resolution: return False @@ -234,7 +245,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 @@ -245,12 +256,12 @@ class PlayUtils(): server = self.server itemid = item['Id'] - type = item['Type'] + itemtype = item['Type'] if 'Path' in item and item['Path'].endswith('.strm'): # Allow strm loading when direct streaming playurl = self.directPlay() - elif type == "Audio": + elif itemtype == "Audio": playurl = "%s/emby/Audio/%s/stream.mp3" % (server, itemid) else: playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid) @@ -259,15 +270,17 @@ class PlayUtils(): def isNetworkSufficient(self): + log = self.logMsg + settings = self.getBitrate()*1000 try: sourceBitrate = int(self.item['MediaSources'][0]['Bitrate']) except (KeyError, TypeError): - self.logMsg("Bitrate value is missing.", 1) + log("Bitrate value is missing.", 1) else: - self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s" - % (settings, sourceBitrate), 1) + log("The add-on settings bitrate is: %s, the video bitrate required is: %s" + % (settings, sourceBitrate), 1) if settings < sourceBitrate: return False @@ -335,6 +348,10 @@ class PlayUtils(): return bitrate.get(videoQuality, 2147483) def audioSubsPref(self, url, listitem): + + log = self.logMsg + lang = utils.language + dialog = xbmcgui.Dialog() # For transcoding only # Present the list of audio to select from audioStreamsList = {} @@ -373,8 +390,6 @@ class PlayUtils(): audioStreams.append(track) elif 'Subtitle' in type: - '''if stream['IsExternal']: - continue''' try: track = "%s - %s" % (index, stream['Language']) except: @@ -396,7 +411,7 @@ class PlayUtils(): if len(audioStreams) > 1: - resp = xbmcgui.Dialog().select("Choose the audio stream", audioStreams) + resp = dialog.select(lang(33013), audioStreams) if resp > -1: # User selected audio selected = audioStreams[resp] @@ -409,7 +424,7 @@ class PlayUtils(): playurlprefs += "&AudioStreamIndex=%s" % selectAudioIndex if len(subtitleStreams) > 1: - resp = xbmcgui.Dialog().select("Choose the subtitle stream", subtitleStreams) + resp = dialog.select(lang(33014), subtitleStreams) if resp == 0: # User selected no subtitles pass @@ -424,7 +439,7 @@ class PlayUtils(): itemid = item['Id'] url = [("%s/Videos/%s/%s/Subtitles/%s/Stream.srt" % (self.server, itemid, itemid, selectSubsIndex))] - self.logMsg("Set up subtitles: %s %s" % (selectSubsIndex, url), 1) + log("Set up subtitles: %s %s" % (selectSubsIndex, url), 1) listitem.setSubtitles(url) else: # Burn subtitles diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index e26c90e3..bff2c91d 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -18,12 +18,14 @@ class Read_EmbyServer(): def __init__(self): + window = utils.window + 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) + self.userId = window('emby_currUser') + self.server = window('emby_server%s' % self.userId) def logMsg(self, msg, lvl=1): @@ -183,6 +185,8 @@ class Read_EmbyServer(): def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None): + log = self.logMsg + doUtils = self.doUtils items = { @@ -208,7 +212,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 @@ -250,27 +254,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 @@ -298,7 +302,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 9b4bce17..7fa353da 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -81,42 +81,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 @@ -138,35 +142,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: %s" + 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: %s" + log("Returning accessToken from SETTINGS for username: %s accessToken: %s" % (username, s_token), 2) - utils.window('emby_accessToken%s' % username, value=s_token) + 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 @@ -175,9 +184,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 @@ -186,11 +197,11 @@ class UserClient(threading.Thread): def setUserPref(self): - doUtils = self.doUtils + doUtils = self.doUtils.downloadUrl art = artwork.Artwork() url = "{server}/emby/Users/{UserId}?format=json" - result = doUtils.downloadUrl(url) + result = doUtils(url) self.userSettings = result # Set user image for skin display if result.get('PrimaryImageTag'): @@ -198,7 +209,7 @@ class UserClient(threading.Thread): # Set resume point max url = "{server}/emby/System/Configuration?format=json" - result = doUtils.downloadUrl(url) + result = doUtils(url) utils.settings('markPlayed', value=str(result['MaxResumePct'])) @@ -218,26 +229,31 @@ class UserClient(threading.Thread): def hasAccess(self): # 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 - self.logMsg("Access is restricted.", 1) + log("Access is restricted.", 1) self.HasAccess = False - elif utils.window('emby_online') != "true": + elif window('emby_online') != "true": # Server connection failed pass - elif utils.window('emby_serverStatus') == "restricted": - self.logMsg("Access is granted.", 1) + elif window('emby_serverStatus') == "restricted": + log("Access is granted.", 1) self.HasAccess = True - utils.window('emby_serverStatus', clear=True) - xbmcgui.Dialog().notification("Emby server", "Access is enabled.") + 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() @@ -252,8 +268,8 @@ class UserClient(threading.Thread): # Test the validity of current token if authenticated == False: url = "%s/emby/Users/%s?format=json" % (self.currServer, userId) - utils.window('emby_currUser', value=userId) - utils.window('emby_accessToken%s' % userId, value=self.currToken) + window('emby_currUser', value=userId) + window('emby_accessToken%s' % userId, value=self.currToken) result = doUtils.downloadUrl(url) if result == 401: @@ -262,10 +278,10 @@ class UserClient(threading.Thread): return False # Set to windows property - utils.window('emby_currUser', value=userId) - utils.window('emby_accessToken%s' % userId, value=self.currToken) - utils.window('emby_server%s' % userId, value=self.currServer) - utils.window('emby_server_%s' % userId, value=self.getServer(prefix=False)) + window('emby_currUser', value=userId) + window('emby_accessToken%s' % userId, value=self.currToken) + window('emby_server%s' % userId, value=self.currServer) + window('emby_server_%s' % userId, value=self.getServer(prefix=False)) # Set DownloadUtils values doUtils.setUsername(username) @@ -284,6 +300,13 @@ class UserClient(threading.Thread): def authenticate(self): + + log = self.logMsg + lang = utils.language + window = utils.window + settings = utils.settings + dialog = xbmcgui.Dialog() + # Get /profile/addon_data addondir = xbmc.translatePath(self.addon.getAddonInfo('profile')).decode('utf-8') hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir) @@ -293,12 +316,12 @@ class UserClient(threading.Thread): # If there's no settings.xml if not hasSettings: - self.logMsg("No settings.xml found.", 1) + log("No settings.xml found.", 1) self.auth = False return # If no user information elif not server or not username: - self.logMsg("Missing server information.", 1) + log("Missing server information.", 1) self.auth = False return # If there's a token, load the user @@ -308,9 +331,9 @@ class UserClient(threading.Thread): if result == False: pass else: - self.logMsg("Current user: %s" % self.currUser, 1) - self.logMsg("Current userId: %s" % self.currUserId, 1) - self.logMsg("Current accessToken: %s" % self.currToken, 2) + log("Current user: %s" % self.currUser, 1) + log("Current userId: %s" % self.currUserId, 1) + log("Current accessToken: %s" % self.currToken, 2) return ##### AUTHENTICATE USER ##### @@ -325,92 +348,93 @@ class UserClient(threading.Thread): if username.decode('utf-8') in name: # If user has password if user['HasPassword'] == True: - password = xbmcgui.Dialog().input( - heading="Enter password for user: %s" % username, + password = dialog.input( + heading="%s %s" % (lang(33008), username), option=xbmcgui.ALPHANUM_HIDE_INPUT) # If password dialog is cancelled if not password: - self.logMsg("No password entered.", 0) - utils.window('emby_serverStatus', value="Stop") + log("No password entered.", 0) + window('emby_serverStatus', value="Stop") self.auth = False return break else: # Manual login, user is hidden - password = xbmcgui.Dialog().input( - heading="Enter password for user: %s" % username, - option=xbmcgui.ALPHANUM_HIDE_INPUT) + password = dialog.input( + heading="%s %s" % (lang(33008), username), + option=xbmcgui.ALPHANUM_HIDE_INPUT) sha1 = hashlib.sha1(password) sha1 = sha1.hexdigest() # Authenticate username and password url = "%s/emby/Users/AuthenticateByName?format=json" % server data = {'username': username, 'password': sha1} - self.logMsg(data, 2) + log(data, 2) result = self.doUtils.downloadUrl(url, postBody=data, type="POST", authenticate=False) try: - self.logMsg("Auth response: %s" % result, 1) + log("Auth response: %s" % result, 1) accessToken = result['AccessToken'] except (KeyError, TypeError): - self.logMsg("Failed to retrieve the api key.", 1) + log("Failed to retrieve the api key.", 1) accessToken = None if accessToken is not None: self.currUser = username - xbmcgui.Dialog().notification("Emby server", "Welcome %s!" % self.currUser) + dialog.notification("Emby for Kodi", "%s %s!" % (lang(33000), self.currUser), 1) userId = result['User']['Id'] - utils.settings('accessToken', value=accessToken) - utils.settings('userId%s' % username, value=userId) - self.logMsg("User Authenticated: %s" % accessToken, 1) + settings('accessToken', value=accessToken) + settings('userId%s' % username, value=userId) + log("User Authenticated: %s" % accessToken, 1) self.loadCurrUser(authenticated=True) - utils.window('emby_serverStatus', clear=True) + window('emby_serverStatus', clear=True) self.retry = 0 else: - self.logMsg("User authentication failed.", 1) - utils.settings('accessToken', value="") - utils.settings('userId%s' % username, value="") - xbmcgui.Dialog().ok("Error connecting", "Invalid username or password.") + log("User authentication failed.", 1) + settings('accessToken', value="") + settings('userId%s' % username, value="") + dialog.ok(lang(33001), lang(33009)) # Give two attempts at entering password if self.retry == 2: - self.logMsg( - """Too many retries. You can retry by resetting - attempts in the addon settings.""", 1) - utils.window('emby_serverStatus', value="Stop") - xbmcgui.Dialog().ok( - heading="Error connecting", - line1="Failed to authenticate too many times.", - line2="You can retry by resetting attempts in the addon settings.") + 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)) self.retry += 1 self.auth = False def resetClient(self): - self.logMsg("Reset UserClient authentication.", 1) - username = self.getUsername() + log = self.logMsg + + log("Reset UserClient authentication.", 1) + userId = self.getUserId() if self.currToken is not None: # In case of 401, removed saved token utils.settings('accessToken', value="") - utils.window('emby_accessToken%s' % username, clear=True) + utils.window('emby_accessToken%s' % userId, clear=True) self.currToken = None - self.logMsg("User token has been removed.", 1) + log("User token has been removed.", 1) self.auth = True self.currUser = None def run(self): + log = self.logMsg + window = utils.window + monitor = xbmc.Monitor() - self.logMsg("----===## Starting UserClient ##===----", 0) + log("----===## Starting UserClient ##===----", 0) while not monitor.abortRequested(): - status = utils.window('emby_serverStatus') + status = window('emby_serverStatus') if status: # Verify the connection status to server if status == "restricted": @@ -419,12 +443,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 @@ -436,13 +460,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 @@ -454,8 +478,8 @@ class UserClient(threading.Thread): # Abort was requested while waiting. We should exit break - self.doUtils.stopSession() - self.logMsg("##===---- UserClient Stopped ----===##", 0) + self.doUtils.stopSession() + log("##===---- UserClient Stopped ----===##", 0) def stopClient(self): # When emby for kodi terminates diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py index 6b5aba72..19aa1d6f 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -23,7 +23,7 @@ class VideoNodes(object): clientInfo = clientinfo.ClientInfo() self.addonName = clientInfo.getAddonName() - self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2]) + self.kodiversion = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) def logMsg(self, msg, lvl=1): @@ -75,7 +75,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: @@ -102,7 +102,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) @@ -181,7 +181,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) @@ -323,7 +323,7 @@ class VideoNodes(object): cleantagname = utils.normalize_nodes(tagname) nodepath = xbmc.translatePath("special://profile/library/video/").decode('utf-8') nodeXML = "%semby_%s.xml" % (nodepath, cleantagname) - path = "library://video/emby_%s.xml" % (cleantagname) + path = "library://video/emby_%s.xml" % cleantagname windowpath = "ActivateWindow(Video,%s,return)" % path # Create the video node directory diff --git a/resources/lib/websocket_client.py b/resources/lib/websocket_client.py index 0da3f4a6..acf0df36 100644 --- a/resources/lib/websocket_client.py +++ b/resources/lib/websocket_client.py @@ -324,4 +324,4 @@ class WebSocket_Client(threading.Thread): self.stopWebsocket = True self.client.close() - self.logMsg("Stopping thread.") \ No newline at end of file + self.logMsg("Stopping thread.", 1) \ No newline at end of file diff --git a/service.py b/service.py index 244e77c8..3681b64e 100644 --- a/service.py +++ b/service.py @@ -277,14 +277,14 @@ class Service(): ##### Emby thread is terminating. ##### + if self.userclient_running: + user.stopClient() + if self.library_running: library.stopThread() if self.websocket_running: ws.stopClient() - - if self.userclient_running: - user.stopClient() log("======== STOP %s ========" % self.addonName, 0) From 8e9efe9734371f7a977f0006f85136ee989916f7 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Wed, 17 Feb 2016 18:37:45 -0600 Subject: [PATCH 17/22] Add autoclose to dialog Delete after playback --- resources/language/English/strings.xml | 2 +- resources/lib/player.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 15ecc74e..d20dbf0e 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -314,7 +314,7 @@ Direct play failed 3 times. Enabled play from HTTP. Choose the audio stream Choose the subtitles stream - Delete file on your Emby server? + Delete file from your Emby server? Play trailers? Gathering movies from: Gathering boxsets diff --git a/resources/lib/player.py b/resources/lib/player.py index d4b2e624..e6ad6d93 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -490,7 +490,7 @@ class Player(xbmc.Player): offerDelete = False if percentComplete >= markPlayedAt and offerDelete: - resp = xbmcgui.Dialog().yesno(lang(30091), lang(33015)) + resp = xbmcgui.Dialog().yesno(lang(30091), lang(33015), autoclose=120000) if not resp: log("User skipped deletion.", 1) continue From c40aa46b644e376920fd1c81ead070afed744169 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 18 Feb 2016 12:09:36 -0600 Subject: [PATCH 18/22] Fix encoding error --- resources/lib/userclient.py | 3 ++- service.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index 7fa353da..066f1a6b 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -383,7 +383,8 @@ class UserClient(threading.Thread): if accessToken is not None: self.currUser = username - dialog.notification("Emby for Kodi", "%s %s!" % (lang(33000), self.currUser), 1) + dialog.notification("Emby for Kodi", + "%s %s!" % (lang(33000), self.currUser.decode('utf-8'))) userId = result['User']['Id'] settings('accessToken', value=accessToken) settings('userId%s' % username, value=userId) diff --git a/service.py b/service.py index 3681b64e..b54a4a7c 100644 --- a/service.py +++ b/service.py @@ -178,7 +178,9 @@ class Service(): add = "" xbmcgui.Dialog().notification( heading="Emby for Kodi", - message="%s %s%s!" % (lang(33000), user.currUser, add), + 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) From 06cd0e981c95896247ce4e4372152e142389b48d Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 18 Feb 2016 19:16:49 -0600 Subject: [PATCH 19/22] Hide manual sync when ran at start-up --- default.py | 2 +- resources/lib/librarysync.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/default.py b/default.py index 91daef65..1bd09c43 100644 --- a/default.py +++ b/default.py @@ -107,7 +107,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/lib/librarysync.py b/resources/lib/librarysync.py index 261e6c48..c894f0c5 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -219,7 +219,7 @@ class LibrarySync(threading.Thread): connection.commit() log("Commit successful.", 1) - def fullSync(self, manualrun=False, repair=False): + def fullSync(self, manualrun=False, repair=False, forceddialog=False): log = self.logMsg window = utils.window @@ -254,11 +254,13 @@ class LibrarySync(threading.Thread): message = "Manual sync" elif repair: message = "Repair sync" + forceddialog = True else: message = "Initial sync" + forceddialog = True window('emby_initialScan', value="true") - pDialog = self.progressDialog("%s" % message, forced=True) + pDialog = self.progressDialog("%s" % message, forced=forceddialog) starttotal = datetime.now() # Set views @@ -980,10 +982,10 @@ class LibrarySync(threading.Thread): class ManualSync(LibrarySync): - def __init__(self): + def __init__(self, dialog=False): LibrarySync.__init__(self) - self.fullSync(manualrun=True) + self.fullSync(manualrun=True, forceddialog=dialog) def movies(self, embycursor, kodicursor, pdialog): From 38f432a282c22c7a42e8329c6141cb19687fb297 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 18 Feb 2016 20:01:11 -0600 Subject: [PATCH 20/22] Add missing artist link for music videos --- resources/lib/kodidb_functions.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodidb_functions.py index 3929bb52..fc117d11 100644 --- a/resources/lib/kodidb_functions.py +++ b/resources/lib/kodidb_functions.py @@ -284,6 +284,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(( @@ -409,6 +420,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() From b3d4bb9192aa72d9bf17134ffb7527fd05758ec9 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 18 Feb 2016 20:38:11 -0600 Subject: [PATCH 21/22] Version bump 2.2.1 --- addon.xml | 2 +- changelog.txt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 0b1f3403..2c9a6569 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index 7f19ba1a..cf3281ca 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +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. From 2ef213b4e36cd46c88880c1c112b669e28b924e2 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 18 Feb 2016 22:06:33 -0600 Subject: [PATCH 22/22] Fix for proxy --- resources/lib/librarysync.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index c894f0c5..32774c1c 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -114,7 +114,7 @@ class LibrarySync(threading.Thread): log("Last sync run: %s" % lastSyncTime, 1) # get server RetentionDateTime - url = "{server}/Emby.Kodi.SyncQueue/GetServerDateTime?format=json" + url = "{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json" result = doUtils(url) retention_time = "2010-01-01T00:00:00Z" if result and result.get('RetentionDateTime'): @@ -166,7 +166,7 @@ class LibrarySync(threading.Thread): # Save last sync time overlap = 2 - url = "{server}/Emby.Kodi.SyncQueue/GetServerDateTime?format=json" + url = "{server}/emby/Emby.Kodi.SyncQueue/GetServerDateTime?format=json" result = self.doUtils(url) try: # datetime fails when used more than once, TypeError server_time = result['ServerDateTime']