From d7d5e8b6e19a3e2c2a646370ea37d51f63a0d44e Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Fri, 15 Jan 2016 03:56:53 -0600 Subject: [PATCH 01/47] Force transcode 720/h265 --- resources/lib/playutils.py | 12 ++++++++++++ resources/settings.xml | 1 + 2 files changed, 13 insertions(+) diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index 81bd5c42..cc04d3b2 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -97,6 +97,12 @@ class PlayUtils(): self.logMsg("Option to transcode 1080P/H265 enabled.", 1) return False + elif (utils.settings('transcode720H265') == "true" and + item['MediaSources'][0]['Name'].startswith(("720P/HEVC","720P/H265"))): + # Avoid H265 720p + self.logMsg("Option to transcode 720P/H265 enabled.", 1) + return False + canDirectPlay = item['MediaSources'][0]['SupportsDirectPlay'] # Make sure direct play is supported by the server if not canDirectPlay: @@ -197,6 +203,12 @@ class PlayUtils(): self.logMsg("Option to transcode 1080P/H265 enabled.", 1) return False + elif (utils.settings('transcode720H265') == "true" and + item['MediaSources'][0]['Name'].startswith(("720P/HEVC","720P/H265"))): + # Avoid H265 720p + self.logMsg("Option to transcode 720P/H265 enabled.", 1) + return False + # Requirement: BitRate, supported encoding canDirectStream = item['MediaSources'][0]['SupportsDirectStream'] # Make sure the server supports it diff --git a/resources/settings.xml b/resources/settings.xml index 53063d1a..5293dc61 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -48,6 +48,7 @@ + From 82f117222a12be4b493ada18df49f5262edaf0e1 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Fri, 15 Jan 2016 15:31:55 +0100 Subject: [PATCH 02/47] fix exception in getSongTags when rating is none --- resources/lib/musicutils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index d9715749..f807fe57 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -83,7 +83,9 @@ def getSongTags(file): if rating > 5: rating = (rating / 255) * 5 else: logMsg( "Not supported fileformat or unable to access file: %s" %(filename)) - rating = int(round(rating,0)) + + if rating: + rating = int(round(rating,0)) except Exception as e: #file in use ? From 9e2f789e53c3f2f777c5d89c9ac1ad683fa778bf Mon Sep 17 00:00:00 2001 From: Shaun Date: Sat, 16 Jan 2016 14:07:36 +1100 Subject: [PATCH 03/47] add a thread pool option to the image cache --- addon.xml | 2 +- resources/lib/artwork.py | 147 +++++++++++++++++++++------- resources/lib/entrypoint.py | 30 ++---- resources/lib/image_cache_thread.py | 52 ++++++++++ resources/settings.xml | 1 + 5 files changed, 173 insertions(+), 59 deletions(-) create mode 100644 resources/lib/image_cache_thread.py diff --git a/addon.xml b/addon.xml index d0f562d3..1eb161ed 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 5952c204..139ebdae 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -8,10 +8,12 @@ import os import urllib import xbmc +import xbmcgui import xbmcvfs import utils import clientinfo +import image_cache_thread ################################################################################################# @@ -23,13 +25,18 @@ class Artwork(): xbmc_username = None xbmc_password = None + imageCacheThreads = [] + imageCacheLimitThreads = 0 def __init__(self): - self.clientinfo = clientinfo.ClientInfo() self.addonName = self.clientinfo.getAddonName() self.enableTextureCache = utils.settings('enableTextureCache') == "true" + self.imageCacheLimitThreads = int(utils.settings("imageCacheLimit")) + self.imageCacheLimitThreads = int(self.imageCacheLimitThreads * 5); + utils.logMsg("Using Image Cache Thread Count: " + str(self.imageCacheLimitThreads), 1) + if not self.xbmc_port and self.enableTextureCache: self.setKodiWebServerDetails() @@ -37,7 +44,6 @@ class Artwork(): self.server = utils.window('emby_server%s' % self.userId) def logMsg(self, msg, lvl=1): - className = self.__class__.__name__ utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) @@ -159,63 +165,130 @@ class Artwork(): def FullTextureCacheSync(self): # This method will sync all Kodi artwork to textures13.db # and cache them locally. This takes diskspace! - - # Remove all existing textures first - path = xbmc.translatePath("special://thumbnails/").decode('utf-8') - if xbmcvfs.exists(path): - allDirs, allFiles = xbmcvfs.listdir(path) - for dir in allDirs: - allDirs, allFiles = xbmcvfs.listdir(path+dir) - for file in allFiles: - xbmcvfs.delete(os.path.join(path+dir,file)) - textureconnection = utils.KodiSQL('texture') - texturecursor = textureconnection.cursor() - texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') - rows = texturecursor.fetchall() - for row in rows: - tableName = row[0] - if(tableName != "version"): - texturecursor.execute("DELETE FROM " + tableName) - textureconnection.commit() - texturecursor.close() + if not xbmcgui.Dialog().yesno("Image Texture Cache", "Running the image cache process can take some time.", "Are you sure you want continue?"): + return + + self.logMsg("Doing Image Cache Sync", 1) + dialog = xbmcgui.DialogProgressBG() + dialog.create("Emby for Kodi", "Image Cache Sync") + + # ask to rest all existing or not + if xbmcgui.Dialog().yesno("Image Texture Cache", "Reset all existing cache data first?", ""): + self.logMsg("Resetting all cache data first", 1) + # Remove all existing textures first + path = xbmc.translatePath("special://thumbnails/").decode('utf-8') + if xbmcvfs.exists(path): + allDirs, allFiles = xbmcvfs.listdir(path) + for dir in allDirs: + allDirs, allFiles = xbmcvfs.listdir(path+dir) + for file in allFiles: + xbmcvfs.delete(os.path.join(path+dir,file)) + + # remove all existing data from texture DB + textureconnection = utils.kodiSQL('texture') + texturecursor = textureconnection.cursor() + texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') + rows = texturecursor.fetchall() + for row in rows: + tableName = row[0] + if(tableName != "version"): + texturecursor.execute("DELETE FROM " + tableName) + textureconnection.commit() + texturecursor.close() + # Cache all entries in video DB - connection = utils.KodiSQL('video') + connection = utils.kodiSQL('video') cursor = connection.cursor() - cursor.execute("SELECT url FROM art") + cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors result = cursor.fetchall() + total = len(result) + count = 1 + percentage = 0.0 + self.logMsg("Image cache sync about to process " + str(total) + " images", 1) for url in result: + percentage = int((float(count) / float(total))*100) + textMessage = str(count) + " of " + str(total) + " (" + str(len(self.imageCacheThreads)) + ")" + dialog.update(percentage, message="Updating Image Cache: " + textMessage) self.CacheTexture(url[0]) - cursor.close() + count += 1 + cursor.close() # Cache all entries in music DB - connection = utils.KodiSQL('music') + connection = utils.kodiSQL('music') cursor = connection.cursor() cursor.execute("SELECT url FROM art") result = cursor.fetchall() + total = len(result) + count = 1 + percentage = 0.0 + self.logMsg("Image cache sync about to process " + str(total) + " images", 1) for url in result: + percentage = int((float(count) / float(total))*100) + textMessage = str(count) + " of " + str(total) + dialog.update(percentage, message="Updating Image Cache: " + textMessage) self.CacheTexture(url[0]) + count += 1 cursor.close() + + dialog.update(percentage, message="Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) + self.logMsg("Waiting for all threads to exit", 1) + while len(self.imageCacheThreads) > 0: + for thread in self.imageCacheThreads: + if thread.isFinished: + self.imageCacheThreads.remove(thread) + dialog.update(percentage, message="Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) + self.logMsg("Waiting for all threads to exit: " + str(len(self.imageCacheThreads)), 1) + xbmc.sleep(500) + + dialog.close() + def addWorkerImageCacheThread(self, urlToAdd): + + while(True): + # removed finished + for thread in self.imageCacheThreads: + if thread.isFinished: + self.imageCacheThreads.remove(thread) + + # add a new thread or wait and retry if we hit our limit + if(len(self.imageCacheThreads) < self.imageCacheLimitThreads): + newThread = image_cache_thread.image_cache_thread() + newThread.setUrl(self.double_urlencode(urlToAdd)) + newThread.setHost(self.xbmc_host, self.xbmc_port) + newThread.setAuth(self.xbmc_username, self.xbmc_password) + newThread.start() + self.imageCacheThreads.append(newThread) + return + else: + self.logMsg("Waiting for empty queue spot: " + str(len(self.imageCacheThreads)), 2) + xbmc.sleep(50) + + def CacheTexture(self, url): # Cache a single image url to the texture cache if url and self.enableTextureCache: self.logMsg("Processing: %s" % url, 2) + + if(self.imageCacheLimitThreads == 0 or self.imageCacheLimitThreads == None): + #Add image to texture cache by simply calling it at the http endpoint + + url = self.double_urlencode(url) + try: # Extreme short timeouts so we will have a exception. + response = requests.head( + url=( + "http://%s:%s/image/image://%s" + % (self.xbmc_host, self.xbmc_port, url)), + auth=(self.xbmc_username, self.xbmc_password), + timeout=(0.01, 0.01)) + # We don't need the result + except: pass + + else: + self.addWorkerImageCacheThread(url) - # Add image to texture cache by simply calling it at the http endpoint - url = self.double_urlencode(url) - try: # Extreme short timeouts so we will have a exception. - response = requests.head( - url=( - "http://%s:%s/image/image://%s" - % (self.xbmc_host, self.xbmc_port, url)), - auth=(self.xbmc_username, self.xbmc_password), - timeout=(0.01, 0.01)) - # We don't need the result - except: pass - def addArtwork(self, artwork, kodiId, mediaType, cursor): # Kodi conversion table diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 9afd5f41..2338a512 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -72,27 +72,15 @@ def doMainListing(): addDirectoryItem(label, path) # some extra entries for settings and stuff. TODO --> localize the labels - addDirectoryItem("Network credentials", "plugin://plugin.video.emby/?mode=passwords", False) - addDirectoryItem("Settings", "plugin://plugin.video.emby/?mode=settings", False) - addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser", False) - #addDirectoryItem("Cache all images to Kodi texture cache (advanced)", "plugin://plugin.video.emby/?mode=texturecache") - addDirectoryItem( - label="Refresh Emby playlists", - path="plugin://plugin.video.emby/?mode=refreshplaylist", - folder=False) - addDirectoryItem("Perform manual sync", "plugin://plugin.video.emby/?mode=manualsync", False) - addDirectoryItem( - label="Repair local database (force update all content)", - path="plugin://plugin.video.emby/?mode=repair", - folder=False) - addDirectoryItem( - label="Perform local database reset (full resync)", - path="plugin://plugin.video.emby/?mode=reset", - folder=False) - addDirectoryItem( - label="Sync Emby Theme Media to Kodi", - path="plugin://plugin.video.emby/?mode=thememedia", - folder=False) + addDirectoryItem("Network credentials", "plugin://plugin.video.emby/?mode=passwords") + addDirectoryItem("Settings", "plugin://plugin.video.emby/?mode=settings") + addDirectoryItem("Add user to session", "plugin://plugin.video.emby/?mode=adduser") + addDirectoryItem("Refresh Emby playlists", "plugin://plugin.video.emby/?mode=refreshplaylist") + addDirectoryItem("Perform manual sync", "plugin://plugin.video.emby/?mode=manualsync") + addDirectoryItem("Repair local database (force update all content)", "plugin://plugin.video.emby/?mode=repair") + addDirectoryItem("Perform local database reset (full resync)", "plugin://plugin.video.emby/?mode=reset") + addDirectoryItem("Cache all images to Kodi texture cache", "plugin://plugin.video.emby/?mode=texturecache") + addDirectoryItem("Sync Emby Theme Media to Kodi", "plugin://plugin.video.emby/?mode=thememedia") xbmcplugin.endOfDirectory(int(sys.argv[1])) diff --git a/resources/lib/image_cache_thread.py b/resources/lib/image_cache_thread.py new file mode 100644 index 00000000..626be481 --- /dev/null +++ b/resources/lib/image_cache_thread.py @@ -0,0 +1,52 @@ +import threading +import utils +import xbmc +import requests + +class image_cache_thread(threading.Thread): + + urlToProcess = None + isFinished = False + + xbmc_host = "" + xbmc_port = "" + xbmc_username = "" + xbmc_password = "" + + def __init__(self): + self.monitor = xbmc.Monitor() + threading.Thread.__init__(self) + + def logMsg(self, msg, lvl=1): + className = self.__class__.__name__ + utils.logMsg("%s" % className, msg, lvl) + + def setUrl(self, url): + self.urlToProcess = url + + def setHost(self, host, port): + self.xbmc_host = host + self.xbmc_port = port + + def setAuth(self, user, pwd): + self.xbmc_username = user + self.xbmc_password = pwd + + def run(self): + + self.logMsg("Image Caching Thread Processing : " + self.urlToProcess, 2) + + try: + response = requests.head( + url=( + "http://%s:%s/image/image://%s" + % (self.xbmc_host, self.xbmc_port, self.urlToProcess)), + auth=(self.xbmc_username, self.xbmc_password), + timeout=(35.1, 35.1)) + # We don't need the result + except: pass + + self.logMsg("Image Caching Thread Exited", 2) + + self.isFinished = True + \ No newline at end of file diff --git a/resources/settings.xml b/resources/settings.xml index 5293dc61..e26d4564 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -31,6 +31,7 @@ + From 30a9074f714f8bfbec12d8773a4e9c2beef51db1 Mon Sep 17 00:00:00 2001 From: Shaun Date: Sat, 16 Jan 2016 17:55:04 +1100 Subject: [PATCH 04/47] fix percentages on thread clean up updates --- resources/lib/artwork.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 139ebdae..039f2111 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -206,7 +206,7 @@ class Artwork(): result = cursor.fetchall() total = len(result) count = 1 - percentage = 0.0 + percentage = 0 self.logMsg("Image cache sync about to process " + str(total) + " images", 1) for url in result: percentage = int((float(count) / float(total))*100) @@ -223,7 +223,7 @@ class Artwork(): result = cursor.fetchall() total = len(result) count = 1 - percentage = 0.0 + percentage = 0 self.logMsg("Image cache sync about to process " + str(total) + " images", 1) for url in result: percentage = int((float(count) / float(total))*100) @@ -233,13 +233,13 @@ class Artwork(): count += 1 cursor.close() - dialog.update(percentage, message="Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) + dialog.update(100, message="Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) self.logMsg("Waiting for all threads to exit", 1) while len(self.imageCacheThreads) > 0: for thread in self.imageCacheThreads: if thread.isFinished: self.imageCacheThreads.remove(thread) - dialog.update(percentage, message="Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) + dialog.update(100, message="Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) self.logMsg("Waiting for all threads to exit: " + str(len(self.imageCacheThreads)), 1) xbmc.sleep(500) From 2e5c0aa8f2dce7bfb98efafe94d3108bb664f6ec Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 16 Jan 2016 16:31:42 -0600 Subject: [PATCH 05/47] Fix music crash --- resources/lib/itemtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 0da05345..92a94a25 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -2225,7 +2225,7 @@ class Music(Items): # Only proceed if we actually have a rating from the file if file_rating is None and currentvalue: return (currentvalue, comment) - elif file_rating is None and currentvalue is None: + elif file_rating is None and not currentvalue: return (emby_rating, comment) file_rating = int(round(file_rating,0)) From ca6df478bdae321e097722ae81c0efba8477b291 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 16 Jan 2016 16:49:30 -0600 Subject: [PATCH 06/47] Version bump 1.1.76 --- addon.xml | 2 +- changelog.txt | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 1eb161ed..7f2d1e25 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index 34f9cb56..917a3cca 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,12 @@ +version 1.1.76 +- Add music rating system +- Add home videos as a dynamic plugin entry (requires a reset) +- Add photo library (requires a reset) +- Add/Fix force transcode setting for 720p-1080p/HEVC-H265 formats +- Fix to incremental sync, caused by the server restarting +- Fix for image caching during the initial sync on rpi devices +- Fix to audio/subtitles tracks (requires a repair, or reset) + version 1.1.72 - Fix to extrafanart - Fix for artists deletion From 2011f5671a7addc3010089b0420995e11f7feb1a Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 16 Jan 2016 16:50:34 -0600 Subject: [PATCH 07/47] Typo --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 917a3cca..b8a87643 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,7 +1,7 @@ version 1.1.76 - Add music rating system - Add home videos as a dynamic plugin entry (requires a reset) -- Add photo library (requires a reset) +- Add photo library - Add/Fix force transcode setting for 720p-1080p/HEVC-H265 formats - Fix to incremental sync, caused by the server restarting - Fix for image caching during the initial sync on rpi devices From ee414472e0945425c3b3c5762fa06bf8b1afa2f2 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 16 Jan 2016 18:28:30 -0600 Subject: [PATCH 08/47] Correct encoding Everything xbmc returns is already in utf-8 --- resources/lib/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 956f63bb..aca6b790 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -62,7 +62,7 @@ def settings(setting, value=None): def language(stringid): # Central string retrieval addon = xbmcaddon.Addon(id='plugin.video.emby') - string = addon.getLocalizedString(stringid).decode("utf-8") + string = addon.getLocalizedString(stringid) return string From fce34ac2af9b2716001b66308c31a6cc3eaf7f52 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sat, 16 Jan 2016 19:02:22 -0600 Subject: [PATCH 09/47] Adjust provides list --- addon.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 7f2d1e25..cfd57b4b 100644 --- a/addon.xml +++ b/addon.xml @@ -12,7 +12,7 @@ - executable video audio image + video audio From 016db1c7553512014baafbfa027e99c0184272eb Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 17 Jan 2016 16:27:25 -0600 Subject: [PATCH 10/47] Re-add image type for pictures --- addon.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index cfd57b4b..275f28d3 100644 --- a/addon.xml +++ b/addon.xml @@ -12,7 +12,7 @@ - video audio + video audio image From b0120aefd52430375df68556660f2ef8db786c20 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Mon, 18 Jan 2016 09:45:39 +0100 Subject: [PATCH 11/47] fix: return emby nodes in skinshortcuts dialog --- resources/lib/entrypoint.py | 6 +++++- resources/lib/utils.py | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 2338a512..4e0df93e 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -68,7 +68,11 @@ def doMainListing(): path = utils.window('Emby.nodes.%s.content' % i) label = utils.window('Emby.nodes.%s.title' % i) type = utils.window('Emby.nodes.%s.type' % i) - if path and ((xbmc.getCondVisibility("Window.IsActive(Pictures)") and type=="photos") or (xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and type != "photos")): + #because we do not use seperate entrypoints for each content type, we need to figure out which items to show in each listing. + #if skinshortcuts is active, we just show all nodes + if path and ((xbmc.getCondVisibility("Window.IsActive(Pictures)") and type=="photos") + or (xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and type != "photos") + or xbmc.getCondVisibility("Window.IsActive(script-skinshortcuts.xml)")): addDirectoryItem(label, path) # some extra entries for settings and stuff. TODO --> localize the labels diff --git a/resources/lib/utils.py b/resources/lib/utils.py index aca6b790..7b69bb79 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -57,12 +57,15 @@ def settings(setting, value=None): if value is not None: addon.setSetting(setting, value) else: - return addon.getSetting(setting) + return addon.getSetting(setting) #returns unicode object def language(stringid): # Central string retrieval addon = xbmcaddon.Addon(id='plugin.video.emby') - string = addon.getLocalizedString(stringid) + string = addon.getLocalizedString(stringid) #returns unicode object + if isinstance(string, unicode): print "EMBY LANGUAGE STRING IS UNICODE !" + elif isinstance(string, txt): print "EMBY LANGUAGE STRING IS TEXT !" + else: print "EMBY LANGUAGE STRING IS UNKNOWN !" return string From ca0906aaafacf1bf2bbce5a8f11e2756adad87e2 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Mon, 18 Jan 2016 09:47:22 +0100 Subject: [PATCH 12/47] remove redundant logging --- resources/lib/utils.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 7b69bb79..1c10cce7 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -63,10 +63,6 @@ def language(stringid): # Central string retrieval addon = xbmcaddon.Addon(id='plugin.video.emby') string = addon.getLocalizedString(stringid) #returns unicode object - if isinstance(string, unicode): print "EMBY LANGUAGE STRING IS UNICODE !" - elif isinstance(string, txt): print "EMBY LANGUAGE STRING IS TEXT !" - else: print "EMBY LANGUAGE STRING IS UNKNOWN !" - return string def kodiSQL(type="video"): From 2bd1d139c1510ecdcf5d15baccde3269ed57854a Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Mon, 18 Jan 2016 10:00:32 +0100 Subject: [PATCH 13/47] unicode fixes fix for users who have redirected kodi data folders (replace open with xbmcvfs.file) --- resources/lib/artwork.py | 2 +- resources/lib/clientinfo.py | 7 ++++--- resources/lib/entrypoint.py | 10 +++++----- resources/lib/utils.py | 22 +++++++++++----------- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 039f2111..ef562af0 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -185,7 +185,7 @@ class Artwork(): for dir in allDirs: allDirs, allFiles = xbmcvfs.listdir(path+dir) for file in allFiles: - xbmcvfs.delete(os.path.join(path+dir,file)) + xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8'))) # remove all existing data from texture DB textureconnection = utils.kodiSQL('texture') diff --git a/resources/lib/clientinfo.py b/resources/lib/clientinfo.py index 2c598934..b86c8a7b 100644 --- a/resources/lib/clientinfo.py +++ b/resources/lib/clientinfo.py @@ -7,6 +7,7 @@ from uuid import uuid4 import xbmc import xbmcaddon +import xbmcvfs import utils @@ -43,7 +44,7 @@ class ClientInfo(): if utils.settings('deviceNameOpt') == "false": # Use Kodi's deviceName - deviceName = xbmc.getInfoLabel('System.FriendlyName') + deviceName = xbmc.getInfoLabel('System.FriendlyName').decode('utf-8') else: deviceName = utils.settings('deviceName') deviceName = deviceName.replace("\"", "_") @@ -78,12 +79,12 @@ class ClientInfo(): GUID_file = xbmc.translatePath(os.path.join(addon_path, "machine_guid")).decode('utf-8') try: - GUID = open(GUID_file) + GUID = xbmcvfs.File(GUID_file) except Exception as e: # machine_guid does not exists. self.logMsg("Generating a new deviceid: %s" % e, 1) clientId = str("%012X" % uuid4()) - GUID = open(GUID_file, 'w') + GUID = xbmcvfs.File(GUID_file, 'w') GUID.write(clientId) else: # machine_guid already exists. Get guid. diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 4e0df93e..6c323f0f 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -283,7 +283,7 @@ def getThemeMedia(): result = doUtils.downloadUrl(url) # Create nfo and write themes to it - nfo_file = open(nfo_path, 'w') + nfo_file = xbmcvfs.File(nfo_path, 'w') pathstowrite = "" # May be more than one theme for theme in result['Items']: @@ -344,7 +344,7 @@ def getThemeMedia(): result = doUtils.downloadUrl(url) # Create nfo and write themes to it - nfo_file = open(nfo_path, 'w') + nfo_file = xbmcvfs.File(nfo_path, 'w') pathstowrite = "" # May be more than one theme for theme in result['Items']: @@ -960,12 +960,12 @@ def getExtraFanArt(): try: # for tvshows we get the embyid just from the path if xbmc.getCondVisibility("Container.Content(tvshows) | Container.Content(seasons) | Container.Content(episodes)"): - itemPath = xbmc.getInfoLabel("ListItem.Path") + itemPath = xbmc.getInfoLabel("ListItem.Path").decode('utf-8') if "plugin.video.emby" in itemPath: embyId = itemPath.split("/")[-2] else: #for movies we grab the emby id from the params - itemPath = xbmc.getInfoLabel("ListItem.FileNameAndPath") + itemPath = xbmc.getInfoLabel("ListItem.FileNameAndPath").decode('utf-8') if "plugin.video.emby" in itemPath: params = urlparse.parse_qs(itemPath) embyId = params.get('id') @@ -1003,7 +1003,7 @@ def getExtraFanArt(): # Use existing cached images dirs, files = xbmcvfs.listdir(fanartDir) for file in files: - fanartFile = os.path.join(fanartDir, file) + fanartFile = os.path.join(fanartDir, file.decode('utf-8')) li = xbmcgui.ListItem(file, path=fanartFile) xbmcplugin.addDirectoryItem( handle=int(sys.argv[1]), diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 1c10cce7..7878a08e 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -45,10 +45,10 @@ def window(property, value=None, clear=False, windowid=10000): if clear: WINDOW.clearProperty(property) - elif value is not None: - WINDOW.setProperty(property, value) - else: - return WINDOW.getProperty(property) + elif value is not None: #setproperty accepts both string and unicode but utf-8 string seems best practice to pass + WINDOW.setProperty(property.encode("utf-8"), value.encode("utf-8")) + else: #getproperty returns string so convert to unicode + return WINDOW.getProperty(property).decode("utf-8") def settings(setting, value=None): # Get or add addon setting @@ -138,11 +138,11 @@ def reset(): path = xbmc.translatePath("special://profile/library/video/").decode('utf-8') dirs, files = xbmcvfs.listdir(path) for dir in dirs: - if dir.startswith('Emby'): - shutil.rmtree("%s%s" % (path, dir)) + if dir.decode('utf-8').startswith('Emby'): + shutil.rmtree("%s%s" % (path, dir.decode('utf-8'))) for file in files: - if file.startswith('emby'): - xbmcvfs.delete("%s%s" % (path, file)) + if file.decode('utf-8').startswith('emby'): + xbmcvfs.delete("%s%s" % (path, file.decode('utf-8'))) # Wipe the kodi databases logMsg("EMBY", "Resetting the Kodi video database.") @@ -223,7 +223,7 @@ def stopProfiling(pr, profileName): timestamp = time.strftime("%Y-%m-%d %H-%M-%S") profile = "%s%s_profile_(%s).tab" % (profiles, profileName, timestamp) - f = open(profile, 'wb') + f = xbmcvfs.File(profile, 'w') f.write("NumbCalls\tTotalTime\tCumulativeTime\tFunctionName\tFileName\r\n") for (key, value) in ps.stats.items(): (filename, count, func_name) = key @@ -471,7 +471,7 @@ def playlistXSP(mediatype, tagname, viewtype="", delete=False): } logMsg("EMBY", "Writing playlist file to: %s" % xsppath, 1) try: - f = open(xsppath, 'w') + f = xbmcvfs.File(xsppath, 'w') except: logMsg("EMBY", "Failed to create playlist: %s" % xsppath, 1) return @@ -495,5 +495,5 @@ def deletePlaylists(): path = xbmc.translatePath("special://profile/playlists/video/").decode('utf-8') dirs, files = xbmcvfs.listdir(path) for file in files: - if file.startswith('Emby'): + if file.decode('utf-8').startswith('Emby'): xbmcvfs.delete("%s%s" % (path, file)) \ No newline at end of file From 08581dbc18f0c6372eb944bcb8d0441eeb187454 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 18 Jan 2016 03:23:56 -0600 Subject: [PATCH 14/47] Add progress dialog for gathering It's not very precise since we gather in batch, but better than nothing. --- resources/lib/librarysync.py | 22 ++++++++--------- resources/lib/read_embyserver.py | 42 +++++++++++++++++--------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 4ceb468e..c89c32c0 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -494,7 +494,7 @@ class LibrarySync(threading.Thread): heading="Emby for Kodi", message="Comparing movies from view: %s..." % viewName) - all_embymovies = emby.getMovies(viewId, basic=True) + all_embymovies = emby.getMovies(viewId, basic=True, dialog=pdialog) for embymovie in all_embymovies['Items']: if self.shouldStop(): @@ -515,7 +515,7 @@ class LibrarySync(threading.Thread): del updatelist[:] else: # Initial or repair sync - all_embymovies = emby.getMovies(viewId) + all_embymovies = emby.getMovies(viewId, dialog=pdialog) total = all_embymovies['TotalRecordCount'] embymovies = all_embymovies['Items'] @@ -543,7 +543,7 @@ class LibrarySync(threading.Thread): if pdialog: pdialog.update(heading="Emby for Kodi", message="Gathering boxsets from server...") - boxsets = emby.getBoxset() + boxsets = emby.getBoxset(dialog=pdialog) if compare: # Manual sync @@ -652,7 +652,7 @@ class LibrarySync(threading.Thread): heading="Emby for Kodi", message="Comparing musicvideos from view: %s..." % viewName) - all_embymvideos = emby.getMusicVideos(viewId, basic=True) + all_embymvideos = emby.getMusicVideos(viewId, basic=True, dialog=pdialog) for embymvideo in all_embymvideos['Items']: if self.shouldStop(): @@ -673,7 +673,7 @@ class LibrarySync(threading.Thread): del updatelist[:] else: # Initial or repair sync - all_embymvideos = emby.getMusicVideos(viewId) + all_embymvideos = emby.getMusicVideos(viewId, dialog=pdialog) total = all_embymvideos['TotalRecordCount'] embymvideos = all_embymvideos['Items'] @@ -755,7 +755,7 @@ class LibrarySync(threading.Thread): heading="Emby for Kodi", message="Comparing tvshows from view: %s..." % viewName) - all_embytvshows = emby.getShows(viewId, basic=True) + all_embytvshows = emby.getShows(viewId, basic=True, dialog=pdialog) for embytvshow in all_embytvshows['Items']: if self.shouldStop(): @@ -775,7 +775,7 @@ class LibrarySync(threading.Thread): total = len(updatelist) del updatelist[:] else: - all_embytvshows = emby.getShows(viewId) + all_embytvshows = emby.getShows(viewId, dialog=pdialog) total = all_embytvshows['TotalRecordCount'] embytvshows = all_embytvshows['Items'] @@ -818,7 +818,7 @@ class LibrarySync(threading.Thread): heading="Emby for Kodi", message="Comparing episodes from view: %s..." % viewName) - all_embyepisodes = emby.getEpisodes(viewId, basic=True) + all_embyepisodes = emby.getEpisodes(viewId, basic=True, dialog=pdialog) for embyepisode in all_embyepisodes['Items']: if self.shouldStop(): @@ -921,9 +921,9 @@ class LibrarySync(threading.Thread): message="Comparing %s..." % type) if type != "artists": - all_embyitems = process[type][0](basic=True) + all_embyitems = process[type][0](basic=True, dialog=pdialog) else: - all_embyitems = process[type][0]() + all_embyitems = process[type][0](dialog=pdialog) for embyitem in all_embyitems['Items']: if self.shouldStop(): @@ -952,7 +952,7 @@ class LibrarySync(threading.Thread): total = len(updatelist) del updatelist[:] else: - all_embyitems = process[type][0]() + all_embyitems = process[type][0](dialog=pdialog) total = all_embyitems['TotalRecordCount'] embyitems = all_embyitems['Items'] diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index d0158b0e..323745d4 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -149,7 +149,7 @@ class Read_EmbyServer(): } return doUtils.downloadUrl(url, parameters=params) - def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False): + def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None): doUtils = self.doUtils items = { @@ -219,7 +219,9 @@ class Read_EmbyServer(): self.logMsg("New throttle for items requested: %s" % jump, 1) else: index += jump - + if dialog: + percentage = int((float(index) / float(total))*100) + dialog.update(percentage) return items def getViews(self, type, root=False): @@ -276,15 +278,15 @@ class Read_EmbyServer(): return views - def getMovies(self, parentId, basic=False): + def getMovies(self, parentId, basic=False, dialog=None): - items = self.getSection(parentId, "Movie", basic=basic) + items = self.getSection(parentId, "Movie", basic=basic, dialog=dialog) return items - def getBoxset(self): + def getBoxset(self, dialog=None): - items = self.getSection(None, "BoxSet") + items = self.getSection(None, "BoxSet", dialog=dialog) return items @@ -294,9 +296,9 @@ class Read_EmbyServer(): return items - def getMusicVideos(self, parentId, basic=False): + def getMusicVideos(self, parentId, basic=False, dialog=None): - items = self.getSection(parentId, "MusicVideo", basic=basic) + items = self.getSection(parentId, "MusicVideo", basic=basic, dialog=dialog) return items @@ -306,9 +308,9 @@ class Read_EmbyServer(): return items - def getShows(self, parentId, basic=False): + def getShows(self, parentId, basic=False, dialog=None): - items = self.getSection(parentId, "Series", basic=basic) + items = self.getSection(parentId, "Series", basic=basic, dialog=dialog) return items @@ -332,9 +334,9 @@ class Read_EmbyServer(): return items - def getEpisodes(self, parentId, basic=False): + def getEpisodes(self, parentId, basic=False, dialog=None): - items = self.getSection(parentId, "Episode", basic=basic) + items = self.getSection(parentId, "Episode", basic=basic, dialog=dialog) return items @@ -350,7 +352,7 @@ class Read_EmbyServer(): return items - def getArtists(self): + def getArtists(self, dialog=None): doUtils = self.doUtils items = { @@ -406,12 +408,14 @@ class Read_EmbyServer(): self.logMsg("New throttle for items requested: %s" % jump, 1) else: index += jump - + if dialog: + percentage = int((float(index) / float(total))*100) + dialog.update(percentage) return items - def getAlbums(self, basic=False): + def getAlbums(self, basic=False, dialog=None): - items = self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic) + items = self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic, dialog=dialog) return items @@ -421,9 +425,9 @@ class Read_EmbyServer(): return items - def getSongs(self, basic=False): + def getSongs(self, basic=False, dialog=None): - items = self.getSection(None, "Audio", basic=basic) + items = self.getSection(None, "Audio", basic=basic, dialog=dialog) return items @@ -460,4 +464,4 @@ class Read_EmbyServer(): if mediatype: sorted_items.setdefault(mediatype, []).append(item) - return sorted_items + return sorted_items \ No newline at end of file From eae4fea76a5123a345cc66d6952b98e70ba1545b Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Mon, 18 Jan 2016 12:05:05 +0100 Subject: [PATCH 15/47] more unicode fixes: os.path functions use unicode depending on the OS --- resources/lib/artwork.py | 5 ++++- resources/lib/clientinfo.py | 5 ++++- resources/lib/downloadutils.py | 2 +- resources/lib/entrypoint.py | 5 ++++- resources/lib/itemtypes.py | 5 +++-- resources/lib/kodimonitor.py | 2 +- resources/lib/musicutils.py | 19 +++++++++++++------ 7 files changed, 30 insertions(+), 13 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index ef562af0..6d42a45f 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -185,7 +185,10 @@ class Artwork(): for dir in allDirs: allDirs, allFiles = xbmcvfs.listdir(path+dir) for file in allFiles: - xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8'))) + if os.path.supports_unicode_filenames: + xbmcvfs.delete(os.path.join(path+dir.decode('utf-8'),file.decode('utf-8'))) + else: + xbmcvfs.delete(os.path.join(path.encode('utf-8')+dir,file)) # remove all existing data from texture DB textureconnection = utils.kodiSQL('texture') diff --git a/resources/lib/clientinfo.py b/resources/lib/clientinfo.py index b86c8a7b..44ed98f2 100644 --- a/resources/lib/clientinfo.py +++ b/resources/lib/clientinfo.py @@ -76,7 +76,10 @@ class ClientInfo(): return clientId addon_path = self.addon.getAddonInfo('path').decode('utf-8') - GUID_file = xbmc.translatePath(os.path.join(addon_path, "machine_guid")).decode('utf-8') + if os.path.supports_unicode_filenames: + GUID_file = xbmc.translatePath(os.path.join(addon_path, "machine_guid")).decode('utf-8') + else: + GUID_file = xbmc.translatePath(os.path.join(addon_path.encode("utf-8"), "machine_guid")).decode('utf-8') try: GUID = xbmcvfs.File(GUID_file) diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index 5c47144d..1c244e74 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -326,7 +326,7 @@ class DownloadUtils(): elif r.status_code == requests.codes.ok: try: - # UTF-8 - JSON object + # UNICODE - JSON object r = r.json() self.logMsg("====== 200 Success ======", 2) self.logMsg("Response: %s" % r, 2) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 6c323f0f..e901df18 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -990,7 +990,10 @@ def getExtraFanArt(): for backdrop in backdrops: # Same ordering as in artwork tag = tags[count] - fanartFile = os.path.join(fanartDir, "fanart%s.jpg" % tag) + if os.path.supports_unicode_filenames: + fanartFile = os.path.join(fanartDir, "fanart%s.jpg" % tag) + else: + fanartFile = os.path.join(fanartDir.encode("utf-8"), "fanart%s.jpg" % tag.encode("utf-8")) li = xbmcgui.ListItem(tag, path=fanartFile) xbmcplugin.addDirectoryItem( handle=int(sys.argv[1]), diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 92a94a25..7559d28c 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -2208,8 +2208,10 @@ class Music(Items): filename = API.getFilePath() rating = 0 emby_rating = int(round(emby_rating, 0)) - file_rating, comment = musicutils.getSongTags(filename) + #get file rating and comment tag from file itself. + #TODO: should we make this an optional setting if it impacts sync speed too much ? + file_rating, comment = musicutils.getSongTags(filename) emby_dbitem = self.emby_db.getItem_byId(embyid) try: @@ -2228,7 +2230,6 @@ class Music(Items): elif file_rating is None and not currentvalue: return (emby_rating, comment) - file_rating = int(round(file_rating,0)) self.logMsg("getSongRatingAndComment --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue)) updateFileRating = False diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index 80e664f6..e4f25a6f 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -72,7 +72,7 @@ class KodiMonitor(xbmc.Monitor): self.logMsg("Method: %s Data: %s" % (method, data), 1) if data: - data = json.loads(data) + data = json.loads(data,'utf-8') if method == "Player.OnPlay": diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index f807fe57..c77f75ee 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -19,15 +19,21 @@ def logMsg(msg, lvl=1): def getRealFileName(filename): #get the filename path accessible by python if possible... isTemp = False - + if not xbmcvfs.exists(filename): logMsg( "File does not exist! %s" %(filename), 0) return (False, "") + #if we use os.path method on older python versions (sunch as some android builds), we need to pass arguments as string + if os.path.supports_unicode_filenames: + checkfile = filename + else: + checkfile = file.encode("utf-8") + # determine if our python module is able to access the file directly... - if os.path.exists(filename): + if os.path.exists(checkfile): filename = filename - elif os.path.exists(filename.replace("smb://","\\\\").replace("/","\\")): + elif os.path.exists(checkfile.replace("smb://","\\\\").replace("/","\\")): filename = filename.replace("smb://","\\\\").replace("/","\\") else: #file can not be accessed by python directly, we copy it for processing... @@ -57,7 +63,7 @@ def getEmbyRatingFromKodiRating(rating): def getSongTags(file): # Get the actual ID3 tags for music songs as the server is lacking that info - rating = None + rating = 0 comment = "" isTemp,filename = getRealFileName(file) @@ -84,12 +90,13 @@ def getSongTags(file): else: logMsg( "Not supported fileformat or unable to access file: %s" %(filename)) - if rating: - rating = int(round(rating,0)) + #the rating must be a round value + rating = int(round(rating,0)) except Exception as e: #file in use ? logMsg("Exception in getSongTags %s" %e,0) + return (None,"") #remove tempfile if needed.... if isTemp: xbmcvfs.delete(filename) From 5640408523b497148371987038af19ff58153a4d Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Mon, 18 Jan 2016 16:31:07 +0100 Subject: [PATCH 16/47] fix crash on sync when song rating is null in db (if user didn't reset the db) resync is advised however to make sure the rating gets scraped initially --- resources/lib/itemtypes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 7559d28c..dbe2664f 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -2222,7 +2222,9 @@ class Music(Items): else: query = "SELECT rating FROM song WHERE idSong = ?" kodicursor.execute(query, (kodiid,)) - currentvalue = int(round(float(kodicursor.fetchone()[0]),0)) + try: + currentvalue = int(round(float(kodicursor.fetchone()[0]),0)) + except: currentvalue = None # Only proceed if we actually have a rating from the file if file_rating is None and currentvalue: From eab0b7d3d912ed4d45f29eea51120973ec1e99df Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Mon, 18 Jan 2016 17:12:57 +0100 Subject: [PATCH 17/47] remove widgetreload. Is entirely handled by skinhelper now. leaving it in means doubled widget refreshes = slower --- resources/lib/librarysync.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index c89c32c0..c1e39b15 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -287,8 +287,6 @@ class LibrarySync(threading.Thread): utils.settings('SyncInstallRunDone', value="true") utils.settings("dbCreatedWithVersion", self.clientInfo.getVersion()) self.saveLastSync() - # tell any widgets to refresh because the content has changed - utils.window('widgetreload', value=datetime.now().strftime('%Y-%m-%d %H:%M:%S')) xbmc.executebuiltin('UpdateLibrary(video)') elapsedtotal = datetime.now() - starttotal @@ -1088,9 +1086,6 @@ class LibrarySync(threading.Thread): embyconn.commit() self.saveLastSync() - # tell any widgets to refresh because the content has changed - utils.window('widgetreload', value=datetime.now().strftime('%Y-%m-%d %H:%M:%S')) - self.logMsg("Updating video library.", 1) utils.window('emby_kodiScan', value="true") xbmc.executebuiltin('UpdateLibrary(video)') From 3421bf88e14ec8f50d6b073db59946100a9ea908 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Mon, 18 Jan 2016 19:38:53 +0100 Subject: [PATCH 18/47] add support for embedded covertart in music files fix typo in music tags code --- addon.xml | 2 +- resources/language/English/strings.xml | 3 +-- resources/lib/itemtypes.py | 17 ++++++++++------- resources/lib/musicutils.py | 24 +++++++++++++++++++----- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/addon.xml b/addon.xml index 275f28d3..6fbb95f8 100644 --- a/addon.xml +++ b/addon.xml @@ -20,7 +20,7 @@ Settings for the Emby Server - [!IsEmpty(ListItem.DBID) + !IsEmpty(ListItem.DBTYPE)] | !IsEmpty(ListItem.Property(embyid)) + [!IsEmpty(ListItem.DBID) + !StringCompare(ListItem.DBID,-1)] | !IsEmpty(ListItem.Property(embyid)) diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index abcf07a5..9ed805d1 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -272,7 +272,6 @@ Remove from Emby favorites Set custom song rating Emby addon settings - Delete item from the server - + Delete item from the server diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index dbe2664f..ec6a8150 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -1954,7 +1954,7 @@ class Music(Items): #if enabled, try to get the rating and comment value from the file itself if not self.directstream: - rating, comment = self.getSongRatingAndComment(itemid, rating, API) + rating, comment, hasEmbeddedCover = self.getSongTagsFromFile(itemid, rating, API) ##### GET THE FILE AND PATH ##### if self.directstream: @@ -2158,7 +2158,10 @@ class Music(Items): # Add genres kodi_db.addMusicGenres(songid, genres, "song") # Update artwork - artwork.addArtwork(artwork.getAllArtwork(item, parentInfo=True), songid, "song", kodicursor) + allart = artwork.getAllArtwork(item, parentInfo=True) + if hasEmbeddedCover: + allart["Primary"] = "image://music@" + artwork.single_urlencode( playurl ) + artwork.addArtwork(allart, songid, "song", kodicursor) def updateUserdata(self, item): # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks @@ -2188,7 +2191,7 @@ class Music(Items): # Process playstates playcount = userdata['PlayCount'] dateplayed = userdata['LastPlayedDate'] - rating, comment = self.getSongRatingAndComment(itemid, rating, API) + rating, comment, hasEmbeddedCover = self.getSongTagsFromFile(itemid, rating, API) query = "UPDATE song SET iTimesPlayed = ?, lastplayed = ?, rating = ? WHERE idSong = ?" kodicursor.execute(query, (playcount, dateplayed, rating, kodiid)) @@ -2200,7 +2203,7 @@ class Music(Items): emby_db.updateReference(itemid, checksum) - def getSongRatingAndComment(self, embyid, emby_rating, API): + def getSongTagsFromFile(self, embyid, emby_rating, API): kodicursor = self.kodicursor @@ -2211,7 +2214,7 @@ class Music(Items): #get file rating and comment tag from file itself. #TODO: should we make this an optional setting if it impacts sync speed too much ? - file_rating, comment = musicutils.getSongTags(filename) + file_rating, comment, hasEmbeddedCover = musicutils.getSongTags(filename) emby_dbitem = self.emby_db.getItem_byId(embyid) try: @@ -2232,7 +2235,7 @@ class Music(Items): elif file_rating is None and not currentvalue: return (emby_rating, comment) - self.logMsg("getSongRatingAndComment --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue)) + self.logMsg("getSongTagsFromFile --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue)) updateFileRating = False updateEmbyRating = False @@ -2281,7 +2284,7 @@ class Music(Items): like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(rating) API.updateUserRating(embyid, like, favourite, deletelike) - return (rating, comment) + return (rating, comment, hasEmbeddedCover) def remove(self, itemid): # Remove kodiid, fileid, pathid, emby reference diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index c77f75ee..f388b3c3 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -5,9 +5,10 @@ import os import xbmc, xbmcaddon, xbmcvfs import utils -from mutagen.flac import FLAC +from mutagen.flac import FLAC, Picture from mutagen.id3 import ID3 from mutagen import id3 +import base64 ################################################################################################# @@ -28,7 +29,7 @@ def getRealFileName(filename): if os.path.supports_unicode_filenames: checkfile = filename else: - checkfile = file.encode("utf-8") + checkfile = filename.encode("utf-8") # determine if our python module is able to access the file directly... if os.path.exists(checkfile): @@ -65,21 +66,34 @@ def getSongTags(file): # Get the actual ID3 tags for music songs as the server is lacking that info rating = 0 comment = "" + hasEmbeddedCover = False isTemp,filename = getRealFileName(file) - logMsg( "getting song ID3 tags for " + filename) + logMsg( "getting song ID3 tags for " + filename,0) try: + ###### FLAC FILES ############# if filename.lower().endswith(".flac"): audio = FLAC(filename) if audio.get("comment"): comment = audio.get("comment")[0] + for pic in audio.pictures: + if pic.type == 3 and pic.data: + #the file has an embedded cover + hasEmbeddedCover = True if audio.get("rating"): rating = float(audio.get("rating")[0]) #flac rating is 0-100 and needs to be converted to 0-5 range if rating > 5: rating = (rating / 100) * 5 + + ###### MP3 FILES ############# elif filename.lower().endswith(".mp3"): audio = ID3(filename) + + if audio.get("APIC:Front Cover"): + if audio.get("APIC:Front Cover").data: + hasEmbeddedCover = True + if audio.get("comment"): comment = audio.get("comment")[0] if audio.get("POPM:Windows Media Player 9 Series"): @@ -96,12 +110,12 @@ def getSongTags(file): except Exception as e: #file in use ? logMsg("Exception in getSongTags %s" %e,0) - return (None,"") + rating = None #remove tempfile if needed.... if isTemp: xbmcvfs.delete(filename) - return (rating, comment) + return (rating, comment, hasEmbeddedCover) def updateRatingToFile(rating, file): #update the rating from Emby to the file From 58533e1c4414de0c8eccab58a83d3200b0dab8c6 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Mon, 18 Jan 2016 20:17:14 +0100 Subject: [PATCH 19/47] fix videonodes and playlists --- resources/language/English/strings.xml | 4 ++++ resources/lib/librarysync.py | 10 +++++----- resources/lib/musicutils.py | 2 +- resources/lib/videonodes.py | 7 +++++++ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 9ed805d1..5a458fef 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -248,6 +248,10 @@ Favourite Photos Favourite Albums + Recently added Music videos + In progress Music videos + Unwatched Music videos + Active Clear Settings diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index c1e39b15..be3dc9a3 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -373,12 +373,12 @@ class LibrarySync(threading.Thread): self.logMsg("Creating viewid: %s in Emby database." % folderid, 1) tagid = kodi_db.createTag(foldername) # Create playlist for the video library - if mediatype != "music": + if mediatype in ['movies', 'tvshows', 'musicvideos']: utils.playlistXSP(mediatype, foldername, viewtype) - # Create the video node - if mediatype != "musicvideos": - vnodes.viewNode(totalnodes, foldername, mediatype, viewtype) - totalnodes += 1 + # Create the video node + if mediatype in ['movies', 'tvshows', 'musicvideos', 'homevideos']: + vnodes.viewNode(totalnodes, foldername, mediatype, viewtype) + totalnodes += 1 # Add view to emby database emby_db.addView(folderid, foldername, viewtype, tagid) diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index f388b3c3..2625cc7f 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -69,7 +69,7 @@ def getSongTags(file): hasEmbeddedCover = False isTemp,filename = getRealFileName(file) - logMsg( "getting song ID3 tags for " + filename,0) + logMsg( "getting song ID3 tags for " + filename) try: ###### FLAC FILES ############# diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py index c1d42df1..bc8f1eb3 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -163,6 +163,13 @@ class VideoNodes(object): '8': 30255, '11': 30254 }, + 'musicvideos': + { + '1': tagname, + '2': 30256, + '4': 30257, + '6': 30258 + }, } nodes = mediatypes[mediatype] From 2661c54938e7ad686fe3f2f4e418b0ccc7fd84c7 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Mon, 18 Jan 2016 20:48:44 +0100 Subject: [PATCH 20/47] fix entrypoints --- default.py | 1 + resources/lib/entrypoint.py | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/default.py b/default.py index c482c918..c5588861 100644 --- a/default.py +++ b/default.py @@ -35,6 +35,7 @@ class Main: # Parse parameters base_url = sys.argv[0] addon_handle = int(sys.argv[1]) + print sys.argv[2] params = urlparse.parse_qs(sys.argv[2][1:]) xbmc.log("Parameter string: %s" % sys.argv[2]) try: diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index e901df18..391cb7ac 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -69,10 +69,12 @@ def doMainListing(): label = utils.window('Emby.nodes.%s.title' % i) type = utils.window('Emby.nodes.%s.type' % i) #because we do not use seperate entrypoints for each content type, we need to figure out which items to show in each listing. - #if skinshortcuts is active, we just show all nodes - if path and ((xbmc.getCondVisibility("Window.IsActive(Pictures)") and type=="photos") - or (xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and type != "photos") - or xbmc.getCondVisibility("Window.IsActive(script-skinshortcuts.xml)")): + #for now we just only show picture nodes in the picture librarym video nodes in the video library and all nodes in any other window + if path and xbmc.getCondVisibility("Window.IsActive(Pictures)") and type == "photos": + addDirectoryItem(label, path) + elif path and xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and type != "photos": + addDirectoryItem(label, path) + elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"): addDirectoryItem(label, path) # some extra entries for settings and stuff. TODO --> localize the labels From dbc91330ce2bb1e42b0021c6d27486281a6f2b0e Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Mon, 18 Jan 2016 20:49:03 +0100 Subject: [PATCH 21/47] remove redundant logging --- default.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/default.py b/default.py index c5588861..fadaa05a 100644 --- a/default.py +++ b/default.py @@ -34,8 +34,6 @@ class Main: # Parse parameters base_url = sys.argv[0] - addon_handle = int(sys.argv[1]) - print sys.argv[2] params = urlparse.parse_qs(sys.argv[2][1:]) xbmc.log("Parameter string: %s" % sys.argv[2]) try: From 11cf78de361caeeb096b8ec829d81d9e8b244269 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Mon, 18 Jan 2016 20:56:10 +0100 Subject: [PATCH 22/47] fix unicode error in urlencode --- resources/lib/artwork.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 6d42a45f..25860bb4 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -55,10 +55,11 @@ class Artwork(): return text def single_urlencode(self, text): - text = urllib.urlencode({'blahblahblah':text}) + + text = urllib.urlencode({'blahblahblah':text.encode("utf-8")}) #urlencode needs a utf- string text = text[13:] - return text + return text.decode("utf-8") #return the result again as unicode def setKodiWebServerDetails(self): # Get the Kodi webserver details - used to set the texture cache From 6f72dfffe919a0807326bac340ed46add9fec1da Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Mon, 18 Jan 2016 21:16:11 +0100 Subject: [PATCH 23/47] one more fix for the musictags --- resources/lib/itemtypes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index ec6a8150..6bcdc2b2 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -2231,9 +2231,9 @@ class Music(Items): # Only proceed if we actually have a rating from the file if file_rating is None and currentvalue: - return (currentvalue, comment) + return (currentvalue, comment, False) elif file_rating is None and not currentvalue: - return (emby_rating, comment) + return (emby_rating, comment, False) self.logMsg("getSongTagsFromFile --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue)) From ad6a4b2dd5f40ea402d198a3a84bfd8c1114f885 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Mon, 18 Jan 2016 23:17:24 +0100 Subject: [PATCH 24/47] version bump for new beta --- addon.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 6fbb95f8..d749aab9 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ From 60f68610f496f8bf3fca92cc410c60c124642d70 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 18 Jan 2016 17:47:12 -0600 Subject: [PATCH 25/47] Close the kodi cursor earlier In case music doesn't complete correctly. --- resources/lib/librarysync.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index be3dc9a3..07d1c218 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -251,6 +251,9 @@ class LibrarySync(threading.Thread): self.logMsg( "SyncDatabase (finished %s in: %s)" % (itemtype, str(elapsedTime).split('.')[0]), 1) + else: + # Close the Kodi cursor + kodicursor.close() # sync music if music_enabled: @@ -282,7 +285,6 @@ class LibrarySync(threading.Thread): pDialog.close() embycursor.close() - kodicursor.close() utils.settings('SyncInstallRunDone', value="true") utils.settings("dbCreatedWithVersion", self.clientInfo.getVersion()) From f3f8c76197173e6f6ecf22642c5bfb61457a537a Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 18 Jan 2016 19:54:20 -0600 Subject: [PATCH 26/47] Fix progress dialog --- resources/lib/kodimonitor.py | 3 +-- resources/lib/playbackutils.py | 10 +++++----- resources/lib/player.py | 2 +- resources/lib/playlist.py | 2 +- service.py | 2 +- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index e4f25a6f..aa9aa79c 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -200,6 +200,5 @@ class KodiMonitor(xbmc.Monitor): utils.window('emby_onWake', value="true") elif method == "Playlist.OnClear": - utils.window('emby_customPlaylist', clear=True, windowid=10101) - #xbmcgui.Window(10101).clearProperties() + utils.window('emby_customPlaylist', clear=True, windowid=10008) self.logMsg("Clear playlist properties.") \ No newline at end of file diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index affa2b81..777fe9e6 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -74,7 +74,7 @@ class PlaybackUtils(): sizePlaylist = playlist.size() currentPosition = startPos - propertiesPlayback = utils.window('emby_playbackProps', windowid=10101) == "true" + propertiesPlayback = utils.window('emby_playbackProps', windowid=10008) == "true" introsPlaylist = False dummyPlaylist = False @@ -91,11 +91,11 @@ class PlaybackUtils(): # Otherwise we get a loop. if not propertiesPlayback: - utils.window('emby_playbackProps', value="true", windowid=10101) + utils.window('emby_playbackProps', value="true", windowid=10008) self.logMsg("Setting up properties in playlist.", 1) if (not homeScreen and not seektime and - utils.window('emby_customPlaylist', windowid=10101) != "true"): + utils.window('emby_customPlaylist', windowid=10008) != "true"): self.logMsg("Adding dummy file to playlist.", 2) dummyPlaylist = True @@ -182,7 +182,7 @@ class PlaybackUtils(): # 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, windowid=10101) + utils.window('emby_playbackProps', clear=True, windowid=10008) #self.pl.verifyPlaylist() ########## SETUP MAIN ITEM ########## @@ -202,7 +202,7 @@ class PlaybackUtils(): self.setListItem(listitem) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) - elif ((introsPlaylist and utils.window('emby_customPlaylist', windowid=10101) == "true") or + elif ((introsPlaylist and utils.window('emby_customPlaylist', windowid=10008) == "true") or (homeScreen and not sizePlaylist)): # Playlist was created just now, play it. self.logMsg("Play playlist.", 1) diff --git a/resources/lib/player.py b/resources/lib/player.py index b6d25e19..1323548b 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -400,7 +400,7 @@ class Player(xbmc.Player): def onPlayBackStopped( self ): # Will be called when user stops xbmc playing a file self.logMsg("ONPLAYBACK_STOPPED", 2) - xbmcgui.Window(10101).clearProperties() + xbmcgui.Window(10008).clearProperties() self.logMsg("Clear playlist properties.") self.stopAll() diff --git a/resources/lib/playlist.py b/resources/lib/playlist.py index 03d07d39..9d2d9a95 100644 --- a/resources/lib/playlist.py +++ b/resources/lib/playlist.py @@ -51,7 +51,7 @@ class Playlist(): playlist.clear() started = False - utils.window('emby_customplaylist', value="true", windowid=10101) + utils.window('emby_customplaylist', value="true", windowid=10008) position = 0 diff --git a/service.py b/service.py index cfd36fdb..4c65cce5 100644 --- a/service.py +++ b/service.py @@ -77,7 +77,7 @@ class Service(): utils.window(prop, clear=True) # Clear playlist properties - xbmcgui.Window(10101).clearProperties() + xbmcgui.Window(10008).clearProperties() # Clear video nodes properties videonodes.VideoNodes().clearProperties() From d14ecfdc5e12e44b03f4611fbd7aec3133158e1c Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Mon, 18 Jan 2016 21:54:02 -0600 Subject: [PATCH 27/47] Adjust playlist props Instead of using clearProperties, clear individually. --- resources/lib/kodimonitor.py | 2 +- resources/lib/playbackutils.py | 10 +++++----- resources/lib/player.py | 5 +++-- resources/lib/playlist.py | 2 +- service.py | 4 +--- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index aa9aa79c..05ecf8a5 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -200,5 +200,5 @@ class KodiMonitor(xbmc.Monitor): utils.window('emby_onWake', value="true") elif method == "Playlist.OnClear": - utils.window('emby_customPlaylist', clear=True, windowid=10008) + utils.window('emby_customPlaylist', clear=True) self.logMsg("Clear playlist properties.") \ No newline at end of file diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index 777fe9e6..4ab5ba0f 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -74,7 +74,7 @@ class PlaybackUtils(): sizePlaylist = playlist.size() currentPosition = startPos - propertiesPlayback = utils.window('emby_playbackProps', windowid=10008) == "true" + propertiesPlayback = utils.window('emby_playbackProps') == "true" introsPlaylist = False dummyPlaylist = False @@ -91,11 +91,11 @@ class PlaybackUtils(): # Otherwise we get a loop. if not propertiesPlayback: - utils.window('emby_playbackProps', value="true", windowid=10008) + utils.window('emby_playbackProps', value="true") self.logMsg("Setting up properties in playlist.", 1) if (not homeScreen and not seektime and - utils.window('emby_customPlaylist', windowid=10008) != "true"): + utils.window('emby_customPlaylist') != "true"): self.logMsg("Adding dummy file to playlist.", 2) dummyPlaylist = True @@ -182,7 +182,7 @@ class PlaybackUtils(): # 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, windowid=10008) + utils.window('emby_playbackProps', clear=True) #self.pl.verifyPlaylist() ########## SETUP MAIN ITEM ########## @@ -202,7 +202,7 @@ class PlaybackUtils(): self.setListItem(listitem) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) - elif ((introsPlaylist and utils.window('emby_customPlaylist', windowid=10008) == "true") or + elif ((introsPlaylist and utils.window('emby_customPlaylist') == "true") or (homeScreen and not sizePlaylist)): # Playlist was created just now, play it. self.logMsg("Play playlist.", 1) diff --git a/resources/lib/player.py b/resources/lib/player.py index 1323548b..7972edec 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -400,8 +400,9 @@ class Player(xbmc.Player): def onPlayBackStopped( self ): # Will be called when user stops xbmc playing a file self.logMsg("ONPLAYBACK_STOPPED", 2) - xbmcgui.Window(10008).clearProperties() - self.logMsg("Clear playlist properties.") + utils.window('emby_customPlaylist', clear=True) + utils.window('emby_playbackProps', clear=True) + self.logMsg("Clear playlist properties.", 1) self.stopAll() def onPlayBackEnded( self ): diff --git a/resources/lib/playlist.py b/resources/lib/playlist.py index 9d2d9a95..c869f288 100644 --- a/resources/lib/playlist.py +++ b/resources/lib/playlist.py @@ -51,7 +51,7 @@ class Playlist(): playlist.clear() started = False - utils.window('emby_customplaylist', value="true", windowid=10008) + utils.window('emby_customplaylist', value="true") position = 0 diff --git a/service.py b/service.py index 4c65cce5..b68145f2 100644 --- a/service.py +++ b/service.py @@ -71,13 +71,11 @@ class Service(): "emby_online", "emby_serverStatus", "emby_onWake", "emby_syncRunning", "emby_dbCheck", "emby_kodiScan", "emby_shouldStop", "emby_currUser", "emby_dbScan", "emby_sessionId", - "emby_initialScan" + "emby_initialScan", "emby_customplaylist", "emby_playbackProps" ] for prop in properties: utils.window(prop, clear=True) - # Clear playlist properties - xbmcgui.Window(10008).clearProperties() # Clear video nodes properties videonodes.VideoNodes().clearProperties() From bfb893ad8e132c9b2dc2a2a7fae1d227667d58f8 Mon Sep 17 00:00:00 2001 From: Shaun Date: Tue, 19 Jan 2016 18:58:42 +1100 Subject: [PATCH 28/47] use standard progress dialog for progress and add cancel --- resources/lib/artwork.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 25860bb4..fd746a55 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -170,10 +170,9 @@ class Artwork(): if not xbmcgui.Dialog().yesno("Image Texture Cache", "Running the image cache process can take some time.", "Are you sure you want continue?"): return - self.logMsg("Doing Image Cache Sync", 1) - dialog = xbmcgui.DialogProgressBG() + dialog = xbmcgui.DialogProgress() dialog.create("Emby for Kodi", "Image Cache Sync") # ask to rest all existing or not @@ -213,9 +212,11 @@ class Artwork(): percentage = 0 self.logMsg("Image cache sync about to process " + str(total) + " images", 1) for url in result: + if dialog.iscanceled(): + break percentage = int((float(count) / float(total))*100) textMessage = str(count) + " of " + str(total) + " (" + str(len(self.imageCacheThreads)) + ")" - dialog.update(percentage, message="Updating Image Cache: " + textMessage) + dialog.update(percentage, "Updating Image Cache: " + textMessage) self.CacheTexture(url[0]) count += 1 cursor.close() @@ -230,20 +231,22 @@ class Artwork(): percentage = 0 self.logMsg("Image cache sync about to process " + str(total) + " images", 1) for url in result: + if dialog.iscanceled(): + break percentage = int((float(count) / float(total))*100) textMessage = str(count) + " of " + str(total) - dialog.update(percentage, message="Updating Image Cache: " + textMessage) + dialog.update(percentage, "Updating Image Cache: " + textMessage) self.CacheTexture(url[0]) count += 1 cursor.close() - dialog.update(100, message="Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) + dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) self.logMsg("Waiting for all threads to exit", 1) while len(self.imageCacheThreads) > 0: for thread in self.imageCacheThreads: if thread.isFinished: self.imageCacheThreads.remove(thread) - dialog.update(100, message="Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) + dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) self.logMsg("Waiting for all threads to exit: " + str(len(self.imageCacheThreads)), 1) xbmc.sleep(500) From 81dcd36dc3117ae7e7383c6b1a151c4f18151f1e Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Tue, 19 Jan 2016 04:28:52 -0600 Subject: [PATCH 29/47] Revert throttle Will have to test further with timeout scenario, before reimplementing --- resources/lib/read_embyserver.py | 36 +++++++++++--------------------- 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index 323745d4..d37d4fbf 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -210,18 +210,12 @@ class Read_EmbyServer(): "MediaSources" ) result = doUtils.downloadUrl(url, parameters=params) - try: - items['Items'].extend(result['Items']) - except TypeError: - # Connection timed out, reduce the number - jump -= 50 - self.limitindex = jump - self.logMsg("New throttle for items requested: %s" % jump, 1) - else: - index += jump - if dialog: - percentage = int((float(index) / float(total))*100) - dialog.update(percentage) + items['Items'].extend(result['Items']) + + index += jump + if dialog: + percentage = int((float(index) / float(total))*100) + dialog.update(percentage) return items def getViews(self, type, root=False): @@ -399,18 +393,12 @@ class Read_EmbyServer(): ) } result = doUtils.downloadUrl(url, parameters=params) - try: - items['Items'].extend(result['Items']) - except TypeError: - # Connection timed out, reduce the number - jump -= 50 - self.limitindex = jump - self.logMsg("New throttle for items requested: %s" % jump, 1) - else: - index += jump - if dialog: - percentage = int((float(index) / float(total))*100) - dialog.update(percentage) + items['Items'].extend(result['Items']) + + index += jump + if dialog: + percentage = int((float(index) / float(total))*100) + dialog.update(percentage) return items def getAlbums(self, basic=False, dialog=None): From 7cf615f6715cccb415ebf6dcf051396d9457a316 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Tue, 19 Jan 2016 21:27:58 +0100 Subject: [PATCH 30/47] simplify channel browse code add experimental support for live tv channels and recordings (without pvr) --- resources/lib/artwork.py | 2 +- resources/lib/entrypoint.py | 112 +++++++++---------------------- resources/lib/playutils.py | 10 ++- resources/lib/read_embyserver.py | 30 +++++++++ 4 files changed, 73 insertions(+), 81 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 25860bb4..b1d408e6 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -506,7 +506,7 @@ class Artwork(): id = item['Id'] artworks = item['ImageTags'] - backdrops = item['BackdropImageTags'] + backdrops = item.get('BackdropImageTags',[]) maxHeight = 10000 maxWidth = 10000 diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 391cb7ac..1240e738 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -76,7 +76,11 @@ def doMainListing(): addDirectoryItem(label, path) elif path and not xbmc.getCondVisibility("Window.IsActive(VideoLibrary) | Window.IsActive(Pictures) | Window.IsActive(MusicLibrary)"): addDirectoryItem(label, path) - + + #experimental live tv nodes + addDirectoryItem("Live Tv Channels (experimental)", "plugin://plugin.video.emby/?mode=browsecontent&type=tvchannels&folderid=root") + addDirectoryItem("Live Tv Recordings (experimental)", "plugin://plugin.video.emby/?mode=browsecontent&type=recordings&folderid=root") + # some extra entries for settings and stuff. TODO --> localize the labels addDirectoryItem("Network credentials", "plugin://plugin.video.emby/?mode=passwords") addDirectoryItem("Settings", "plugin://plugin.video.emby/?mode=settings") @@ -395,10 +399,12 @@ def refreshPlaylist(): time=1000, sound=False) -##### BROWSE EMBY HOMEVIDEOS AND PICTURES ##### +##### BROWSE EMBY NODES DIRECTLY ##### def BrowseContent(viewname, type="", folderid=None, filter=""): emby = embyserver.Read_EmbyServer() + art = artwork.Artwork() + doUtils = downloadutils.DownloadUtils() utils.logMsg("BrowseHomeVideos","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname, type, folderid, filter)) xbmcplugin.setPluginCategory(int(sys.argv[1]), viewname) #get views for root level @@ -421,7 +427,11 @@ def BrowseContent(viewname, type="", folderid=None, filter=""): itemtype = "" #get the actual listing - if filter == "recent": + if type == "recordings": + listing = emby.getTvRecordings(folderid) + elif type == "tvchannels": + listing = emby.getTvChannels() + elif filter == "recent": listing = emby.getFilteredSection("", itemtype=itemtype.split(",")[0], sortby="DateCreated", recursive=True, limit=25, sortorder="Descending") elif filter == "random": listing = emby.getFilteredSection("", itemtype=itemtype.split(",")[0], sortby="Random", recursive=True, limit=150, sortorder="Descending") @@ -435,7 +445,7 @@ def BrowseContent(viewname, type="", folderid=None, filter=""): #process the listing if listing: for item in listing.get("Items"): - li = createListItemFromEmbyItem(item) + li = createListItemFromEmbyItem(item,art,doUtils) if item.get("IsFolder") == True: #for folders we add an additional browse request, passing the folderId path = "%s?id=%s&mode=browsecontent&type=%s&folderid=%s" % (sys.argv[0], viewname, type, item.get("Id")) @@ -455,10 +465,8 @@ def BrowseContent(viewname, type="", folderid=None, filter=""): xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_RUNTIME) ##### CREATE LISTITEM FROM EMBY METADATA ##### -def createListItemFromEmbyItem(item): +def createListItemFromEmbyItem(item,art=artwork.Artwork(),doUtils=downloadutils.DownloadUtils()): API = api.API(item) - art = artwork.Artwork() - doUtils = downloadutils.DownloadUtils() itemid = item['Id'] title = item.get('Name') @@ -495,10 +503,11 @@ def createListItemFromEmbyItem(item): genre = API.getGenres() overlay = 0 userdata = API.getUserData() + runtime = item.get("RunTimeTicks",0)/ 10000000.0 seektime = userdata['Resume'] if seektime: li.setProperty("resumetime", seektime) - li.setProperty("totaltime", item.get("RunTimeTicks")/ 10000000.0) + li.setProperty("totaltime", str(runtime)) played = userdata['Played'] if played: overlay = 7 @@ -515,26 +524,35 @@ def createListItemFromEmbyItem(item): 'id': itemid, 'rating': rating, 'year': item.get('ProductionYear'), - 'premieredate': premieredate, - 'date': premieredate, 'genre': genre, 'playcount': str(playcount), 'title': title, 'plot': API.getOverview(), 'Overlay': str(overlay), + 'duration': runtime } + if premieredate: + extradata["premieredate"] = premieredate + extradata["date"] = premieredate li.setInfo('video', infoLabels=extradata) - li.setThumbnailImage(allart.get('Primary')) + if allart.get('Primary'): + li.setThumbnailImage(allart.get('Primary')) + else: li.setThumbnailImage('DefaultTVShows.png') li.setIconImage('DefaultTVShows.png') if not allart.get('Background'): #add image as fanart for use with skinhelper auto thumb/backgrund creation li.setArt( {"fanart": allart.get('Primary') } ) else: pbutils.PlaybackUtils(item).setArtwork(li) - + mediastreams = API.getMediaStreams() + videostreamFound = False if mediastreams: for key, value in mediastreams.iteritems(): + if key == "video" and value: videostreamFound = True if value: li.addStreamInfo(key, value[0]) + if not videostreamFound: + #just set empty streamdetails to prevent errors in the logs + li.addStreamInfo("video", {'duration': runtime}) return li @@ -558,84 +576,20 @@ def BrowseChannels(itemid, folderid=None): url = "{server}/emby/Channels/%s/Items?UserId={UserId}&format=json" % itemid result = doUtils.downloadUrl(url) - try: - channels = result['Items'] - except TypeError: - pass - else: - for item in channels: - - API = api.API(item) + if result and result.get("Items"): + for item in result.get("Items"): itemid = item['Id'] itemtype = item['Type'] - title = item.get('Name', "Missing Title") - li = xbmcgui.ListItem(title) - + li = createListItemFromEmbyItem(item,art,doUtils) if itemtype == "ChannelFolderItem": isFolder = True else: isFolder = False - channelId = item.get('ChannelId', "") channelName = item.get('ChannelName', "") - - premieredate = API.getPremiereDate() - # Process Genres - genre = API.getGenres() - # Process UserData - overlay = 0 - - userdata = API.getUserData() - seektime = userdata['Resume'] - played = userdata['Played'] - if played: - overlay = 7 - else: - overlay = 6 - - favorite = userdata['Favorite'] - if favorite: - overlay = 5 - - playcount = userdata['PlayCount'] - if playcount is None: - playcount = 0 - - # Populate the details list - details = { - - 'title': title, - 'channelname': channelName, - 'plot': API.getOverview(), - 'Overlay': str(overlay), - 'playcount': str(playcount) - } - - if itemtype == "ChannelVideoItem": - xbmcplugin.setContent(_addon_id, 'movies') - elif itemtype == "ChannelAudioItem": - xbmcplugin.setContent(_addon_id, 'songs') - - # Populate the extradata list and artwork - pbutils.PlaybackUtils(item).setArtwork(li) - extradata = { - - 'id': itemid, - 'rating': item.get('CommunityRating'), - 'year': item.get('ProductionYear'), - 'premieredate': premieredate, - 'genre': genre, - 'playcount': str(playcount), - 'itemtype': itemtype - } - li.setInfo('video', infoLabels=extradata) - li.setThumbnailImage(art.getAllArtwork(item)['Primary']) - li.setIconImage('DefaultTVShows.png') - if itemtype == "Channel": path = "%s?id=%s&mode=channels" % (_addon_url, itemid) xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True) - elif isFolder: path = "%s?id=%s&mode=channelsfolder&folderid=%s" % (_addon_url, channelId, itemid) xbmcplugin.addDirectoryItem(handle=_addon_id, url=path, listitem=li, isFolder=True) diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index cc04d3b2..443792de 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -34,9 +34,17 @@ class PlayUtils(): def getPlayUrl(self): item = self.item + print "playutilks" + print 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 ? + 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") - if item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http": + elif item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http": # Only play as http self.logMsg("File protocol is http.", 1) playurl = self.httpPlay() diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py index 323745d4..665972fc 100644 --- a/resources/lib/read_embyserver.py +++ b/resources/lib/read_embyserver.py @@ -149,6 +149,36 @@ class Read_EmbyServer(): } return doUtils.downloadUrl(url, parameters=params) + def getTvChannels(self): + doUtils = self.doUtils + url = "{server}/emby/LiveTv/Channels/?userid={UserId}&format=json" + params = { + + 'EnableImages': True, + 'Fields': ( "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," + "CommunityRating,OfficialRating,CumulativeRunTimeTicks," + "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," + "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," + "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers") + } + return doUtils.downloadUrl(url, parameters=params) + + def getTvRecordings(self, groupid): + doUtils = self.doUtils + url = "{server}/emby/LiveTv/Recordings/?userid={UserId}&format=json" + if groupid == "root": groupid = "" + params = { + + 'GroupId': groupid, + 'EnableImages': True, + 'Fields': ( "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," + "CommunityRating,OfficialRating,CumulativeRunTimeTicks," + "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," + "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," + "Tags,ProviderIds,ParentId,RemoteTrailers,SpecialEpisodeNumbers") + } + return doUtils.downloadUrl(url, parameters=params) + def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False, dialog=None): doUtils = self.doUtils From 2d7f8a5a0659e79bf18531d34de616a525298c15 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Tue, 19 Jan 2016 19:59:22 -0600 Subject: [PATCH 31/47] Version bump 1.1.78 --- addon.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index d749aab9..8cd6d717 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ From ae06548c9d4152ccf2876db90ec7e70fc21f6147 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Wed, 20 Jan 2016 20:21:56 +0100 Subject: [PATCH 32/47] fix: redundant userdata update for music rating add settings for music ratings import/export --- contextmenu.py | 8 +- resources/lib/downloadutils.py | 1 + resources/lib/itemtypes.py | 94 ++-------------------- resources/lib/musicutils.py | 141 +++++++++++++++++++++++++++++---- resources/settings.xml | 4 + 5 files changed, 142 insertions(+), 106 deletions(-) diff --git a/contextmenu.py b/contextmenu.py index e7bee464..ab557b9f 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -112,9 +112,11 @@ if __name__ == '__main__': if newvalue: newvalue = int(newvalue) if newvalue > 5: newvalue = "5" - musicutils.updateRatingToFile(newvalue, API.getFilePath()) - like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue) - API.updateUserRating(embyid, like, favourite, deletelike) + if utils.settings('enableUpdateSongRating') == "true": + musicutils.updateRatingToFile(newvalue, API.getFilePath()) + if utils.settings('enableExportSongRating') == "true": + like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(newvalue) + API.updateUserRating(embyid, like, favourite, deletelike) query = ' '.join(( "UPDATE song","SET rating = ?", "WHERE idSong = ?" )) kodicursor.execute(query, (newvalue,itemid,)) kodiconn.commit() diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index 1c244e74..578c932c 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -274,6 +274,7 @@ class DownloadUtils(): verify=verifyssl) elif type == "POST": + print url r = requests.post(url, json=postBody, headers=header, diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 6bcdc2b2..7397b9f6 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -38,7 +38,7 @@ class Items(object): self.directpath = utils.settings('useDirectPaths') == "1" self.music_enabled = utils.settings('enableMusic') == "true" self.contentmsg = utils.settings('newContent') == "true" - + self.artwork = artwork.Artwork() self.emby = embyserver.Read_EmbyServer() self.emby_db = embydb.Embydb_Functions(embycursor) @@ -1613,6 +1613,9 @@ class Music(Items): Items.__init__(self, embycursor, musiccursor) self.directstream = utils.settings('streamMusic') == "true" + self.enableimportsongrating = utils.settings('enableImportSongRating') == "true" + self.enableexportsongrating = utils.settings('enableExportSongRating') == "true" + self.enableupdatesongrating = utils.settings('enableUpdateSongRating') == "true" self.userid = utils.window('emby_currUser') self.server = utils.window('emby_server%s' % self.userid) @@ -1952,9 +1955,9 @@ class Music(Items): #the server doesn't support comment on songs so this will always be empty comment = API.getOverview() - #if enabled, try to get the rating and comment value from the file itself + #if enabled, try to get the rating from file and/or emby if not self.directstream: - rating, comment, hasEmbeddedCover = self.getSongTagsFromFile(itemid, rating, API) + rating, comment, hasEmbeddedCover = musicutils.getAdditionalSongTags(itemid, rating, API, kodicursor, emby_db, self.enableimportsongrating, self.enableexportsongrating, self.enableupdatesongrating) ##### GET THE FILE AND PATH ##### if self.directstream: @@ -2191,7 +2194,7 @@ class Music(Items): # Process playstates playcount = userdata['PlayCount'] dateplayed = userdata['LastPlayedDate'] - rating, comment, hasEmbeddedCover = self.getSongTagsFromFile(itemid, rating, API) + rating, comment, hasEmbeddedCover = musicutils.getAdditionalSongTags(itemid, rating, API, kodicursor, emby_db, self.enableimportsongrating, self.enableexportsongrating, self.enableupdatesongrating) query = "UPDATE song SET iTimesPlayed = ?, lastplayed = ?, rating = ? WHERE idSong = ?" kodicursor.execute(query, (playcount, dateplayed, rating, kodiid)) @@ -2203,89 +2206,6 @@ class Music(Items): emby_db.updateReference(itemid, checksum) - def getSongTagsFromFile(self, embyid, emby_rating, API): - - kodicursor = self.kodicursor - - previous_values = None - filename = API.getFilePath() - rating = 0 - emby_rating = int(round(emby_rating, 0)) - - #get file rating and comment tag from file itself. - #TODO: should we make this an optional setting if it impacts sync speed too much ? - file_rating, comment, hasEmbeddedCover = musicutils.getSongTags(filename) - - emby_dbitem = self.emby_db.getItem_byId(embyid) - try: - kodiid = emby_dbitem[0] - except TypeError: - # Item is not in database. - currentvalue = None - else: - query = "SELECT rating FROM song WHERE idSong = ?" - kodicursor.execute(query, (kodiid,)) - try: - currentvalue = int(round(float(kodicursor.fetchone()[0]),0)) - except: currentvalue = None - - # Only proceed if we actually have a rating from the file - if file_rating is None and currentvalue: - return (currentvalue, comment, False) - elif file_rating is None and not currentvalue: - return (emby_rating, comment, False) - - self.logMsg("getSongTagsFromFile --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue)) - - updateFileRating = False - updateEmbyRating = False - - if currentvalue != None: - # we need to translate the emby values... - if emby_rating == 1 and currentvalue == 2: - emby_rating = 2 - if emby_rating == 3 and currentvalue == 4: - emby_rating = 4 - - if (emby_rating == file_rating) and (file_rating != currentvalue): - #the rating has been updated from kodi itself, update change to both emby ands file - rating = currentvalue - updateFileRating = True - updateEmbyRating = True - elif (emby_rating != currentvalue) and (file_rating == currentvalue): - #emby rating changed - update the file - rating = emby_rating - updateFileRating = True - elif (file_rating != currentvalue) and (emby_rating == currentvalue): - #file rating was updated, sync change to emby - rating = file_rating - updateEmbyRating = True - elif (emby_rating != currentvalue) and (file_rating != currentvalue): - #both ratings have changed (corner case) - the highest rating wins... - if emby_rating > file_rating: - rating = emby_rating - updateFileRating = True - else: - rating = file_rating - updateEmbyRating = True - else: - #nothing has changed, just return the current value - rating = currentvalue - else: - # no rating yet in DB, always prefer file details... - rating = file_rating - updateEmbyRating = True - - if updateFileRating: - musicutils.updateRatingToFile(rating, filename) - - if updateEmbyRating: - # sync details to emby server. Translation needed between ID3 rating and emby likes/favourites: - like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(rating) - API.updateUserRating(embyid, like, favourite, deletelike) - - return (rating, comment, hasEmbeddedCover) - def remove(self, itemid): # Remove kodiid, fileid, pathid, emby reference emby_db = self.emby_db diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index 2625cc7f..915714ec 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -17,9 +17,9 @@ import base64 def logMsg(msg, lvl=1): utils.logMsg("%s %s" % ("Emby", "musictools"), msg, lvl) -def getRealFileName(filename): +def getRealFileName(filename,useTemp = False): #get the filename path accessible by python if possible... - isTemp = False + useTemp = False if not xbmcvfs.exists(filename): logMsg( "File does not exist! %s" %(filename), 0) @@ -38,14 +38,16 @@ def getRealFileName(filename): filename = filename.replace("smb://","\\\\").replace("/","\\") else: #file can not be accessed by python directly, we copy it for processing... - isTemp = True + useTemp = True + + if useTemp: if "/" in filename: filepart = filename.split("/")[-1] else: filepart = filename.split("\\")[-1] tempfile = "special://temp/"+filepart xbmcvfs.copy(filename, tempfile) filename = xbmc.translatePath(tempfile).decode("utf-8") - return (isTemp,filename) + return (useTemp,filename) def getEmbyRatingFromKodiRating(rating): # Translation needed between Kodi/ID3 rating and emby likes/favourites: @@ -61,7 +63,112 @@ def getEmbyRatingFromKodiRating(rating): if (rating == 1 or rating == 2): deletelike = True if (rating >= 5): favourite = True return(like, favourite, deletelike) + +def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enableimportsongrating, enableexportsongrating, enableupdatesongrating): + previous_values = None + filename = API.getFilePath() + rating = 0 + emby_rating = int(round(emby_rating, 0)) + #get file rating and comment tag from file itself. + if enableimportsongrating: + file_rating, comment, hasEmbeddedCover = getSongTags(filename) + else: + file_rating = 0 + comment = "" + hasEmbeddedCover = False + + + emby_dbitem = emby_db.getItem_byId(embyid) + try: + kodiid = emby_dbitem[0] + except TypeError: + # Item is not in database. + currentvalue = None + else: + query = "SELECT rating FROM song WHERE idSong = ?" + kodicursor.execute(query, (kodiid,)) + try: + currentvalue = int(round(float(kodicursor.fetchone()[0]),0)) + except: currentvalue = None + + # Only proceed if we actually have a rating from the file + if file_rating is None and currentvalue: + return (currentvalue, comment, False) + elif file_rating is None and not currentvalue: + return (emby_rating, comment, False) + + logMsg("getAdditionalSongTags --> embyid: %s - emby_rating: %s - file_rating: %s - current rating in kodidb: %s" %(embyid, emby_rating, file_rating, currentvalue)) + + updateFileRating = False + updateEmbyRating = False + + if currentvalue != None: + # we need to translate the emby values... + if emby_rating == 1 and currentvalue == 2: + emby_rating = 2 + if emby_rating == 3 and currentvalue == 4: + emby_rating = 4 + + #if updating rating into file is disabled, we ignore the rating in the file... + if not enableupdatesongrating: + file_rating = currentvalue + #if convert emby likes/favourites convert to song rating is disabled, we ignore the emby rating... + if not enableexportsongrating: + emby_rating = currentvalue + + if (emby_rating == file_rating) and (file_rating != currentvalue): + #the rating has been updated from kodi itself, update change to both emby ands file + rating = currentvalue + updateFileRating = True + updateEmbyRating = True + elif (emby_rating != currentvalue) and (file_rating == currentvalue): + #emby rating changed - update the file + rating = emby_rating + updateFileRating = True + elif (file_rating != currentvalue) and (emby_rating == currentvalue): + #file rating was updated, sync change to emby + rating = file_rating + updateEmbyRating = True + elif (emby_rating != currentvalue) and (file_rating != currentvalue): + #both ratings have changed (corner case) - the highest rating wins... + if emby_rating > file_rating: + rating = emby_rating + updateFileRating = True + else: + rating = file_rating + updateEmbyRating = True + else: + #nothing has changed, just return the current value + rating = currentvalue + else: + # no rating yet in DB + if enableimportsongrating: + #prefer the file rating + rating = file_rating + #determine if we should also send the rating to emby server + if enableexportsongrating: + if emby_rating == 1 and file_rating == 2: + emby_rating = 2 + if emby_rating == 3 and file_rating == 4: + emby_rating = 4 + if emby_rating != file_rating: + updateEmbyRating = True + + elif enableexportsongrating: + #set the initial rating to emby value + rating = emby_rating + + if updateFileRating and enableupdatesongrating: + updateRatingToFile(rating, filename) + + if updateEmbyRating and enableexportsongrating: + # sync details to emby server. Translation needed between ID3 rating and emby likes/favourites: + like, favourite, deletelike = getEmbyRatingFromKodiRating(rating) + API.updateUserRating(embyid, like, favourite, deletelike) + + return (rating, comment, hasEmbeddedCover) + def getSongTags(file): # Get the actual ID3 tags for music songs as the server is lacking that info rating = 0 @@ -120,31 +227,33 @@ def getSongTags(file): def updateRatingToFile(rating, file): #update the rating from Emby to the file - isTemp,filename = getRealFileName(file) - logMsg( "setting song rating: %s for filename: %s" %(rating,filename)) + isTemp,tempfile = getRealFileName(file,True) + logMsg( "setting song rating: %s for filename: %s" %(rating,tempfile)) - if not filename: + if not tempfile: return try: - if filename.lower().endswith(".flac"): - audio = FLAC(filename) + if tempfile.lower().endswith(".flac"): + audio = FLAC(tempfile) calcrating = int(round((float(rating) / 5) * 100, 0)) audio["rating"] = str(calcrating) audio.save() - elif filename.lower().endswith(".mp3"): - audio = ID3(filename) + elif tempfile.lower().endswith(".mp3"): + audio = ID3(tempfile) calcrating = int(round((float(rating) / 5) * 255, 0)) audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1)) audio.save() else: - logMsg( "Not supported fileformat: %s" %(filename)) + logMsg( "Not supported fileformat: %s" %(tempfile)) - #remove tempfile if needed.... - if isTemp: + #once we have succesfully written the flags we move the temp file to destination, otherwise not proceeding and just delete the temp + #safety check: we check if we can read the new tags successfully from the tempfile before deleting anything + checksum_rating, checksum_comment, checksum_hasEmbeddedCover = getSongTags(tempfile) + if checksum_rating == rating: xbmcvfs.delete(file) - xbmcvfs.copy(filename,file) - xbmcvfs.delete(filename) + xbmcvfs.copy(tempfile,file) + xbmcvfs.delete(tempfile) except Exception as e: #file in use ? diff --git a/resources/settings.xml b/resources/settings.xml index e26d4564..6191e1db 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -60,6 +60,10 @@ + + + + From 79644ca3e8288a6166e695428b03dfd5c5a0cb9c Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Wed, 20 Jan 2016 20:35:43 +0100 Subject: [PATCH 33/47] additional fix to prevent doubled updated --- resources/lib/itemtypes.py | 9 +++++++++ resources/lib/musicutils.py | 1 + 2 files changed, 10 insertions(+) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 7397b9f6..51e76532 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -2191,9 +2191,18 @@ class Music(Items): return if mediatype == "song": + + #should we ignore this item ? + #happens when userdata updated by ratings method + if utils.window("ignore-update-%s" %itemid): + utils.window("ignore-update-%s" %itemid,clear=True) + return + # Process playstates playcount = userdata['PlayCount'] dateplayed = userdata['LastPlayedDate'] + + #process item ratings rating, comment, hasEmbeddedCover = musicutils.getAdditionalSongTags(itemid, rating, API, kodicursor, emby_db, self.enableimportsongrating, self.enableexportsongrating, self.enableupdatesongrating) query = "UPDATE song SET iTimesPlayed = ?, lastplayed = ?, rating = ? WHERE idSong = ?" diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index 915714ec..970ae643 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -165,6 +165,7 @@ def getAdditionalSongTags(embyid, emby_rating, API, kodicursor, emby_db, enablei if updateEmbyRating and enableexportsongrating: # sync details to emby server. Translation needed between ID3 rating and emby likes/favourites: like, favourite, deletelike = getEmbyRatingFromKodiRating(rating) + utils.window("ignore-update-%s" %embyid, "true") #set temp windows prop to ignore the update from webclient update API.updateUserRating(embyid, like, favourite, deletelike) return (rating, comment, hasEmbeddedCover) From 435982b74110948f51dd3fa173950ce562ab539e Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Wed, 20 Jan 2016 20:44:19 +0100 Subject: [PATCH 34/47] fix typo --- resources/lib/musicutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index 970ae643..b9d6bd3b 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -21,7 +21,7 @@ def getRealFileName(filename,useTemp = False): #get the filename path accessible by python if possible... useTemp = False - if not xbmcvfs.exists(filename): + if not xbmcvfs.exists(filename) and not useTemp: logMsg( "File does not exist! %s" %(filename), 0) return (False, "") From 8e544b4dadbf8e3bb3d3dad54982ad446c77e205 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Wed, 20 Jan 2016 21:17:23 +0100 Subject: [PATCH 35/47] more improvements to music tags - add checksum when writing tags to files --- resources/lib/musicutils.py | 41 ++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/resources/lib/musicutils.py b/resources/lib/musicutils.py index b9d6bd3b..92fbaf66 100644 --- a/resources/lib/musicutils.py +++ b/resources/lib/musicutils.py @@ -17,11 +17,10 @@ import base64 def logMsg(msg, lvl=1): utils.logMsg("%s %s" % ("Emby", "musictools"), msg, lvl) -def getRealFileName(filename,useTemp = False): +def getRealFileName(filename, isTemp=False): #get the filename path accessible by python if possible... - useTemp = False - - if not xbmcvfs.exists(filename) and not useTemp: + + if not xbmcvfs.exists(filename): logMsg( "File does not exist! %s" %(filename), 0) return (False, "") @@ -38,16 +37,14 @@ def getRealFileName(filename,useTemp = False): filename = filename.replace("smb://","\\\\").replace("/","\\") else: #file can not be accessed by python directly, we copy it for processing... - useTemp = True - - if useTemp: + isTemp = True if "/" in filename: filepart = filename.split("/")[-1] else: filepart = filename.split("\\")[-1] tempfile = "special://temp/"+filepart xbmcvfs.copy(filename, tempfile) filename = xbmc.translatePath(tempfile).decode("utf-8") - return (useTemp,filename) + return (isTemp,filename) def getEmbyRatingFromKodiRating(rating): # Translation needed between Kodi/ID3 rating and emby likes/favourites: @@ -219,7 +216,7 @@ def getSongTags(file): #file in use ? logMsg("Exception in getSongTags %s" %e,0) rating = None - + #remove tempfile if needed.... if isTemp: xbmcvfs.delete(filename) @@ -228,8 +225,18 @@ def getSongTags(file): def updateRatingToFile(rating, file): #update the rating from Emby to the file - isTemp,tempfile = getRealFileName(file,True) - logMsg( "setting song rating: %s for filename: %s" %(rating,tempfile)) + f = xbmcvfs.File(file) + org_size = f.size() + f.close() + + #create tempfile + if "/" in file: filepart = file.split("/")[-1] + else: filepart = file.split("\\")[-1] + tempfile = "special://temp/"+filepart + xbmcvfs.copy(file, tempfile) + tempfile = xbmc.translatePath(tempfile).decode("utf-8") + + logMsg( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile)) if not tempfile: return @@ -249,11 +256,17 @@ def updateRatingToFile(rating, file): logMsg( "Not supported fileformat: %s" %(tempfile)) #once we have succesfully written the flags we move the temp file to destination, otherwise not proceeding and just delete the temp - #safety check: we check if we can read the new tags successfully from the tempfile before deleting anything - checksum_rating, checksum_comment, checksum_hasEmbeddedCover = getSongTags(tempfile) - if checksum_rating == rating: + #safety check: we check the file size of the temp file before proceeding with overwite of original file + f = xbmcvfs.File(tempfile) + checksum_size = f.size() + f.close() + if checksum_size >= org_size: xbmcvfs.delete(file) xbmcvfs.copy(tempfile,file) + else: + logMsg( "Checksum mismatch for filename: %s - using tempfile: %s - not proceeding with file overwite!" %(rating,file,tempfile)) + + #always delete the tempfile xbmcvfs.delete(tempfile) except Exception as e: From a5d4c881e115084ce7c7f561f6b89184d8d93840 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Thu, 21 Jan 2016 11:10:48 +0100 Subject: [PATCH 36/47] remove redundant debug logging --- resources/lib/playutils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index 443792de..10575a71 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -34,8 +34,6 @@ class PlayUtils(): def getPlayUrl(self): item = self.item - print "playutilks" - print item playurl = None if item.get('Type') in ["Recording","TvChannel"] and item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http": From cf943057438987efb6ae83b08ae0350fa00b5ab4 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Thu, 21 Jan 2016 11:15:31 +0100 Subject: [PATCH 37/47] prevent possible unicode error in window method --- resources/lib/utils.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 7878a08e..11a059b3 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -43,10 +43,16 @@ def window(property, value=None, clear=False, windowid=10000): # Get or set window property WINDOW = xbmcgui.Window(windowid) + #setproperty accepts both string and unicode but utf-8 strings are adviced by kodi devs because some unicode can give issues + if isinstance(property, unicode): + property = property.encode("utf-8") + if isinstance(value, unicode): + value = value.encode("utf-8") + if clear: WINDOW.clearProperty(property) - elif value is not None: #setproperty accepts both string and unicode but utf-8 string seems best practice to pass - WINDOW.setProperty(property.encode("utf-8"), value.encode("utf-8")) + elif value is not None: + WINDOW.setProperty(property, value) else: #getproperty returns string so convert to unicode return WINDOW.getProperty(property).decode("utf-8") From f3c8ba707773394fe9162a9cbac0abb330f8329d Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 21 Jan 2016 11:10:01 -0600 Subject: [PATCH 38/47] New transcode method for subs Extract subtitles and append them to the player, rather than burning them (burning them would result in playback failure 80% of the time) --- resources/lib/playbackutils.py | 4 ++-- resources/lib/playutils.py | 25 +++++++++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index 4ab5ba0f..3ec60b64 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -189,7 +189,7 @@ class PlaybackUtils(): # For transcoding only, ask for audio/subs pref if utils.window('emby_%s.playmethod' % playurl) == "Transcode": - playurl = playutils.audioSubsPref(playurl) + playurl = playutils.audioSubsPref(playurl, listitem) utils.window('emby_%s.playmethod' % playurl, value="Transcode") listitem.setPath(playurl) @@ -232,7 +232,7 @@ class PlaybackUtils(): playmethod = utils.window('%s.playmethod' % embyitem) # Only for direct play and direct stream subtitles = self.externalSubs(playurl) - if playmethod in ("DirectStream", "Transcode"): + if playmethod != "Transcode": # Direct play automatically appends external listitem.setSubtitles(subtitles) diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index 10575a71..8b5474aa 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -323,7 +323,7 @@ class PlayUtils(): # max bit rate supported by server (max signed 32bit integer) return bitrate.get(videoQuality, 2147483) - def audioSubsPref(self, url): + def audioSubsPref(self, url, listitem): # For transcoding only # Present the list of audio to select from audioStreamsList = {} @@ -331,6 +331,7 @@ class PlayUtils(): audioStreamsChannelsList = {} subtitleStreamsList = {} subtitleStreams = ['No subtitles'] + downloadableStreams = [] selectAudioIndex = "" selectSubsIndex = "" playurlprefs = "%s" % url @@ -361,8 +362,8 @@ class PlayUtils(): audioStreams.append(track) elif 'Subtitle' in type: - if stream['IsExternal']: - continue + '''if stream['IsExternal']: + continue''' try: track = "%s - %s" % (index, stream['Language']) except: @@ -370,10 +371,14 @@ class PlayUtils(): default = stream['IsDefault'] forced = stream['IsForced'] + downloadable = stream['IsTextSubtitleStream'] + if default: track = "%s - Default" % track if forced: track = "%s - Forced" % track + if downloadable: + downloadableStreams.append(index) subtitleStreamsList[track] = index subtitleStreams.append(track) @@ -401,7 +406,19 @@ class PlayUtils(): # User selected subtitles selected = subtitleStreams[resp] selectSubsIndex = subtitleStreamsList[selected] - playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex + + # Load subtitles in the listitem if downloadable + if selectSubsIndex in downloadableStreams: + + 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) + listitem.setSubtitles(url) + else: + # Burn subtitles + playurlprefs += "&SubtitleStreamIndex=%s" % selectSubsIndex + else: # User backed out of selection playurlprefs += "&SubtitleStreamIndex=%s" % mediasources.get('DefaultSubtitleStreamIndex', "") From 8adfa7a91183ee877fea58d09ffc6ff8c2f2b17f Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 21 Jan 2016 13:13:26 -0600 Subject: [PATCH 39/47] Missing version bump --- addon.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon.xml b/addon.xml index 8cd6d717..4948d7ab 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ From 65be0b6262f58bed985959989c0877a37998162f Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 21 Jan 2016 14:35:24 -0600 Subject: [PATCH 40/47] Fixing unicode bug for device name Removing decode to unicode, because we use it for the downloadutils. We need it in utf-8, not unicode. --- resources/lib/clientinfo.py | 2 +- resources/lib/downloadutils.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/lib/clientinfo.py b/resources/lib/clientinfo.py index 44ed98f2..94f7d207 100644 --- a/resources/lib/clientinfo.py +++ b/resources/lib/clientinfo.py @@ -44,7 +44,7 @@ class ClientInfo(): if utils.settings('deviceNameOpt') == "false": # Use Kodi's deviceName - deviceName = xbmc.getInfoLabel('System.FriendlyName').decode('utf-8') + deviceName = xbmc.getInfoLabel('System.FriendlyName') else: deviceName = utils.settings('deviceName') deviceName = deviceName.replace("\"", "_") diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index 578c932c..e68d1951 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -189,7 +189,7 @@ class DownloadUtils(): # If user is not authenticated auth = ( 'MediaBrowser Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' - % (deviceName, deviceId, version)) + % (deviceName, deviceId.encode('utf-8'), version.encode('utf-8'))) header = { 'Content-type': 'application/json', @@ -205,7 +205,8 @@ class DownloadUtils(): # Attached to the requests session auth = ( 'MediaBrowser UserId="%s", Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' - % (userId, deviceName, deviceId, version)) + % (userId.encode('utf-8'), deviceName, deviceId.encode('utf-8'), + version.encode('utf-8'))) header = { 'Content-type': 'application/json', @@ -274,7 +275,6 @@ class DownloadUtils(): verify=verifyssl) elif type == "POST": - print url r = requests.post(url, json=postBody, headers=header, From c54108cfa748cc04b258ee6a64afc9d504b78488 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 21 Jan 2016 16:32:30 -0600 Subject: [PATCH 41/47] Normalize device name Server is unable to display unicode correctly for device names. Luke is aware of this. --- resources/lib/clientinfo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/lib/clientinfo.py b/resources/lib/clientinfo.py index 94f7d207..4479d8b9 100644 --- a/resources/lib/clientinfo.py +++ b/resources/lib/clientinfo.py @@ -45,6 +45,7 @@ class ClientInfo(): if utils.settings('deviceNameOpt') == "false": # Use Kodi's deviceName deviceName = xbmc.getInfoLabel('System.FriendlyName') + deviceName = utils.normalize_string(deviceName) else: deviceName = utils.settings('deviceName') deviceName = deviceName.replace("\"", "_") From c28b43451f071c904c5f9255d6bc94be21e261ac Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 21 Jan 2016 16:34:35 -0600 Subject: [PATCH 42/47] Revert useless unicode changes Now that the device name is normalized without special characters. --- resources/lib/downloadutils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index e68d1951..1c244e74 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -189,7 +189,7 @@ class DownloadUtils(): # If user is not authenticated auth = ( 'MediaBrowser Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' - % (deviceName, deviceId.encode('utf-8'), version.encode('utf-8'))) + % (deviceName, deviceId, version)) header = { 'Content-type': 'application/json', @@ -205,8 +205,7 @@ class DownloadUtils(): # Attached to the requests session auth = ( 'MediaBrowser UserId="%s", Client="Kodi", Device="%s", DeviceId="%s", Version="%s"' - % (userId.encode('utf-8'), deviceName, deviceId.encode('utf-8'), - version.encode('utf-8'))) + % (userId, deviceName, deviceId, version)) header = { 'Content-type': 'application/json', From 0363804c8e875b7e1efe568ba693420d34ae18f1 Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Thu, 21 Jan 2016 23:45:51 +0100 Subject: [PATCH 43/47] fix music sync when directstream enabled --- addon.xml | 2 +- resources/lib/itemtypes.py | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/addon.xml b/addon.xml index 8cd6d717..4948d7ab 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 51e76532..e2ad16c4 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -1948,17 +1948,16 @@ class Music(Items): track = disc*2**16 + tracknumber year = item.get('ProductionYear') duration = API.getRuntime() - - #the server only returns the rating based on like/love and not the actual rating from the song - rating = userdata['UserRating'] - - #the server doesn't support comment on songs so this will always be empty - comment = API.getOverview() - + #if enabled, try to get the rating from file and/or emby if not self.directstream: rating, comment, hasEmbeddedCover = musicutils.getAdditionalSongTags(itemid, rating, API, kodicursor, emby_db, self.enableimportsongrating, self.enableexportsongrating, self.enableupdatesongrating) - + else: + hasEmbeddedCover = False + comment = API.getOverview() + rating = userdata['UserRating'] + + ##### GET THE FILE AND PATH ##### if self.directstream: path = "%s/emby/Audio/%s/" % (self.server, itemid) From af4b3bdd9eeecaf22680793698a2692c63ecc49d Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Fri, 22 Jan 2016 01:08:08 +0100 Subject: [PATCH 44/47] additional comments --- resources/lib/entrypoint.py | 9 +++++---- resources/settings.xml | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 1240e738..4e961238 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -432,13 +432,14 @@ def BrowseContent(viewname, type="", folderid=None, filter=""): elif type == "tvchannels": listing = emby.getTvChannels() elif filter == "recent": - listing = emby.getFilteredSection("", itemtype=itemtype.split(",")[0], sortby="DateCreated", recursive=True, limit=25, sortorder="Descending") + #why don't we get a recursive result when the parentid is set ? + listing = emby.getFilteredSection(parentid="", itemtype=itemtype.split(",")[0], sortby="DateCreated", recursive=True, limit=25, sortorder="Descending") elif filter == "random": - listing = emby.getFilteredSection("", itemtype=itemtype.split(",")[0], sortby="Random", recursive=True, limit=150, sortorder="Descending") + listing = emby.getFilteredSection(parentid="", itemtype=itemtype.split(",")[0], sortby="Random", recursive=True, limit=150, sortorder="Descending") elif filter == "recommended": - listing = emby.getFilteredSection("", itemtype=itemtype.split(",")[0], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite") + listing = emby.getFilteredSection(parentid="", itemtype=itemtype.split(",")[0], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite") elif filter == "sets": - listing = emby.getFilteredSection("", itemtype=itemtype.split(",")[1], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite") + listing = emby.getFilteredSection(parentid="", itemtype=itemtype.split(",")[1], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite") else: listing = emby.getFilteredSection(folderid, itemtype=itemtype, recursive=False) diff --git a/resources/settings.xml b/resources/settings.xml index 6191e1db..4a9ba88f 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -60,7 +60,7 @@ - + From 3caa2ae3a70a2b9da612ad9417c26bb6a16251d6 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 21 Jan 2016 19:19:25 -0600 Subject: [PATCH 45/47] Re-add decode to unicode Then convert to utf-8, then ascii for the auth header. --- resources/lib/clientinfo.py | 3 +-- resources/lib/downloadutils.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/clientinfo.py b/resources/lib/clientinfo.py index 4479d8b9..44ed98f2 100644 --- a/resources/lib/clientinfo.py +++ b/resources/lib/clientinfo.py @@ -44,8 +44,7 @@ class ClientInfo(): if utils.settings('deviceNameOpt') == "false": # Use Kodi's deviceName - deviceName = xbmc.getInfoLabel('System.FriendlyName') - deviceName = utils.normalize_string(deviceName) + deviceName = xbmc.getInfoLabel('System.FriendlyName').decode('utf-8') else: deviceName = utils.settings('deviceName') deviceName = deviceName.replace("\"", "_") diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index 1c244e74..0a1e0e28 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -182,6 +182,7 @@ class DownloadUtils(): clientInfo = self.clientInfo deviceName = clientInfo.getDeviceName() + deviceName = utils.normalize_string(deviceName.encode('utf-8')) deviceId = clientInfo.getDeviceId() version = clientInfo.getVersion() From 333d6b283115237894452166ffb27b7d7c1e32ff Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Fri, 22 Jan 2016 11:10:42 +0100 Subject: [PATCH 46/47] fix subnodes for homevideos and photos --- default.py | 5 +++-- resources/lib/entrypoint.py | 36 +++++++++++++++++++++++++++--------- resources/lib/videonodes.py | 4 ++-- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/default.py b/default.py index fadaa05a..a3c461e7 100644 --- a/default.py +++ b/default.py @@ -58,6 +58,7 @@ class Main: 'channels': entrypoint.BrowseChannels, 'channelsfolder': entrypoint.BrowseChannels, 'browsecontent': entrypoint.BrowseContent, + 'getsubfolders': entrypoint.GetSubFolders, 'nextup': entrypoint.getNextUpEpisodes, 'inprogressepisodes': entrypoint.getInProgressEpisodes, 'recentepisodes': entrypoint.getRecentEpisodes, @@ -77,11 +78,11 @@ class Main: limit = int(params['limit'][0]) modes[mode](itemid, limit) - elif mode == "channels": + elif mode in ["channels","getsubfolders"]: modes[mode](itemid) elif mode == "browsecontent": - modes[mode]( itemid, params.get('type',[""])[0], params.get('folderid',[""])[0], params.get('filter',[""])[0] ) + modes[mode]( itemid, params.get('type',[""])[0], params.get('folderid',[""])[0] ) elif mode == "channelsfolder": folderid = params['folderid'][0] diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 4e961238..79b79e88 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -69,7 +69,7 @@ def doMainListing(): label = utils.window('Emby.nodes.%s.title' % i) type = utils.window('Emby.nodes.%s.type' % i) #because we do not use seperate entrypoints for each content type, we need to figure out which items to show in each listing. - #for now we just only show picture nodes in the picture librarym video nodes in the video library and all nodes in any other window + #for now we just only show picture nodes in the picture library video nodes in the video library and all nodes in any other window if path and xbmc.getCondVisibility("Window.IsActive(Pictures)") and type == "photos": addDirectoryItem(label, path) elif path and xbmc.getCondVisibility("Window.IsActive(VideoLibrary)") and type != "photos": @@ -399,13 +399,31 @@ def refreshPlaylist(): time=1000, sound=False) +#### SHOW SUBFOLDERS FOR NODE ##### +def GetSubFolders(nodeindex): + nodetypes = ["",".recent",".recentepisodes",".inprogress",".inprogressepisodes",".unwatched",".nextepisodes",".sets",".genres",".random",".recommended"] + for node in nodetypes: + title = utils.window('Emby.nodes.%s%s.title' %(nodeindex,node)) + if title: + path = utils.window('Emby.nodes.%s%s.content' %(nodeindex,node)) + type = utils.window('Emby.nodes.%s%s.type' %(nodeindex,node)) + addDirectoryItem(title, path) + xbmcplugin.endOfDirectory(int(sys.argv[1])) + ##### BROWSE EMBY NODES DIRECTLY ##### -def BrowseContent(viewname, type="", folderid=None, filter=""): +def BrowseContent(viewname, type="", folderid=""): emby = embyserver.Read_EmbyServer() art = artwork.Artwork() doUtils = downloadutils.DownloadUtils() - utils.logMsg("BrowseHomeVideos","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname, type, folderid, filter)) + + #folderid used as filter ? + if folderid in ["recent","recentepisodes","inprogress","inprogressepisodes","unwatched","nextepisodes","sets","genres","random","recommended"]: + filter = folderid + folderid = "" + else: + filter = "" + xbmcplugin.setPluginCategory(int(sys.argv[1]), viewname) #get views for root level if not folderid: @@ -414,6 +432,7 @@ def BrowseContent(viewname, type="", folderid=None, filter=""): if view.get("name") == viewname: folderid = view.get("id") + utils.logMsg("BrowseContent","viewname: %s - type: %s - folderid: %s - filter: %s" %(viewname, type, folderid, filter)) #set the correct params for the content type #only proceed if we have a folderid if folderid: @@ -432,14 +451,13 @@ def BrowseContent(viewname, type="", folderid=None, filter=""): elif type == "tvchannels": listing = emby.getTvChannels() elif filter == "recent": - #why don't we get a recursive result when the parentid is set ? - listing = emby.getFilteredSection(parentid="", itemtype=itemtype.split(",")[0], sortby="DateCreated", recursive=True, limit=25, sortorder="Descending") + listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="DateCreated", recursive=True, limit=25, sortorder="Descending") elif filter == "random": - listing = emby.getFilteredSection(parentid="", itemtype=itemtype.split(",")[0], sortby="Random", recursive=True, limit=150, sortorder="Descending") + listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="Random", recursive=True, limit=150, sortorder="Descending") elif filter == "recommended": - listing = emby.getFilteredSection(parentid="", itemtype=itemtype.split(",")[0], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite") + listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[0], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite") elif filter == "sets": - listing = emby.getFilteredSection(parentid="", itemtype=itemtype.split(",")[1], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite") + listing = emby.getFilteredSection(folderid, itemtype=itemtype.split(",")[1], sortby="SortName", recursive=True, limit=25, sortorder="Ascending", filter="IsFavorite") else: listing = emby.getFilteredSection(folderid, itemtype=itemtype, recursive=False) @@ -456,7 +474,7 @@ def BrowseContent(viewname, type="", folderid=None, filter=""): xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=li.getProperty("path"), listitem=li) - xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) + xbmcplugin.endOfDirectory(handle=int(sys.argv[1]),succeeded=True,updateListing=False,cacheToDisc=False) if filter == "recent": xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE) else: diff --git a/resources/lib/videonodes.py b/resources/lib/videonodes.py index bc8f1eb3..4ef8d273 100644 --- a/resources/lib/videonodes.py +++ b/resources/lib/videonodes.py @@ -96,7 +96,7 @@ class VideoNodes(object): return if mediatype=="photos": - path = "plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=photos&filter=index" % tagname + path = "plugin://plugin.video.emby/?id=%s&mode=getsubfolders" % indexnumber utils.window('Emby.nodes.%s.index' % indexnumber, value=path) @@ -192,7 +192,7 @@ class VideoNodes(object): path = "plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s" %(tagname,mediatype) elif (mediatype == "homevideos" or mediatype == "photos"): # Custom query - path = "plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s&filter=%s" %(tagname,mediatype,nodetype) + path = "plugin://plugin.video.emby/?id=%s&mode=browsecontent&type=%s&folderid=%s" %(tagname,mediatype,nodetype) elif nodetype == "nextepisodes": # Custom query path = "plugin://plugin.video.emby/?id=%s&mode=nextup&limit=25" % tagname From 01db164dc9f05209ab4f6a4b8fc6818fd554307c Mon Sep 17 00:00:00 2001 From: marcelveldt Date: Fri, 22 Jan 2016 11:41:12 +0100 Subject: [PATCH 47/47] do not create playlists for homevideos or photos --- resources/lib/entrypoint.py | 2 +- resources/lib/librarysync.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 79b79e88..58053e1f 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -474,7 +474,7 @@ def BrowseContent(viewname, type="", folderid=""): xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=li.getProperty("path"), listitem=li) - xbmcplugin.endOfDirectory(handle=int(sys.argv[1]),succeeded=True,updateListing=False,cacheToDisc=False) + xbmcplugin.endOfDirectory(handle=int(sys.argv[1])) if filter == "recent": xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_DATE) else: diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 07d1c218..babe1985 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -415,7 +415,8 @@ class LibrarySync(threading.Thread): viewtype=current_viewtype, delete=True) # Added new playlist - utils.playlistXSP(mediatype, foldername, viewtype) + if mediatype in ['movies', 'tvshows', 'musicvideos']: + utils.playlistXSP(mediatype, foldername, viewtype) # Add new video node if mediatype != "musicvideos": vnodes.viewNode(totalnodes, foldername, mediatype, viewtype) @@ -430,7 +431,8 @@ class LibrarySync(threading.Thread): else: if mediatype != "music": # Validate the playlist exists or recreate it - utils.playlistXSP(mediatype, foldername, viewtype) + if mediatype in ['movies', 'tvshows', 'musicvideos']: + utils.playlistXSP(mediatype, foldername, viewtype) # Create the video node if not already exists if mediatype != "musicvideos": vnodes.viewNode(totalnodes, foldername, mediatype, viewtype)